• Day 35 GIL全局解释器锁,死锁与递归锁,信号量,event事件,线程queue


    一、GIL全局解释器

    GIL是一个互斥锁:保证数据的安全(以牺牲效率来换取数据的安全)

    阻止同一个进程内的多个线程同时执行(不能并行但是能够实现并发)

    并发:看起来像同时进行的

    GIL全局解释器存在的原因是因为Cpython解释器的内存管理不是线程安全的

    垃圾回收机制
    1.引用计数
    2.标记清除
    3.分代回收

    同一个进程下的多个线程不能实现并行但是能够实现并发,多个进程下的线程能够实现并行

    问题:python多线程是不是就没有用了呢?
    四个任务:计算密集的任务 每个任务耗时10s
    单核情况下:
    多线程好一点,消耗的资源少一点
    多核情况下:
    开四个进程:10s多一点
    开四个线程:40s多一点

    多线程和多进程都有自己的优点,要根据项目需求合理选择
    # 计算密集型
    from multiprocessing import Process
    from threading import Thread
    import os,time
    def work():
        res=0
        for i in range(100000000):
            res*=i
    
    
    if __name__ == '__main__':
        l=[]
        print(os.cpu_count())  # 本机为8核
        start=time.time()
        for i in range(8):
            # p=Process(target=work) #耗时9.252728939056396
            p=Thread(target=work) #耗时35.369622230529785
            l.append(p)
            p.start()
        for p in l:
            p.join()
        stop=time.time()
        print('run time is %s' %(stop-start))
    cpu越多越好,但是对于I/O来说,再多的cpu也没用
    四个任务:IO密集的任务   每个任务io 10s
    单核情况下:
    多线程好一点
    多核情况下:
    多线程好一点
    from multiprocessing import Process
    from threading import Thread
    import threading
    import os,time
    def work():
        time.sleep(2)
    
    
    if __name__ == '__main__':
        l=[]
        print(os.cpu_count()) #本机为8核
        start=time.time()
        for i in range(600):
            p=Process(target=work) #耗时4.699530839920044
            # p=Thread(target=work) #耗时2.054128885269165
            l.append(p)
            p.start()
        for p in l:
            p.join()
        stop=time.time()
        print('run time is %s' %(stop-start))

    二、GIL与普通锁的区别

    对于不同的数据,要想保证安全,需要加不同的锁处理
    GIL并不能保证数据的安全,它是对Cpython解释器加锁,针对的是线程
    保证的是同一个进程下多个线程之间的安全
    
    
    from threading import Thread,Lock
    import time
    
    mutex = Lock()
    
    n = 100
    
    def task():
        global n
        mutex.acquire()
        tmp = n
        time.sleep(0.1)
        n = tmp - 1
        mutex.release()
    
    t_list = []
    for i in range(100):
        t = Thread(target=task)
        t.start()
        t_list.append(t)
    
    for t in t_list:
        t.join()
    
    print(n)
    
    

    三、死锁与递归锁

    所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁

    from threading import Thread,Lock,RLock
    import time
    
    """
    自定义锁一次acquire必须对应一次release,不能连续acquire
    递归锁可以连续的acquire,每acquire一次计数加一:针对的是第一个抢到我的人
    """
    import random
    #
    # mutexA = Lock()
    # mutexB = Lock()
    mutexA = mutexB = RLock()  # 抢锁之后会有一个计数 抢一次计数加一 针对的是第一个抢到我的人
    
    class MyThead(Thread):
        def run(self):
            self.func1()
            self.func2()
    
        def func1(self):
            mutexA.acquire()
            print('%s 抢到A锁了'%self.name)
            mutexB.acquire()
            print('%s 抢到B锁了' % self.name)
            mutexB.release()
            print('%s 释放了B锁'%self.name)
            mutexA.release()
            print('%s 释放了A锁'%self.name)
    
        def func2(self):
            mutexB.acquire()
            print('%s 抢到了B锁'%self.name)
            time.sleep(1)
            mutexA.acquire()
            print('%s 抢到A锁了' % self.name)
            mutexA.release()
            print('%s 释放了A锁' % self.name)
            mutexB.release()
            print('%s 释放了B锁' % self.name)
    
    
    for i in range(100):
        t = MyThead()
        t.start()

    解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

    这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

    mutexA=mutexB=threading.RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止

    四、信号量

    from threading import Thread,Semaphore
    import time
    import random
    sm = Semaphore(5)  # 五个厕所五把锁
    # 跟你普通的互斥锁区别在于,普通的互斥锁是独立卫生间,所有人抢一把锁
    # 信号量 公共卫生间 有多个坑,所有人抢多把锁
    
    
    
    def task(name):
        sm.acquire()
        print('%s正在蹲坑'%name)
        # 模拟蹲坑耗时
        time.sleep(random.randint(1,5))
        sm.release()
    
    
    if __name__ == '__main__':
        for i in range(20):
            t = Thread(target=task,args=('伞兵%s号'%i,))
            t.start()

    五、event事件

    指程序中的其他线程需要通过判断某个线程的状态来确定自己下一步的操作

    event.isSet():返回event的状态值;
    
    event.wait():如果 event.isSet()==False将阻塞线程;
    
    event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
    
    event.clear():恢复event的状态值为False。
    from threading import Event,Thread
    import time
    import random
    
    
    event = Event()
    
    
    def light():
        print('红灯亮着!')
        time.sleep(3)
        event.set()  # 解除阻塞,给我的event发了一个信号
        print('绿灯亮了!')
    
    
    def car(i):
        print('%s 正在等红灯了'%i)
        event.wait()  # 阻塞
        print('%s 加油门飙车了'%i)
    
    t1 = Thread(target=light)
    t1.start()
    
    
    for i in range(10):
        t = Thread(target=car,args=(i,))
        t.start()

    六、线程queue

    queue.Queue(maxsize=0) #先进先出

    queue.LifoQueue(maxsize=0) #先进后出

    queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列 数字越小先出

    import queue
    
    # 1.普通q
    # 2.先进后出q
    # 3.优先级q
    
    
    q=queue.Queue(3)
    q.put(1)
    q.put(2)
    q.put(3)
    print(q.get())
    print(q.get())
    print(q.get())
    
    q = queue.LifoQueue(5)
    q.put(1)
    q.put(2)
    q.put(3)
    q.put(4)
    print(q.get())
    
    
    
    # 优先级q
    q = queue.PriorityQueue()
    q.put((10,'a'))
    q.put((-1,'b'))
    q.put((100,'c'))
    print(q.get())
    print(q.get())
    print(q.get())
     
  • 相关阅读:
    SOUI更新到2.0
    第三十一篇:SOUI布局之相对于特定兄弟窗口
    SOUI与WTL
    在SOUI中非半透明窗口如何实现圆角窗口?
    拥抱ARM妹纸第二季 之 第一次 点亮太阳
    拥抱ARM妹子第二季 之 序:我和春天有个约会
    解决自定义控件窗体缩放时闪烁
    The 50 Most Essential Pieces of Classical Music
    嵌入式开发目录
    C中浮点数转字符串
  • 原文地址:https://www.cnblogs.com/zhengyuli/p/10831312.html
Copyright © 2020-2023  润新知