今日内容
1、全局解释器锁GIl
2、GIL与自定义互斥锁
3、死锁与递归锁(了解)
4、信号量(了解)
5、Event事件
6、线程Queue
一、全局解释器锁GIL
'''
定义:
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple
native threads from executing Python bytecodes at once. This lock is necessary mainly
because CPython’s memory management is not thread-safe. (However, since the GIL
exists, other features have grown to depend on the guarantees that it enforces.)
'''
结论:在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势
关于垃圾回收机制,在python解释器中,垃圾回收机制与其他线程不是串行的,但也不是并行的。
如果是并行的话,就会出现错误回收数据的情况:
x=10的操作在执行到给10开空间的时候,还没引用x,垃圾回收机制就会把10给回收掉。
为了解决这一问题,就给CPython解释器上了一个互斥锁
# 原理是:
1、首先线程想运行就要调用CPython解释器
2、为了不让垃圾回收机制与其他线程并行,就给CPython上了一个互斥锁,使得同一时间只有一个线程能使用CPython解释器
3、抢到GIL全局互斥锁的线程可以运行,一旦遇到IO操作便会返回GIL锁外层重新解释器执行代码
结论:GIl就是一把互斥锁,CPython解释器的互斥锁(牺牲了效率,保证了数据的安全)
# python中多线程到底有没有用?
单核情况下:四个任务
多核情况下:四个任务
计算密集型: 一个任务算十秒,四个进程和四个线程,肯定是进程快
IO密集型: 任务都是纯IO情况下,线程开销比进程小,肯定是线程好
测试代码:
# 计算密集型
from multiprocessing import Process
from threading import Thread
import os,time
def work():
res=0
for i in range(10000000):
res*=i
if __name__ == '__main__':
l=[]
print(os.cpu_count()) # 本机为4核
start=time.time()
for i in range(12):
# p=Process(target=work) #耗时7.3804872035980225
p=Thread(target=work) #耗时10.810997724533081
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))
# IO密集型
from multiprocessing import Process
from threading import Thread
import os,time
def work():
time.sleep(2)
if __name__ == '__main__':
l=[]
print(os.cpu_count()) #本机为4核
start=time.time()
for i in range(400):
# p=Process(target=work) #耗时4.527513027191162,大部分时间耗费在创建进程上
p=Thread(target=work) #耗时2.0667171478271484多
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))
二、GIL与自定义互斥锁
不同的数据需要加不同的锁才能实现数据安全,GIL只是对线程加锁,对数据没有加锁效果
代码示例:
from threading import Thread,Lock
import time
mutex=Lock() # 在要修改的数据上方创建锁
n=100
def task():
global n
with mutex: # 在获得数据之前抢锁 with mutex的代码块结束后自动释放锁,同with open
temp=n
time.sleep(0.1)
n=temp-1
if __name__ == '__main__':
l=[]
for i in range(100):
t=Thread(target=task)
l.append(t)
t.start()
for t in l:
t.join()
print(n)
# 对于修改不同的数据,需要加不同的锁进行处理
三、死锁与递归锁(了解)
自定义锁一次acquire必须对应一次release,不能连续acquire
递归锁可以连续的acquire,每acquire一次计数加一
递归锁:Rlock
递归锁解决死锁 代码示例:
from threading import Thread,Lock,RLock
import time
# mutexA=Lock()
# mutexB=Lock()
mutexB=mutexA=RLock()
class Mythead(Thread):
def run(self):
self.f1()
self.f2()
def f1(self):
mutexA.acquire() # acquire的计数+1 一旦acquire的计数不为0,其他进程就抢不到锁,就堵塞
print('%s 抢到A锁' %self.name)
mutexB.acquire() # acquire的计数+1
print('%s 抢到B锁' %self.name)
mutexB.release() # acquire的计数-1
mutexA.release() # acquire的计数-1
def f2(self):
mutexB.acquire()
print('%s 抢到了B锁' %self.name)
time.sleep(2)
mutexA.acquire()
print('%s 抢到了A锁' %self.name)
mutexA.release()
mutexB.release()
if __name__ == '__main__':
for i in range(100):
t=Mythead()
t.start()
四、信号量(了解)
自定义的互斥锁如果是一个厕所,那么信号量就相当于公共厕所,门口挂着多个厕所的钥匙。抢和释放跟互斥锁一致
代码示例:
from threading import Thread, Semaphore
import time
import random
# sm = Lock() 换下面一行的创建锁方法
sm = Semaphore(5) # 公共厕所里面有五个坑位,在厕所外面放了五把钥匙
# 可以理解为一个放数据的容器,取出数据用完之后放回容器中,没有数据了就堵塞
def task(name):
sm.acquire()
print('%s正在蹲坑' % name)
# 模拟蹲坑耗时
time.sleep(random.randint(1, 5))
sm.release()
if __name__ == '__main__':
for i in range(20):
t = Thread(target=task, args=('伞兵%s号' % i,))
t.start()
五、Event事件
一些线程需要等待另外一些线程运行完毕才能运行,类似于发射信号一样
红路灯 代码示例:
from threading import Thread, Event
import time
event = Event() # 创建一个红绿灯
def light():
print('is red light...')
time.sleep(3)
event.set() # 发出信号
print('is green light...')
def car(name):
print('car%s is wait red light...' % name)
event.wait() # 接收到信号才会通行,否则堵塞
print('car%s is running...' % name)
if __name__ == '__main__':
l = Thread(target=light)
l.start()
for i in range(10):
c = Thread(target=car, args=('车辆%s' % i, ))
c.start()
六、线程Queue
# 我们现在的q只能在本地使用,后面我们会学基于网络的q
import queue
queue.Queue() #先进先出
q=queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get()) # 1
print(q.get()) # 2
print(q.get()) # 3
queue.LifoQueue() #后进先出->堆栈
q=queue.LifoQueue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get()) # 3
print(q.get()) # 2
print(q.get()) # 1
queue.PriorityQueue() #优先级
q=queue.PriorityQueue(3) #优先级,优先级用数字表示,数字越小优先级越高
q.put((10,'a'))
q.put((-1,'b'))
q.put((100,'c'))
print(q.get()) # (-1, 'b')
print(q.get()) # (10, 'a')
print(q.get()) # (100, 'c')