GIL全局解释器锁/Event事件/信号量/线程Queue
GLL全局解释器锁
背景:python解释器有很多种,最常见的就是Cpython解释器
定义:GIL本质也是一把互斥锁:将并发变成串行,牺牲效率保证数据的安全;用来阻止同一个进程下的多个线程同时执行(同一个进程内多个线程无法实现并行,但是可以实现并发)
注意:python的多线程没法利用多核优势,并不是没用了
GIL的作用与意义:
- GIL的存在是因为Cpython解释器的内存管理是不安全的
- GIL是Cpython解释器的特点,且单进程下多个线程无法利用多核优势是所有解释器语言的通病
- 针对不同数据应该加不同的锁进行处理
- GIL用来保证线程安全
研究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()) # 本机为6核 start=time.time() for i in range(6): p=Process(target=work) # 耗时 4.732933044433594 p=Thread(target=work) # 耗时 22.83087730407715 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
若有四个任务,且是IO密集型的
- 单核情况下
- 开线程更节省资源
- 多核情况下
- 开线程更节省资源
# 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()) #本机为6核 start=time.time() for i in range(4000): p=Process(target=work) # 耗时9.001083612442017s多,大部分时间耗费在创建进程上 # p=Thread(target=work) # 耗时2.051966667175293s多 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
总结:
- python的多线程到底有没有用,需要看情况而定,首先可以肯定是有用的
- 多进程+多线程配合使用
GIL与普通的互斥锁
from threading import Thread import time n = 100 def task(): global n tmp = n time.sleep(1) n = tmp -1 t_list = [] for i in range(100): t = Thread(target=task) t.start() t_list.append(t) for t in t_list: t.join() print(n) # 99
死锁
PS:只要类加括号实例化对象,无论传入的参数是否一样,生成的对象肯定不一样,单例模式除外
注意:自己千万不要轻易的处理锁的问题
from threading import Thread,Lock import time mutexA = Lock() mutexB = Lock() class MyThread(Thread): def run(self): # 创建线程自动触发run方法,方法内调用func1,func2也相当于是自动触发 self.func1() self.func2() def func1(self): mutexA.acquire() print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name mutexB.acquire() print('%s抢到了B锁'%self.name) mutexB.release() print('%s释放了B锁'%self.name) mutexA.release() print('%s释放了A锁'%self.name) def func2(self): mutexB.acquire() print('%s抢到了B锁'%self.name) time.sleep(1) mutexA.acquire() print('%s抢到了A锁'%self.name) mutexA.release() print('%s释放了A锁'%self.name) mutexB.release() print('%s释放了B锁'%self.name) for i in range(10): t = MyThread() t.start() ''' Thread-1抢到了A锁 Thread-1抢到了B锁 Thread-1释放了B锁 Thread-1释放了A锁 Thread-1抢到了B锁 Thread-2抢到了A锁 '''
RLock
定义:RLock是递归锁
- 可以被第一个抢到锁的人连续的acquire和release
- 每acquire一次锁身上的计数加1
- 每release一次锁身上的计数加1
- 只要锁的计数不为0,其他人都不能抢
from threading import Thread,RLock import time mutexA = mutexB = RLock() class MyThread(Thread): def run(self): # 创建线程自动触发run方法,方法内调用func1,func2也相当于是自动触发 self.func1() self.func2() def func1(self): mutexA.acquire() print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name mutexB.acquire() print('%s抢到了B锁'%self.name) mutexB.release() print('%s释放了B锁'%self.name) mutexA.release() print('%s释放了A锁'%self.name) def func2(self): mutexB.acquire() print('%s抢到了B锁'%self.name) time.sleep(1) mutexA.acquire() print('%s抢到了A锁'%self.name) mutexA.release() print('%s释放了A锁'%self.name) mutexB.release() print('%s释放了B锁'%self.name) for i in range(10): t = MyThread() t.start() ''' Thread-1抢到了A锁 Thread-1抢到了B锁 Thread-1释放了B锁 Thread-1释放了A锁 Thread-1抢到了B锁 Thread-1抢到了A锁 Thread-1释放了A锁 Thread-1释放了B锁 Thread-2抢到了A锁 Thread-2抢到了B锁 Thread-2释放了B锁 Thread-2释放了A锁 ... '''
Event事件
from threading import Event,Thread import time # 先生成一个event对象 e = Event() def light(): print('红灯正亮着') time.sleep(3) e.set() # 发信号 print('绿灯亮了') def car(name): print('%s正在等红灯'%name) e.wait() # 等待信号 print('%s加油门飙车了'%name) t = Thread(target=light) t.start() for i in range(6): t = Thread(target=car,args=('伞兵%s'%i,)) t.start() ''' 红灯正亮着 伞兵0正在等红灯 伞兵1正在等红灯 伞兵2正在等红灯 伞兵3正在等红灯 伞兵4正在等红灯 伞兵5正在等红灯
(三秒后)
绿灯亮了
伞兵4加油门飙车了
伞兵5加油门飙车了
伞兵2加油门飙车了
伞兵3加油门飙车了
伞兵0加油门飙车了
伞兵1加油门飙车了
'''
信号量
- 互斥锁类似一个厕所(一个坑位)
- 信号量类似公共厕所(多个坑位)
from threading import Semaphore,Thread import time import random sm = Semaphore(5) # 造了一个有5个坑位的公共厕所 def task(name): sm.acquire() print('%s占了一个坑位'%name) time.sleep(random.randint(1,3)) sm.release() for i in range(10): t = Thread(target=task,args=(i,)) t.start() ''' 0占了一个坑位 1占了一个坑位 2占了一个坑位 3占了一个坑位 4占了一个坑位 5占了一个坑位 7占了一个坑位 6占了一个坑位 8占了一个坑位 9占了一个坑位 '''
线程Queue
同一个进程下的多个线程本来就是数据共享,为什么还要用队列?
- 因为队列是管道+锁 使用队列你就不需要自己手动操作锁的问题
import queue q = queue.Queue() q.put('hahha') print(q.get()) q = queue.LifoQueue() q.put(1) q.put(2) q.put(3) print(q.get()) q = queue.PriorityQueue() # 数字越小 优先级越高 q.put((10,'haha')) q.put((100,'hehehe')) q.put((0,'xxxx')) q.put((-10,'yyyy')) print(q.get()) ''' hahha 3 (-10, 'yyyy') '''