-
-
线程是执行单位,但是不能直接运行,需要先拿到python解释器解释之后才能被cpu执行
-
同一时刻同一个进程内多个线程无法实现并行,但是可以实现并发
-
为什么要有GIL是因为它内部的垃圾回收机制不是线程安全的
-
垃圾回收机制也是一个任务,跟你的代码不是串行运行,如果是串行会明显有卡顿
-
这个垃圾回收到底是开进程还是开线程?肯定是线程,线程肯定也是一段代码,所以想运行也必须要拿到python解释器
-
假设能够并行,会出现什么情况?一个线程刚好要造一个a=1的绑定关系之前,这个垃圾线程来扫描,矛盾点就来了,谁成功都不对!
-
也就意味着在Cpython解释器上有一把GIL全局解释器锁
-
同一个进程下的多个线程不能实现并行但是能够实现并发,多个进程下的线程能够实现并行
1.python中的多线程到底有没有用?
单核情况下:四个任务
多核情况下:四个任务
计算密集型:一个任务算十秒,四个进程和四个线程,肯定是进程快
IO密集型:任务都是纯io情况下,线程开销比进程小,肯定是线程好
```python # 计算密集型 from multiprocessing import Process from threading import Thread import os,time def work(): res=0 for i in range(100000000): res*=i if __name__ == '__main__': l=[] print(os.cpu_count()) # 本机为12核 start=time.time() for i in range(12): # p=Process(target=work) #耗时8s多 p=Thread(target=work) #耗时44s多 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 threading import os,time def work(): time.sleep(2) if __name__ == '__main__': l=[] print(os.cpu_count()) #本机为12核 start=time.time() for i in range(400): p=Process(target=work) #耗时12s多,大部分时间耗费在创建进程上 # p=Thread(target=work) #耗时2s多 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
2.GIL与自定义互斥锁
不同的数据需要加不同的锁才能保证数据的安全,GIL锁只是对线程加锁,对数据并没有加锁的效果 ```python from threading import Thread,Lock import time mutex=Lock() n=100 def task(): global n with mutex: 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) # 对于修改不同的数据,需要加不同的锁进行处理
3.死锁与递归锁(了解)
自定义锁一次acquire必须对应一次release,不能连续acquire
递归锁可以连续的acquire,每acquire一次计数加一
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() 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(2) mutexA.acquire() print('%s 抢到了A锁' %self.name) mutexA.release() mutexB.release() if __name__ == '__main__': for i in range(100): t=Mythead() t.start()
4.信号量(了解)
自定义的互斥锁如果是一个厕所,那么信号量就相当于公共厕所,门口挂着多个厕所的钥匙。抢和释放跟互斥锁一致
from threading import Thread,Semaphore import time import random 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()
5.Event事件
一些线程需要等待另外一些线程运行完毕才能运行,类似于发射信号一样
from threading import Thread,Event import time event = Event() # 造了一个红绿灯 def light(): print('红灯亮着的') time.sleep(3) print('绿灯亮了') event.set() def car(name): print('%s 车正在等红灯'%name) event.wait() print('%s 车加油门飙车走了'%name) if __name__ == '__main__': t = Thread(target=light) t.start() for i in range(10): t = Thread(target=car,args=('%s'%i,)) t.start()
6.线程queue
同一个进程下的线程数据都是共享的为什么还要用queue?queue本身自带锁的功能,能够保证数据的安全
# 我们现在的q只能在本地使用,后面我们会学基于网络的q import queue queue.Queue() #先进先出 q=queue.Queue(3) q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get()) print(q.get()) queue.LifoQueue() #后进先出->堆栈 q=queue.LifoQueue(3) q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get()) print(q.get()) queue.PriorityQueue() #优先级 q=queue.PriorityQueue(3) #优先级,优先级用数字表示,数字越小优先级越高 q.put((10,'a')) q.put((-1,'b')) q.put((100,'c')) print(q.get()) print(q.get()) print(q.get())