线程相关锁

mac2022-06-30  85

目录

线程相关锁 一、线程同步锁(互斥锁) 多线程抢占资源的数据安全问题使用线程同步锁解决数据安全问题二、死锁三、递归锁四、全局解释器锁GIL

线程相关锁

一、线程同步锁(互斥锁)

多线程抢占资源的数据安全问题

# 案例一 from threading import Thread import time n = 10 def func(): global n temp = n time.sleep(0.2) n = temp - 1 if __name__ == '__main__': lis = [] for i in range(10): t = Thread(target=func) t.start() lis.append(t) for i in lis: i.join() print(n) # 9 ''' 因为将n的递减操作拆分为两个阶段,一个赋值,一个减值,中间休眠0.2s 此时,当10个线程去修改n的时候,就会出现在0.2s之前10个线程拿到的n都是10 最后每个线程返回的修改值都是9,这样就产生了数据不同步的安全问题 所以,当延迟时间过长或者数据量过大时,使用多线程同时访问同一个数据时,不安全 ''' # 案例二 from threading import Thread n = 0 def task(): global n for i in range(100000): n += 1 if __name__ == '__main__': lis = [] for i in range(3): t = Thread(target=task) t.start() lis.append(t) for i in lis: i.join() print(n) # 274155 ''' 当启动三个线程来执行task时 发现结果永远小于300000 这也验证了多个线程修改同一个数据时会引发数据安全问题 **********所以,有了进程锁的概念*********** '''

使用线程同步锁解决数据安全问题

# 案例一解决方法 from threading import Thread,Lock import time n = 10 lock = Lock() def func(): global n lock.acquire() temp = n time.sleep(0.2) n = temp - 1 lock.release() if __name__ == '__main__': lis = [] for i in range(10): t = Thread(target=func) t.start() lis.append(t) for i in lis: i.join() print(n) # 0 # 案例二解决方 from threading import Thread,Lock n = 0 lock = Lock() def task(): global n lock.acquire() for i in range(100000): n += 1 lock.release() if __name__ == '__main__': lis = [] for i in range(3): t = Thread(target=task) t.start() lis.append(t) for i in lis: i.join() print(n) # 300000 ''' 给要处理的数据加一把锁,限定同一时间只能有一个线程来访问这段代码 同样,在保证了数据安全的同时,牺牲了效率 '''

二、死锁

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

# 著名的科学家吃面问题 from threading import Thread,Lock import time noodle_lock = Lock() fork_lock = Lock() def eat1(name): noodle_lock.acquire() print(f'{name}拿到面条了') fork_lock.acquire() time.sleep(1) print(f'{name}拿到叉子了') print(f'{name}吃面') fork_lock.release() noodle_lock.release() def eat2(name): fork_lock.acquire() print(f'{name}拿到叉子了') time.sleep(1) noodle_lock.acquire() print(f'{name}拿到面条了') print(f'{name}吃面') noodle_lock.release() fork_lock.release() Thread(target=eat1,args=('alex',)).start() Thread(target=eat2,args=('nick',)).start() Thread(target=eat1,args=('tank',)).start() Thread(target=eat2,args=('egon',)).start() ''' alex拿到面条了 alex拿到叉子了 alex吃面 nick拿到叉子了 tank拿到面条了 '''

三、递归锁

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

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

from threading import Thread,RLock import time fork_lock = noodle_lock = RLock() def eat1(name): noodle_lock.acquire() print(f'{name}拿到面条了') fork_lock.acquire() print(f'{name}拿到叉子了') print(f'{name}吃面') fork_lock.release() noodle_lock.release() def eat2(name): fork_lock.acquire() print(f'{name}拿到叉子了') time.sleep(1) noodle_lock.acquire() print(f'{name}拿到面条了') print(f'{name}吃面') noodle_lock.release() fork_lock.release() Thread(target=eat1,args=('alex',)).start() Thread(target=eat2,args=('nick',)).start() Thread(target=eat1,args=('tank',)).start() Thread(target=eat2,args=('egon',)).start() ''' alex拿到面条了 alex拿到叉子了 alex吃面 nick拿到叉子了 nick拿到面条了 nick吃面 tank拿到面条了 tank拿到叉子了 tank吃面 egon拿到叉子了 egon拿到面条了 egon吃面 '''

四、全局解释器锁GIL

​ Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。   对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。

  在多线程环境中,Python 虚拟机按以下方式执行:

  a、设置 GIL;

  b、切换到一个线程去运行;

  c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));

  d、把线程设置为睡眠状态;

  e、解锁 GIL;

  d、再次重复以上所有步骤。   在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。

GIL 就是当线程访问cpu对数据进行修改时介于两者之间的一把锁,其实就是Cpython的解释器起作用。 他控制了同一时刻只能有一个线程来访问数据,将此线程设置为睡眠状态 当线程对数据修改完毕了,GIL就会释放该线程 之后才允许下一个线程来访问数据 GIL锁的是线程 GIL导致的不能并发问题是Cpython的特性,目前还不能得以解决!

转载于:https://www.cnblogs.com/dadazunzhe/p/11545522.html

最新回复(0)