一 守护线程
from threading import Thread
import time
def foo():
print(123)
time.sleep(1)
print('end123')
def bar():
print(456)
time.sleep(3)
print('end456')
t1 = Thread(target=foo)
t2 = Thread(target=bar)
t1.daemon = True
t1.start()
t2.start()
print('主线程')
# 123
# 456
# 主线程
# end123
# end456
守护线程 等待非守护子线程以及主线程结束之后,结束.
二 互斥锁
from threading import Thread,Lock
x = 100
def task(lock):
lock.acquire()
global x
x -= 1
lock.release()
if __name__ == '__main__':
lock = Lock()
lst = []
for i in range(100):
t = Thread(target=task,args=(lock,))
lst.append(t)
t.start()
for i in lst:
i.join()
print(f'主线程{x}')
三 死锁现象与递归锁
1.死锁:
两个或两个以上的进程或线程在执行过程中,因为争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
2.递归锁:
解决死锁的方法
from threading import Thread,RLock
lock_A = lock_B = RLock()
class MyThread(Thread):
def run(self):
self.f1()
self.f2()
def f1(self):
lock_A.acquire()
print(f'{self.name}拿到了A锁')
lock_B.acquire()
print(f'{self.name}拿到了B锁')
lock_B.release()
lock_A.release()
def f2(self):
lock_B.acquire()
print(f'{self.name}拿到了B锁')
lock_A.acquire()
print(f'{self.name}拿到了A锁')
lock_A.release()
lock_B.release()
if __name__ == '__main__':
for i in range(3):
t = MyThread()
t.start()
四 信号量 (Semaphore)
信号量也是一种锁,控制并发数量
from threading import Thread,Semaphore,current_thread
import time,random
sem = Semaphore(5)
def task():
sem.acquire()
print(f'{current_thread().name}厕所ing')
time.sleep(random.randint(1,3))
sem.release()
if __name__ == '__main__':
for i in range(20):
t = Thread(target=task,)
t.start()
五 GIL全局解释器锁
理论上来说: 单个进程的多线程可以利用多核
但是,开发开发CPython解释器的程序员给进入解释器的线程加了锁.同一时刻只能允许一个线程进入解释器
为什么加锁?
-
当时都是单核时代,而且CPU价格非常贵
-
如果不加全局解释器锁,开发CPython解释器的程序员就会在源码内部各种主动加锁,非常麻烦,各种死锁现象等等.为了省事,进入解释器时直接给线程加一个锁
优点: 保证了CPython解释器的数据资源的安全.
缺点: 单个进程的多线程不能利用多核
Jython和pypy没有GIL锁
单个进程的多线程可以并发,但不能利用多核,不能并行
遇到IO阻塞,CPU就会被操作系统切走,GIL锁被释放,线程挂起,另一个线程进入,可以实现并发
多个进程可以并发,并行
IO密集型: 单个进程的多线程适合并发执行.
计算密集型: 多进程的并行
六 GIL与lock锁的区别
相同点: 都是同种锁,互斥锁
不同点:
GIL锁保护解释器内部的资源数据的安全. GIL锁上锁,释放无需手动操作.
自定义的互斥锁保护进程中的资源数据的安全, 必须自己手动上锁,释放锁