python 之 并发编程(守护线程与守护进程的区别、线程互斥锁、死锁现象与递归锁、信号量、GIL全局解释器锁)

时间:2023-12-05 23:32:08

9.94 守护线程与守护进程的区别

1.对主进程来说,运行完毕指的是主进程代码运行完毕
2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕

详细解释:
1.主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
2.主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。

9.95 线程互斥锁

from threading import Thread,Lock
import time
mutex=Lock()
x=100
def task():
global x
mutex.acquire()
temp=x
time.sleep(0.1)
x=temp-1
mutex.release()

if __name__ == '__main__':
start=time.time()
t_l=[]
for i in range(100):
t=Thread(target=task)
t_l.append(t)
t.start()
for t in t_l:
t.join()

print('主',x) #主 0
print(time.time()-start) #10.1311

9.96 死锁现象与递归锁

mutexA=mutexB=RLock() 一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止

from threading import Thread,Lock,RLock
import time
# mutexA=Lock()
# mutexB=Lock() #产生死锁
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(10):
t=MyThread()
t.start()

print('主')

9.97 信号量

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

# from multiprocessing import Semaphore
from threading import Thread,Semaphore,current_thread
import time,random

sm=Semaphore(5)
def go_wc():
sm.acquire()
print('%s 上厕所ing' %current_thread().getName())
time.sleep(random.randint(1,3))
sm.release()

if __name__ == '__main__':
for i in range(23):
t=Thread(target=go_wc)
t.start()

与进程池是完全不同的概念,进程池Pool(4),最大只能产生4个进程,而且从头到尾都只是这四个进程,不会产生新的,而信号量是产生一堆线程/进程

9.10 GIL解释器锁

GIL:全局解释器锁 GIL本质就是一把互斥锁,是夹在解释器身上的,每一个python进程内都有这么一把锁,同一个进程内的所有线程都需要先抢到GIL锁,才能执行解释器代码

GIL会对单进程下的多个线程造成什么样的影响: 多线程要想执行,首先需要争抢GIL,对所有待执行的线程来说,GIL就相当于执行权限,同一时刻只有一个线程争抢成功,即单进程下的多个线程同一时刻只有一个在运行,意味着单进程下的多线程没有并行的效果,但是有并发的效果

ps:分散于不同进程内的线程不会去争抢同一把GIL,只有同一个进程的多个线程才争抢同一把GIL

为什么要有GIL: Cpython解释器的内存管理机制不是线程安全的

GIL的优缺点: 优点: 保证Cpython解释器内存管理的线程安全

缺点: ​ 同一进程内所有的线程同一时刻只能有一个执行,也就说Cpython解释器的多线程无法实现并行

GIL与自定义互斥锁的异同,多个线程争抢GIL与自定义互斥锁的过程分析: 相同:都是互斥锁 不同点:GIL是加到解释器上的,作用于全局,自定义互斥锁作用于局部;单进程内的所有线程都会去抢GIL,单进程内的只有一部分线程会去抢自定义的互斥锁

9.101 Cpython解释器并发效率验证

单进程下的多个线程是无法并行,无法并行意味着不能利用多核优势

计算密集型应该使用多进程,如金融分析;IO密集型应该使用多线程,多核对性能的提升微不足道,如socket,爬虫,web

9.102 线程互斥锁与GIL对比

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

from threading import Thread,Lock
import time
mutex=Lock()
count=0
def task():
global count
mutex.acquire() #GIL只能让线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代
temp=count #码去执行,但线程拿不到mutex,同样要等待
time.sleep(0.1)
count=temp+1
mutex.release()

if __name__ == '__main__':
t_l=[]
for i in range(2):
t=Thread(target=task)
t_l.append(t)
t.start()
for t in t_l:
t.join()

print('主',count) #主 2