zoukankan      html  css  js  c++  java
  • GIL全局解释器锁

    一、GIL介绍

    GIL是一个互斥锁:将并发编程串行,降低了效率但保证了数据的安全

    Cpython解释器中才有的概念,不是python的特点

    保护不同的数据安全,就应该加不同的锁

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

    单进程下多个线程无法利用多核优势是所有解释型语言的通病

    二、GIL解释

    每次运行python解释器都会产生进程,进程下会有多个线程

    如图所示:因为python解释器有垃圾回收机制,所以每次运行python解释器都会自带一个垃圾回收线程,假设该进程下还有3个线程,如果四个线程需要运行,需要去解释器那抢锁(GIL互斥锁),谁抢到了,谁就运行。这样就能防止,如果四个线程运行,线程1申请变量a =1 ,刚申请了一个变量,垃圾回收机制就把它回收。

     

     

    三、GIL与多线程

    研究python的多线程是否需有用需分情况讨论

    四个任务:计算密集的任务,每个任务耗时10秒

    单核情况下:

    多线程好一点,消耗资源少一点

    多核情况下:

    开四个进程:10s多一点

    开四个线程:40s多一点

    from multiprocessing import Process
    from threading import Thread
    import os,time
    def work():
        res=0
        for i in range(10000000):
            res*=i
    
    
    if __name__ == '__main__':
        l=[]
        print(os.cpu_count())  # 本机为4核
        start=time.time()
        for i in range(6):
            p=Process(target=work) # 多进程耗时 3.0867862701416016
            # p=Thread(target=work) # 多线程耗时  5.1183037757873535
            l.append(p)
            p.start()
        for p in l:
            p.join()
        stop=time.time()
        print('run time is %s' %(stop-start))
    计算密集型:更好

    四个任务:IO密集的任务

    单核情况下:

    多线程好

    多核情况下:

    多线程好

    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()) #本机为4核
        start=time.time()
        for i in range(100):
            # p=Process(target=work) # 多进程耗时13.326009750366211多,大部分时间耗费在创建进程上
            p=Thread(target=work) # 多线程耗时2.0372188091278076多
            l.append(p)
            p.start()
        for p in l:
            p.join()
        stop=time.time()
        print('run time is %s' %(stop-start))
    IO密集型:多线程更好

    四、GIL与普通的锁

    from threading import Thread
    import time
    
    n = 100
    
    
    def task():
        global n
        tmp = n
        time.sleep(1)  # 如果在此睡1秒,就会自动释放锁,其他99个人都会抢到锁,打印结果:99;如果不睡,那么此锁就会一直在第一个人手中,打印结果:0
        n = tmp -1
    
    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)

    五、死锁现象与递归锁

    1.死锁现象

    一共有A与B两把锁,两个人在抢锁的过程中会出现抢到对方的锁,却被锁在自己的房间,因而产生了死锁现象。

    from threading import Thread,Lock,current_thread,RLock
    import time
    """
    Rlock可以被第一个抢到锁的人连续的acquire和release
    每acquire一次锁身上的计数加1
    每release一次锁身上的计数减1
    只要锁的计数不为0 其他人都不能抢
    
    """
     mutexA = Lock()
     mutexB = Lock()
    
    
    
    class MyThread(Thread):
        def run(self):  # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发
            self.func1()
            self.func2()
    
        def func1(self):
            mutexA.acquire()
            print('%s抢到了A锁'%self.name)  # self.name等价于current_thread().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(10):
        t = MyThread()
        t.start()
    死锁现象

    2.递归锁

    A与B变成了一把锁,这样只有在一个人抢到锁时,别人就无法抢锁,只有等这个人释放了锁,别人才可以抢锁。

    from threading import Thread,Lock,current_thread,RLock
    import time
    """
    Rlock可以被第一个抢到锁的人连续的acquire和release
    每acquire一次锁身上的计数加1
    每release一次锁身上的计数减1
    只要锁的计数不为0 其他人都不能抢
    
    """
    
    mutexA = mutexB = RLock()  # A B现在是同一把锁
    
    
    class MyThread(Thread):
        def run(self):  # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发
            self.func1()
            self.func2()
    
        def func1(self):
            mutexA.acquire()
            print('%s抢到了A锁'%self.name)  # self.name等价于current_thread().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(10):
        t = MyThread()
        t.start()
    递归锁

    六、信号量

    """
    互斥锁:一个厕所(一个坑位)
    信号量:公共厕所(多个坑位)
    """
    from threading import Semaphore,Thread
    import time
    import random
    
    
    sm = Semaphore(5)  # 造了一个含有五个的坑位的公共厕所
    
    def task(name):
        sm.acquire()
        print('%s占了一个坑位'%name)
        time.sleep(random.randint(1,3))
        sm.release()
    
    for i in range(40):
        t = Thread(target=task,args=(i,))
        t.start()
    抢厕所例子

    七、Even

    同进程的一样

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

    from threading import Event,Thread
    import time
    
    # 先生成一个event对象
    e = Event()
    
    
    def light():
        print('红灯正亮着')
        time.sleep(3)
        e.set()  # 发信号
        print('绿灯亮了')
    
    def car(name):
        print('%s正在等红灯'%name)
        e.wait()  # 等待信号
        print('%s加油门飙车了'%name)
    
    t = Thread(target=light)
    t.start()
    
    for i in range(10):
        t = Thread(target=car,args=('伞兵%s'%i,))
        t.start()
    红绿灯事件

    八、线程queue

    1.class queue.Queue()

    import queue
    q = queue.Queue()
    q.put('1')
    q.put('2')
    q.put('3')
    print(q.get())
    print(q.get())
    print(q.get())
    
    
    打印结果:1  2  3
    先进先出

    2.class queue.LifoQueue()

    import queue
    q = queue.LifoQueue()
    q.put('1')
    q.put('2')
    q.put('3')
    print(q.get())
    print(q.get())
    print(q.get())
    
    
    打印结果:3  2  1
    先进后出

    3.class queue.priorityQueue()

    import queue
    q = queue.PriorityQueue()
    q.put(5)
    q.put(-2)
    q.put(3)
    print(q.get())
    print(q.get())
    print(q.get())
    
    
    打印结果:-2  3  5
    按优先级顺序:数字越小,优先级越高
  • 相关阅读:
    【转】Foreach用法
    【转】PB实现在通知区域添加图标
    手动清理SQLSERVER的日志文件
    2012腾讯实习笔试
    触控(捕鱼达人)笔试
    C和指针第五章操作符和表达式
    浮点数的存储
    C和指针第三章数据
    动态规划
    有关海量数据处理
  • 原文地址:https://www.cnblogs.com/xiongying4/p/11352856.html
Copyright © 2011-2022 走看看