''' 定义: 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解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势
前言:
- GIL其实就是一把互斥锁(牺牲了效率但是保证了数据的安全)。 - 线程是执行单位,但是不能直接运行,需要先拿到python解释器解释之后才能被cpu执行 - 同一时刻同一个进程内多个线程无法实现并行,但是可以实现并发
一.GIL全局解释器 垃圾回收机制:
- 垃圾回收机制也是一个任务,跟你的代码不是串行运行,如果是串行会明显有卡顿 - 这个垃圾回收到底是开进程还是开线程?肯定是线程,线程肯定也是一段代码,所以想运行也必须要拿到python解释器 没有GIL全局解释器锁 他只是对线程加锁 不是对数据 运行垃圾回收机制:引用计数 1,必须先拿到python 解释器---> 2.python 进程下的多个线程是并发。若此时你想创建一个 a = 1 cpu运行速度是非常快的
那么就会引发 其他线程垃圾回收机制扫描把我刚创建的内存清理掉 所以必须设置GIL全局解释器锁
也就意味着在Cpython解释器上有一把GIL全局解释器锁
二.
1.python中的多线程到底有没有用?
一、数据密集型
二、IO密集型
#### 1.python中的多线程到底有没有用? 单核情况下:四个任务 多核情况下:四个任务 计算密集型:一个任务算十秒,四个进程和四个线程,肯定是进程快 IO密集型:任务都是纯io情况下,线程开销比进程小,肯定是线程好
一、数据密集型
def task(): res = 0 for i in range(100000000): res = res*i if __name__ == '__main__': print(os.cpu_count()) #本机内核 p_list=[] start_time= time.time() for i in range(4): p = Process(target=task) # 进程运行时间为10.636553287506104 # p = Thread(target= task) # 线程运行时间为19.97660756111145 p.start() p_list.append(p) for p in p_list: p.join() end_time = time.time() print('运行时间为%s'% (end_time-start_time))
"" 二、IO密集型 def work(): time.sleep(3) if __name__ == '__main__': print(os.cpu_count()) start_time =time.time() p_list=[] for i in range(4): # p = Process(target= work) # run is total_time7.271259546279907 p = Thread(target= work) # run is total_time3.002392053604126 p.start() p_list.append(p) for p in p_list: p.join() end_time =time.time() print('run is total_time%s'%(end_time-start_time))
三、全局锁与普通锁
对于不同的数据,要想保证安全,需要加不同的锁处理 GIL并不能保证数据的安全,它是对Cpython解释器加锁,针对的是线程 保证的是同一个进程下多个线程之间的安全
""" from threading import Thread import os import time from threading import Lock mutex = Lock() num = 100 def task(): global num mutex.acquire() #抢锁 temp = num time.sleep(0.1) num = temp-1 mutex.release() # 释放锁 开始一个 if __name__ == '__main__': p_lsit=[] for i in range(10): p = Thread(target=task) p.start() p_lsit.append(p) for p in p_lsit: p.join() print(num) # 90 相当于10个线程同时去抢100票 必须要确保一个数据同时被10个进程同时抢 锁是起到保护作用 取完一个减一个
四、.死锁与递归锁(了解)
自定义锁一次acquire必须对应一次release,不能连续acquire
递归锁可以连续的acquire,每acquire一次计数加一
import time from threading import Thread,RLock mutexA = mutexB= RLock() # 递归锁RLock class Mythread(Thread): def run(self): self.fn1() self.fn2() def fn1(self): #设置锁 mutexA.acquire() print('%s 抢到A锁了'%self.name) mutexB.acquire() print('%s 抢到B锁了'%self.name) mutexB.release() print('%s释放了B锁'%self.name) mutexA.release() print('%s释放了A锁'%self.name) def fn2(self): mutexB.acquire() print('%s 抢到A锁了' % self.name) time.sleep(1) mutexA.acquire() print('%s 抢到B锁了' % self.name) mutexA.release() print('%s释放了B锁' % self.name) mutexB.release() print('%s释放了A锁' % self.name) if __name__ == '__main__': for i in range(100): t = Mythread() t.start()
五.Event事件
一些线程需要等待另外一些线程运行完毕才能运行,类似于发射信号一样
from threading import Thread from threading import Event import time event = Event() #造了一个绿灯 def light(): print('等红灯') time.sleep(3) event.set() # 解除阻塞并且给 event发了一个信号 print('绿灯亮了') def car(i): print('%s灯红灯'%i) event.wait() print('%s绿灯了,加油门'%i) if __name__ == '__main__': t = Thread(target=light) t.start() p_list=[] for i in range(5): p = Thread(target=car,args=(i,)) p.start() # 等红灯 # 0灯红灯 # 1灯红灯 # 2灯红灯 # 3灯红灯 # 4灯红灯 # 绿灯亮了 # 0绿灯了,加油门 # 1绿灯了,加油门 # 3绿灯了,加油门 # 4绿灯了,加油门 # 2绿灯了,加油门#
六.信号量(了解)
自定义的互斥锁如果是一个厕所,那么信号量就相当于公共厕所,门口挂着多个厕所的钥匙。抢和释放跟互斥锁一致
普通互斥锁, 独立卫生间 所有人只有一把锁 信号量 ,公共卫生间 有多少个坑 就有多少把锁 """ from threading import Thread from threading import Semaphore #信号量 import time import random sm = Semaphore(5) #一个公共厕所造了5个坑 在厕所外放了5把锁 def task(name): sm.acquire() # 释放信号锁 print('%s正在蹲坑'%name) time.sleep(random.randint(1, 3)) sm.release() for i in range(20): t = Thread(target= task,args=('%s伞兵'%i,)) t.start()
0伞兵正在蹲坑 1伞兵正在蹲坑 2伞兵正在蹲坑 3伞兵正在蹲坑 4伞兵正在蹲坑 此时5个人中 有一个人好了 同时释放了一把锁 5伞兵正在蹲坑 前面5个好了两个释放给 6,7 6伞兵正在蹲坑 7伞兵正在蹲坑 8伞兵正在蹲坑 9伞兵正在蹲坑 10伞兵正在蹲坑 11伞兵正在蹲坑 12伞兵正在蹲坑 13伞兵正在蹲坑 14伞兵正在蹲坑 15伞兵正在蹲坑 16伞兵正在蹲坑 17伞兵正在蹲坑 18伞兵正在蹲坑 19伞兵正在蹲坑
七.线程queue
同一个进程下的线程数据都是共享的为什么还要用queue?queue本身自带锁的功能,能够保证数据的安全
import queue """ 1.普通q 2.堆栈。先进后出q 3.优先级q """ q = queue.Queue(3) q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get()) print(q.get()) # 取值
——————》》》
1
2
3 q = queue.LifoQueue(5) q.put(1) q.put(2) q.put(3) q.put(4) q.put(5) print(q.get()) print(q.get()) print(q.get()) print(q.get()) print(q.get()) # 先进后出 和堆栈一样
——————》》》
5
4
3
2
1
q = queue.PriorityQueue(3) q.put(100,"q") q.put(20,"t") q.put(-1,'s') print(q.get()) print(q.get()) print(q.get()) # 优先级是按照数字从小到大排列的
————————》》》
-1
20
100