• GIL全局解释器锁、死锁递归锁、信号量、Event事件、线程Queue


    GIL全局解释器锁

      GIL本质就是一把互斥锁,和所有互斥锁本质一样,都是把并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务修改,进而保证数据安全

      保护不同的数据的安全,就应该加不同的锁。例如 IO模式下的就应该用多线程 (打开文件,time.sleep,输入输出等等),而计算相关的就是用多进程

      在一个python的进程内,不仅有你运行的py文件的主线程还有由该主线程开启的其他线程,还有解释器开启的垃圾回收等解释器级别的线程,所有线程都运行在这一个进程内。如果多个线程的target=work 那么执行流程是 多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行,解释器的代码是所有线程共享的,所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:对于同一个数据100,可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操作,解决这种问题没有什么高明的方法就是加锁处理,如下图,保证python解释器同一时间只能执行一个任务的代码

    GIL保护的是解释器级的数据,保护用户自己的数据则需要自己加锁处理如下图

    有了GIL的存在,同一时刻同一进城只有一个线程被执行

    对于计算来说,cpu越多越好,但是对于I/O来说 再多cpu也没用

    我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:

    方案一:开启四个进程

    方案二:一个进程下,开启四个线程

    单核情况下:如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜   如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜

    多核情况下:如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜   如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜

    结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

    # 计算密集型:应该使用多进程
    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=[]
        start=time.time()
        for i in range(6):
            p=Process(target=work)#进程用时18.202494621276855
            # p=Thread(target=work)#进程用时32.08451318740845
            l.append(p)
            p.start()
        for p in l:
            p.join()
        stop=time.time()
        print('run time is %s' %(stop-start))
    
    # IO密集型: 应该开启多线程
    # from threading import Thread
    # from multiprocessing import  Process
    # import time,random
    #
    # def task():
    #     time.sleep(2)
    #
    # if __name__ == '__main__':
    #     l=[]
    #     star=time.time()
    #     for i in range(10):
    #         t=Process(target=task) #进程用时3.76550555229187
    #         t=Thread(target=task) #线程用时2.002195119857788
    #         l.append(t)
    #         t.start()
    #     for n in l:
    #         n.join()
    #     stop=time.time()
    #     print(stop-star)
    

    死锁与递归锁

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

     

    from threading import Thread,Lock,RLock
    import time
    mutexA=Lock()
    mutexB=Lock()
    # mutexB=mutexA=RLock()
    
    class Mythead(Thread):
        def run(self):
            self.f1()
            self.f2()
    
        def f1(self):
            mutexA.acquire()
            print('%s 抢到A锁' %self.name)
            mutexB.acquire()
            print('%s 抢到B锁' %self.name)
            mutexB.release()
            mutexA.release()
    
        def f2(self):
            mutexB.acquire()
            print('%s 抢到了B锁' %self.name)
            time.sleep(2)
            mutexA.acquire()
            print('%s 抢到了A锁' %self.name)
            mutexA.release()
            mutexB.release()
    
    if __name__ == '__main__':
        for i in range(100):
            t=Mythead()
            t.start()
    '''
    Thread-1 拿到A锁
    Thread-1 拿到B锁
    Thread-1 拿到B锁
    Thread-2 拿到A锁
    然后就卡住,死锁了
    '''

     解决方法就是递归锁,就是RLock() 递归锁就是一个线程或者进程 抢到锁后 可以多次拿这个锁,而这个锁每被拿一次他内部就会记录一次 ,除非一个进程或者线程所有的acquire 都被 release 其他线程才能去争抢锁

    信号量 Semaphore

    同进程一样

    Semaphore管理一个内置的计数器,每当调用acquire()时计数器-1 调用release时计数器+1 计数器不能小于0,当计数器为0时acquire将阻塞线程直到其他线程调用release

    from threading import Thread,Semaphore
    import time,random
    sm=Semaphore(5)
    
    def task(name):
        sm.acquire()
        print('%s 正在上厕所' %name)
        time.sleep(random.randint(1,3))
        sm.release()
    
    if __name__ == '__main__':
        for i in range(20):
            t=Thread(target=task,args=('路人%s' %i,))
            t.start()
    

     这段代码内Semaphors设置了最大数量5 刚开始进去5个线程 当有两个线程调用release 时 其他的线程就可以再acquire两个。

    Event事件

      线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行

    event.isSet():返回event的状态值;

    event.wait():如果 event.isSet()==False将阻塞线程;

    event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

    event.clear():恢复event的状态值为False。

    from threading import Thread,Event
    import time
    
    event=Event()
    
    def light():
        print('红灯正亮着')
        time.sleep(3)
        event.set() #绿灯亮
    
    def car(name):
        print('车%s正在等绿灯' %name)
        event.wait() #等灯绿
        print('车%s通行' %name)
    
    if __name__ == '__main__':
        # 红绿灯
        t1=Thread(target=light)
        t1.start()
        # 车
        for i in range(10):
            t=Thread(target=car,args=(i,))
            t.start()
    

    线程Queue

      和进程Queue一样

    import queue
    
    # queue.Queue() #先进先出
    q=queue.Queue(3)
    q.put(1)
    q.put(2)
    q.put(3)
    print(q.get())
    print(q.get())
    print(q.get())
    
    # queue.LifoQueue() #后进先出->堆栈
    q=queue.LifoQueue(3)
    q.put(1)
    q.put(2)
    q.put(3)
    print(q.get())
    print(q.get())
    print(q.get())
    
    # queue.PriorityQueue() #优先级
    q=queue.PriorityQueue(3) #优先级,优先级用数字表示,数字越小优先级越高
    q.put((10,'a'))
    q.put((-1,'b'))
    q.put((100,'c'))
    print(q.get())
    print(q.get())
    print(q.get())
    
  • 相关阅读:
    Path类的最全面具体解释
    数据挖掘之分类算法---knn算法(有matlab样例)
    Android View框架的measure机制
    2017年本博客知识体系引导(更新至2017.8.11)
    [DevExpress]DevExpress 中 汉化包 汉化方法
    盗墓笔记第一季全(12集)下载地址
    浅谈spring——注解配置(九)
    git使用系列(一)
    算法与数据结构(一)
    phpstrom 2016.2 注册服务器地址
  • 原文地址:https://www.cnblogs.com/layerluo/p/9606853.html
Copyright © 2020-2023  润新知