zoukankan      html  css  js  c++  java
  • GIL全局解释器锁Event事件信号量死锁递归锁

    GIL是什么?
    首先我们要知道一个常识,python的解释器有很多种,我们用的是cpython解释器,Cpython是使用C语言编写的
    那么GIL的本质就是一把互斥锁:
    互斥锁:
    将并发转成串行,牺牲了运行效率,但是提高了安全性,锁就是为了安全嘛
    使用来阻止一个进程下多个线程的同时执行,(同一个进程中的多个线程是不能并行的,但是是可以实现并发)
    GIL全局解释器锁的存在是因为cpython中内存管理线程是不安全的,因为垃圾回收机制,如果没有GIL那么会产生刚生成完的变量直接被垃圾回收机制的给回收了
    
    
    那么垃圾回收机制是什么?
    1.引用计数
    内存中的数据如果没有任何的变量名与其有绑定关系,那么会被自动回收
    2.标记清除
    当内存快要被某个应用程序占满的时候,会自动触发
    3.分代回收
    根据值的存活时间的不同,划为不同的等级,等级越高垃圾回收机制扫描的频率越低
    GIL的特点:
    gil不是python解释器的特点,单进程边无法利用多核的优势这是所有解释性语言的通病

    解释性语言是是读一行解决一行,这个和编译性的方法不一样,他不是python的优势,是cpython的
    针对不同的数据我们应该加不同的锁进行处理,不能用一把锁使用到不同的数据上面,GIL是用来保证线程的安全,是不可在其他数据
    上使用的,有了GIL后我们在进程中就不需要加其他的互斥锁


    # 验证实例
    from threading import Thread
    
    import time
    
    n = 100
    
    
    def task():
        global n
        tmp = n
        time.sleep(1)
        # 因为第一个抢到锁后休息一秒钟,产生了IO操作,把锁自动释放让其他的人都去抢到后休息一秒钟
        # 这个时候原来的100-1等于99,这个时候做有的人的手里面的值都是99
        # 如果没有进行io操作,那么下一个抢到锁的人拿到的值就是上一个人处理后的结果
        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(t)
    print(n)
    这样说难道是python中多线程是不是也没法利用多核的优势了吗?
    那这个就研究就一下了,我们从两个方面来举例
    一. 四个任务 计算密集类型方面
    单核情况下
    开线程更省资源,因为开进程要开空间,进程则不需要开空间
    多核情况下
    开进程比开线程快4倍,开进程可以同时计算,但是线程只能一个一个的计算,是需要排队的
    二. 四个任务 io密集类型方面
    单核情况下
    开线程节省资源
    多核情况下
    也是开线程更节省资源,因为他是io操作,和cpu多少核心是没有什么关系反正都要停滞,一个是等待一个是切换.
    结论就是:
    肯定是有用的,但是需要看情况而定,一般情况下呢都是多进程+多线程配合使用的
    
    
    # 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())
        start = time.time()
        for i in range(300):
            # p = Process(target=work)  # 运行时间:16.447757959365845
            p = Thread(target=work)  # 运行时间:2.0386486053466797
            l.append(p)
            p.start()
        for p in l:
            p.join()
        stop = time.time()
        print("运行时间:%s" % (stop - start))
    
    
    # 计算密集型
    def work1():
        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(6):
            p = Process(target=work1)  # 耗时  运行时间:7.944072008132935
            # p = Thread(target=work1)  # 运行时间:29.27473521232605
            l.append(p)
            p.start()
        for p in l:
            p.join()
        stop = time.time()
        print("运行时间:%s" % (stop - start))
    
    
    死锁和递归锁
    所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的
    一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁
    状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
    递归锁
    我们分析了死锁,那么python里面是如何解决这样的递归锁呢?
    在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
    这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,
    从而使得资源可以被多次require。直到一个线程所有的acquire都被release,
    其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:
    
    
    死锁实例:
    from threading import Thread, Lock
    import time
    
    mutexA = Lock()
    mutexB = Lock()
    
    
    class MyThread(Thread):
        def run(self):
            self.func1()
            self.func2()
    
        def func1(self):
            mutexA.acquire()
            print('33[41m%s 拿到A锁33[0m' % self.name)
    
            mutexB.acquire()
            print('33[42m%s 拿到B锁33[0m' % self.name)
            mutexB.release()
    
            mutexA.release()
    
        def func2(self):
            mutexB.acquire()
            print('33[43m%s 拿到B锁33[0m' % self.name)
            time.sleep(2)
    
            mutexA.acquire()
            print('33[44m%s 拿到A锁33[0m' % self.name)
            mutexA.release()
    
            mutexB.release()
    
    
    if __name__ == '__main__':
        for i in range(5):
            t = MyThread()
            t.start()
    结果:>>>
    Thread-1 拿到A锁
    Thread-1 拿到B锁
    Thread-1 拿到B锁
    Thread-2 拿到A锁
    分析如上代码是如何产生死锁的: 
    启动5个线程,执行run方法,假如thread1首先抢到了A锁,此时thread1没有释放A锁,紧接着执行代码mutexB.acquire(),
    抢到了B锁,在抢B锁时候,没有其他线程与thread1争抢,因为A锁没有释放,其他线程只能等待,然后A锁就执行完func1代码,
    然后继续执行func2代码,与之同时,在func2中,执行代码 mutexB.acquire(),抢到了B锁,然后进入睡眠状态,在thread1执
    行完func1函数,释放AB锁时候,其他剩余的线程也开始抢A锁,执行func1代码,如果thread2抢到了A锁,接下来thread2要抢B锁
    ,ok,在这个时间段,thread1已经执行func2抢到了B锁,然后在sleep(2),持有B锁没有释放,为什么没有释放,因为没有其他的
    线程与之争抢,他只能睡着,然后thread1握着B锁,thread2要抢B锁,ok,这样就形成了死锁
    <<<
    # 递归锁的实例
    from threading import Thread, Lock, RLock
    import time
    
    mutexA = mutexB = RLock()
    
    
    class MyThread(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(0.1)
            mutexA.acquire()
            print('%s 拿到A锁' % self.name)
            mutexA.release()
    
            mutexB.release()
    
    
    if __name__ == '__main__':
        for i in range(5):
            t = MyThread()
            t.start()
    
    <<<Thread-1 拿到A锁
    Thread-1 拿到B锁
    Thread-1 拿到B锁
    Thread-1 拿到A锁
    Thread-2 拿到A锁
    Thread-2 拿到B锁
    Thread-2 拿到B锁
    Thread-2 拿到A锁
    Thread-4 拿到A锁
    Thread-4 拿到B锁
    Thread-4 拿到B锁
    Thread-4 拿到A锁
    Thread-3 拿到A锁
    Thread-3 拿到B锁
    Thread-3 拿到B锁
    Thread-3 拿到A锁
    Thread-5 拿到A锁
    Thread-5 拿到B锁
    Thread-5 拿到B锁
    Thread-5 拿到A锁>>>
    由于锁A,B是同一个递归锁,thread1拿到A,B锁,counter记录了acquire的次数2次,然后在func1执行完毕,就释放递归锁,
    在thread1释放完递归锁,执行完func1代码,接下来会有2种可能,
        1、thread1在次抢到递归锁,执行func2代码 
        2、其他的线程抢到递归锁,去执行func1的任务代码
    信号量,在不同的领域中对应的是不同的知识点

    比如:
    互斥锁:就是抢厕所(这个厕所的一个坑位的)
    信号量就是:公共测试就是多个坑位的厕所
    就是说可以多个人去同时拿到锁,也可以同时释放锁
    # 代码演示
    from threading import Semaphore, Thread
    import time
    import random
    
    SM = Semaphore(6)  # 六个坑位的厕所
    
    
    def task(name):
        SM.acquire()  # 抢锁
        print("%s 占了一个坑位" % name)
        time.sleep(random.randint(1, 5))  # 每个人上厕所的时间在1秒到5秒之间
        SM.release()  # 释放锁
        print()
        
    
    
    for i in range(50):  # 50个人在排队
        t = Thread(target=task, args=(i,))
        t.start()
    
    <<<
    0 占了一个坑位
    1 占了一个坑位
    2 占了一个坑位
    3 占了一个坑位
    4 占了一个坑位
    5 占了一个坑位
    有六个坑位,进去了六个人
    
    6 占了一个坑位
    出来了一个人第七个人进去了
    7 占了一个坑位
    又出来了一个人第八个人进去了
    8 占了一个坑位
    
    
    
    11 占了一个坑位
    9 占了一个坑位
    10 占了一个坑位
    
    
    13 占了一个坑位
    12 占了一个坑位
    
    14 占了一个坑位
    
    15 占了一个坑位
    
    
    17 占了一个坑位
    16 占了一个坑位
    
    18 占了一个坑位
    
    19 占了一个坑位
    
    20 占了一个坑位
    
    21 占了一个坑位
    
    22 占了一个坑位
    
    23 占了一个坑位
    
    24 占了一个坑位
    
    25 占了一个坑位
    
    26 占了一个坑位
    
    27 占了一个坑位
    
    28 占了一个坑位
    
    29 占了一个坑位
    
    30 占了一个坑位
    
    31 占了一个坑位
    
    32 占了一个坑位
    
    33 占了一个坑位
    
    34 占了一个坑位
    
    35 占了一个坑位
    
    36 占了一个坑位
    
    37 占了一个坑位
    
    
    38 占了一个坑位
    39 占了一个坑位
    
    40 占了一个坑位
    
    41 占了一个坑位
    
    42 占了一个坑位
    
    43 占了一个坑位
    
    44 占了一个坑位
    
    45 占了一个坑位
    
    46 占了一个坑位
    
    47 占了一个坑位
    
    48 占了一个坑位
    
    49 占了一个坑位
    

      Event事件:

    同进程的一样,线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,
    这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等
    待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会
    被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置
    为真的Event对象,那么它将忽略这个事件, 继续执行
    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()  # 等灯绿 此时event为False,直到event.set()将其值设置为True,才会继续运行.
        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()
    运行结果:      
    红灯正亮着
    车0正在等绿灯
    车1正在等绿灯
    车2正在等绿灯
    车3正在等绿灯
    车4正在等绿灯
    车5正在等绿灯
    车6正在等绿灯
    车7正在等绿灯
    车8正在等绿灯
    车9正在等绿灯
    车3通行
    车5通行
    车4通行
    车6通行
    车9通行
    车2通行
    车7通行
    车1通行
    车8通行
    车0通行        
            
    进程queue
    同一个进程下的多个线程本来就是数据共享 为什么还要用队列

    因为队列是管道+锁 使用队列你就不需要自己手动操作锁的问题

    因为锁操作的不好极容易产生死锁现象

    PriorityQueue() 优先级数字越小优先级越大
    import queue
    
    q = queue.Queue()
    q.put("你追我呀!")
    print(q.get())
    
    q = queue.LifoQueue()  # 从左
    q.put("你追我呀!")
    q.put("追上就让你")
    q.put("嘿嘿嘿!")
    print(q.get())  # 从左取
    
    q = queue.PriorityQueue()
    q.put((10, "追上就让你"))
    q.put((-100, "你追我"))
    q.put((90, "papapapa!!!"))
    print(q.get())
    print(q.get())
    print(q.get())













  • 相关阅读:
    WP7 操作XML文件
    C#和C/C++指针实现swap交换
    感受
    我学到了什么&nbsp;我思考了什么.
    hdu 2768 Cat vs. Dog (最大独立)
    hdu 1960 Taxi Cab Scheme (最小覆盖)
    hdu 1528 Card Game Cheater (最小覆盖)
    hdu 4160 Dolls (最大独立)
    hdu 2458 Kindergarten (最大独立集)
    hdu 2119 Matrix (最小覆盖)
  • 原文地址:https://www.cnblogs.com/ioipchina/p/11354053.html
Copyright © 2011-2022 走看看