zoukankan      html  css  js  c++  java
  • 线程锁,死锁,GIL锁等等

    线程锁

    from threading import Thread,Lock
    
    x = 0
    mutex = Lock()
    def task():
        global x
        # mutex.acquire()
        for i in range(200000):
            x = x+1
            # t1 的 x刚拿到0 保存状态 就被切了
            # t2 的 x拿到0 进行+1       1
            # t1 又获得运行了  x = 0  +1  1
            # 思考:一共加了几次1? 加了两次1 真实运算出来的数字本来应该+2 实际只+1
            # 这就产生了数据安全问题.
        # mutex.release()
    
    if __name__ == '__main__':
        t1 = Thread(target=task)
        t2 = Thread(target=task)
        t3 = Thread(target=task)
        t1.start()
        t2.start()
        t3.start()
    
        t1.join()
        t2.join()
        t3.join()
        print(x)
    
    

    531388

    我们讲过的进程有进程锁,那么线程也有线程锁,先看上面代码,我们知道线程共享一个进程的空间,所以他们都对x进行增加操作,按道理来说,最后打印的结果应该是600000,而这里却远远不到,这是因为线程在进行长时间的操作时,cup切到下一个线程了,而这个时候可能x=1000+1,只执行到1000+1,所以就保存了这个状态,不管后面的线程对x做了多少次加一,最后回到了这个线程都会被重新赋值为1001。

    所以要加上一个线程锁,把这个加的过程变成串行,就是注释的部分。当他运行太久了,切到了别的线程,别的线程也进不去,因为锁在第一个线程手上。因此,这一小段代码就是串行的。

    死锁

    什么是死锁:

    ​ 互相都有对方继续执行下面代码的必须条件,且必须要拿到对方手里的必须条件后才能给对方他的必要条件。因此导致互相都无法继续执行。

    概念是我自己总结的,看代码会比较明朗:

    from threading import Thread,Lock
    import time
    lock1=Lock()
    lock2=Lock()
    class Mythread1(Thread):
        def run(self):
            self.task1()
        def task1(self):
            lock1.acquire()
            print('a获得锁1')
            time.sleep(3)
            lock2.acquire()
            print('a获得锁2')
            lock1.release()
            print('a释放锁1')
            lock2.release()
            print('a释放锁2')
    class Mythread2(Thread):
        def run(self):
            self.task2()
        def task2(self):
            lock2.acquire()
            print('b获得锁2')
            time.sleep(3)
            lock1.acquire()
            print('b获得锁1')
            lock2.release()
            print('b释放锁2')
            lock1.release()
            print('b释放锁1')
    
    
    t1 =Mythread1()
    t1.start()
    t2=Mythread2()
    t2.start()
    

    a获得锁1
    b获得锁2

    因为ti拿到了lock1,先要拿lock2,但是lock2这时候在t2手里,那么就要等t2释放lock2,而t2释放lock2却必须要先拿到lock1,而此时lock1在t1手里,这就造成了死锁。

    接下来看一下多线程的时候,死锁的样子。

    from threading import Thread,Lock
    mutex1 = Lock()
    mutex2 = Lock()
    import time
    class MyThreada(Thread):
        def run(self):
            self.task1()
            self.task2()
        def task1(self):
            mutex1.acquire()
            print(f'{self.name} 抢到了 锁1 ')
            mutex2.acquire()
            print(f'{self.name} 抢到了 锁2 ')
            mutex2.release()
            print(f'{self.name} 释放了 锁2 ')
            mutex1.release()
            print(f'{self.name} 释放了 锁1 ')
    
        def task2(self):
            mutex2.acquire()
            print(f'{self.name} 抢到了 锁2 ')
            time.sleep(1)
            mutex1.acquire()
            print(f'{self.name} 抢到了 锁1 ')
            mutex1.release()
            print(f'{self.name} 释放了 锁1 ')
            mutex2.release()
            print(f'{self.name} 释放了 锁2 ')
    
    
    for i in range(3):
        t = MyThreada()
        t.start()
    

    Thread-1 抢到了 锁1
    Thread-1 抢到了 锁2
    Thread-1 释放了 锁2
    Thread-1 释放了 锁1
    Thread-1 抢到了 锁2
    Thread-2 抢到了 锁1

    那么怎么解决死锁问题呢,用递归锁。

    递归锁

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

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

    mutex1 = RLock()
    mutex2 = mutex1
    
    import time
    class MyThreada(Thread):
        def run(self):
            self.task1()
            self.task2()
        def task1(self):
            mutex1.acquire()
            print(f'{self.name} 抢到了 锁1 ')
            mutex2.acquire()
            print(f'{self.name} 抢到了 锁2 ')
            mutex2.release()
            print(f'{self.name} 释放了 锁2 ')
            mutex1.release()
            print(f'{self.name} 释放了 锁1 ')
    
        def task2(self):
            mutex2.acquire()
            print(f'{self.name} 抢到了 锁2 ')
            time.sleep(1)
            mutex1.acquire()
            print(f'{self.name} 抢到了 锁1 ')
            mutex1.release()
            print(f'{self.name} 释放了 锁1 ')
            mutex2.release()
            print(f'{self.name} 释放了 锁2 ')
    
    
    for i in range(3):
        t = MyThreada()
        t.start()
    
    

    Thread-1 抢到了 锁1
    Thread-1 抢到了 锁2
    Thread-1 释放了 锁2
    Thread-1 释放了 锁1
    Thread-1 抢到了 锁2
    Thread-1 抢到了 锁1
    Thread-1 释放了 锁1
    Thread-1 释放了 锁2
    Thread-2 抢到了 锁1
    Thread-2 抢到了 锁2
    Thread-2 释放了 锁2
    Thread-2 释放了 锁1
    Thread-2 抢到了 锁2
    Thread-2 抢到了 锁1
    Thread-2 释放了 锁1
    Thread-2 释放了 锁2
    Thread-3 抢到了 锁1
    Thread-3 抢到了 锁2
    Thread-3 释放了 锁2
    Thread-3 释放了 锁1
    Thread-3 抢到了 锁2
    Thread-3 抢到了 锁1
    Thread-3 释放了 锁1
    Thread-3 释放了 锁2

    信号量

    from threading import Thread,currentThread,Semaphore
    import time
    
    def task():
        sm.acquire()
        print(f'{currentThread().name} 在执行')
        time.sleep(3)
        sm.release()
    
    sm = Semaphore(5)
    for i in range(15):
        t = Thread(target=task)
        t.start()
    

    他允许的就是同时有多少线程可以拿到这个锁。

    GIL锁

    在Cpython解释器中有一把GIL锁(全局解释器锁),GIl锁本质是一把互斥锁。
    导致了同一个进程下,同一时间只能运行一个线程,无法利用多核优势.
    同一个进程下多个线程只能实现并发不能实现并行.

    为什么要有GIL?
    因为cpython自带的垃圾回收机制不是线程安全的,所以要有GIL锁.

    导致了同一个进程下,同一时间只能运行一个线程,无法利用多核优势.
    分析:
    我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:
    方案一:开启四个进程
    方案二:一个进程下,开启四个线程

    计算密集型 推荐使用多进程
    每个都要计算10s
    多线程
    在同一时刻只有一个线程会被执行,也就意味着每个10s都不能省,分开每个都要计算10s,共40.ns
    多进程
    可以并行的执行多个线程,10s+开启进程的时间

    io密集型 推荐多线程
    4个任务每个任务90%大部分时间都在io.
    每个任务io10s 0.5s
    多线程
    可以实现并发,每个线程io的时间不咋占用cpu, 10s + 4个任务的计算时间
    多进程
    可以实现并行,10s+1个任务执行的时间+开进程的时间

  • 相关阅读:
    CAP.dll of dotnetcore
    GRPC 高性能 RPC 框架, 服务间通讯
    REST Client
    ERP Export
    ERP Update DataSource
    knockout js
    面试题目总结
    JavaScript Objiects and Prototypes
    PLS-00172: string literal too long
    字符串连接性能
  • 原文地址:https://www.cnblogs.com/chanyuli/p/11545233.html
Copyright © 2011-2022 走看看