Queue
标准库queue模块,提供FIFO(先进先出)的Queue、LIFO(后进先出)的队列、优先队列。
Queue类是线程安全的,适用于多线程间安全的交换数据。内部使用了Lock和Condition。
使用魔术方法,实现的容器的大小,是不准确的。not reliable!
因为在多线程中,如果不加锁,是不可能获得准确的大小的,因为当刚刚读取到一个大小数值,还没有取走,就有可能被其它线程修改了。
Queue类的size虽然加了锁,但是,依然不能保证立即get、put就能成功,因为读取队列大小qsize和get、put方法是分开的。
全局解释器锁:
CPython在解释器级别的一把锁,叫GIL全局解释器锁。(Ruby也有)
Global Interpreter Lock
程序编译成字节码,程序想跑多线程,但是GIL保证CPython进程中,同一时刻只能有一个线程执行字节码。
所以,哪怕是在多CPU的情况下,即使每个线程恰好调度到了每个CPU上,有了这把大锁,同时只能有一个CPU使用CPython执行一个线程的字节码,其它线程只能阻塞等待。
也就是说只要有这把锁,CPython中根本就没有真正的多线程。同一时刻只有CPU的一个线程被调度使用。
GIL锁从CPython最初版本到现在一直存在。
CPython中:
IO密集型,由于线程阻塞,就会调度其它线程;(wait会让出时间片,让其它线程有机会被调度、sleep不会),大量使用网络IO、磁盘IO
CPU密集型,当前线程可能会连续的获得GIL,导致其它线程几乎无法使用CPU。(刚释放锁就又夺走了),大量占用CPU计算得数
IO密集型,使用多线程;CPU密集型,使用多进程,绕开GIL。
多进程时,同样每个进程内的线程也受GIL这把大锁的限制。
新版CPython正在努力优化GIL的问题,但不是移除。
如果非要使用多线程的效率问题,请绕行,选择其它语言erlang、Go等。
保留GIL的原因:
独裁者Guido坚持简单哲学(import this),对于初学者门槛低,不需要高深的系统只是也能安全、简单的使用Python。
而且移除GIL,会降低CPython单线程的执行效率。
模拟CPU密集型:
#模拟CPU密集型 import threading,logging,time,random,datetime DATEFMT="%H:%M:%S" FORMAT = "[%(asctime)s] [%(threadName)s,%(thread)d] %(message)s" logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt=DATEFMT) def calc(): sum = 0 for _ in range(100000000): sum += 1 start =datetime.datetime.now() calc() calc() calc() calc() calc() delta = (datetime.datetime.now() -start).tota 运行结果: 38.820701
上例是单线程串行执行结果。
对上例使用多线程:
#模拟CPU密集型 多线程 import threading,logging,time,random,datetime DATEFMT="%H:%M:%S" FORMAT = "[%(asctime)s] [%(threadName)s,%(thread)d] %(message)s" logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt=DATEFMT) def calc(): sum = 0 for _ in range(100000000): sum += 1 start =datetime.datetime.now() lst = [] for _ in range(5): t = threading.Thread(target=calc) t.start() lst.append(t) for t in lst: t.join() delta = (datetime.datetime.now() -start).total_seconds() print(delta) 运行结果: 38.782773
上面分别使用了单线程和多线程来测试效率,耗时都在38秒左右,所以因为GIL的存在,CPython中多线程根本没有任何优势,和单线程执行效率相当。