线程锁
from threading import Thread
x = 0
def task():
global x
for i in range(200000):
x = x + 1
if __name__ == '__main__':
t1 = Thread(target=task)
t2 = Thread(target=task)
t1.start()
t2.start()
t1.join()
t2.join()
print(x)
# 337204
当线程t1,刚拿到x,还未进行加值和赋值操作的时候,就被CPU切走了,这时t1的x保存的是刚拿到数据的状态;然后线程t2开始加值操作,当t2被CPU切走时,x已经加了很多次了,但是t1的x还是原来的数据,然后线程t1重新开始加值操作,还是用原先x的数据,造成了数据安全的问题,
所给线程加一把锁,以此保证数据的安全
from threading import Thread,Lock
x = 0
mutex = Lock()
def task:
global x
mutex.acquire()
for i in range(200000):
x = x + 1
mutex.release()
if __name__ == '__main__':
t1 = Thread(target=task)
t2 = Thread(target=task)
t1.start()
t2.start()
t1.join()
t2.join()
print(x)
# 400000
死锁问题
import time
from threading import Thread,Lock
mutex1 = Lock()
mutex2 = Lock()
class MyThread(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 = MyThread()
t.start()
线程1拿到了锁头2,想要往下执行需要锁头1,
线程2拿到了锁头1,想要往下执行需要锁头2,
线程1和线程2互相拿到了彼此想要往下执行的必须条件,但是互相都不放弃手里的锁头。
递归锁
递归锁:在同一个线程内可以被多次acquire
如何释放:内部相当维护了一个计数器,也就是说同一个线程acquire了几次就要release几次
import time
from threading import Thread,RLock
mutex1 = RLock()
mutex2 = mutex1
class MyThread(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 = MyThread()
t.start()
信号量
Semaphore管理一个内置的计数器,每当调用acquire()时内置计数器-1,调用release()时内置计数器+1;当计数器为0时,acquire()会阻塞线程,直到其他线程调用release()
实例:同时只有5个线程可以获得semaphore,即可以限制最大的连接数为5
import time
from threading import Thread,Semaphore,currentThread
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()
信号量和进程池是完全不同的概念,进程池Pool(4),最多产生4个进程,而且只有这四个进程,不会产生其他的进程,信号量是产生一堆的线程/进程。
GIL
GIL锁:全局解释器锁
在CPython解释器中有一把GIL锁,GIL锁本质上是一把互斥锁。
为什么要有GIL锁?
因为CPython自带的垃圾回收机制不是线程完全的,所以要有GIL锁。
影响
这就导致了同一个进程下,同一时间只能运行一个线程,无法利用多核优势。
同一个进程下多个线程只能实现并发不能实现并行。
多进程vs多线程
计算密集型,推荐使用多进程
IO密集型,推荐使用多线程