今天在知乎上看到一篇文章 为什么有人说 Python 多线程是鸡肋? 中Python中的多线程是单核多线程,是伪多线程!为什么会这么说?
由于Python 中 GIL。正是这个锁能保证同时只有一个线程在运行。罪魁祸首::。但如果去掉GIL的 Python 在单线程条件下执行效率将近慢了2倍。~~如果一定要通过多线程利用多核,那只能通过C扩展来实现,不过这样就失去了Python简单易用的特点。
当然,对于 IO 密集型的程序,Python目前对 多线程性能还是有很大地改善的。 Python 3.4引入的 asyncio 模块来实现“协程”。
协程其实也是一种线程,其开销比threading小。而且它是Python3.4引入的新标准库 asyncio。asyncio的编程模型是一个消息循环。需要从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,实现了异步IO。这里的EventLoop有点像线程池。
这就使得很多 IO 操作有了更好的方式去解决,虽然Python没有真正意义上的多线程,但采用 Event Loop 来处理耗时的 IO 操作,效果非常好。
下面简单聊聊多线程协程之间的协作:
EventLoop
总是与 thread
共存,它只是负责接收事件,余下的由 thread
来解决,保证并发。
下面举一个测试例子:
def task(): for i in range(5): time.sleep(1) print("task--"+str(i)) def run_loop_inside_thread(loop): loop.run_forever() new_loop = asyncio.new_event_loop() asyncio.set_event_loop(new_loop) loop = asyncio.get_event_loop() threading.Thread(target=run_loop_inside_thread, args=(loop,)).start() loop.call_soon_threadsafe(task) return "finish"
上面的例子,主要是在主线程中创建一个new_loop,然后在另外的子线程中开启一个无限事件循环。主线程通过call_soon_threadsafe新注册协程对象。这样就能在子线程中进行事件循环的并发操作,同时主线程又不会被block。
如果想传参的话,则可如下面所写:(传参6到more_work方法中)
.call_soon_threadsafe(more_work, 6)
其中:
event_loop
是 asyncio
的起点,是执行所有事件的起点
通过 loop.run_forever()
+ loop.call_*
实现对事件的调度
如果不加
new_loop = asyncio.new_event_loop() asyncio.set_event_loop(new_loop)
则会报错:RuntimeError: There is no current event loop in thread 'Thread-1'。为什么会这样?根据asyncio的文档介绍,asyncio的事件循环不是线程安全的,一个event loop只能在一个线程内调度和执行任务,并且同一时间只有一个任务在运行。
处于非主线程时,还需要调用set_event_loop
方法指定一个event loop对象,这样get_event_loop
才会获取到被标记的event loop对象
但如果你只是运行 一个只有主线程的demo 的话,会发现 asyncio.get_event_loop()来
获取 event_loop,是没问题的。
上面的例子中的call_soon_threadsafe
是asynico在多线程情况下专门针对线程安全的调用的解决方案。一般如果event loop在主线程中运行的话,子线程是不能使用它来调度任务。
值得注意点是:在非阻塞的情况下,多线程是同步的代表,而协程是异步的代表。二者都可以开启多个线程。在多线程中,多个线程会竞争谁先运行,一个等待结束也不会去通知主程序,这样没有章法的随机运行会造成一些资源浪费。而在协程中,多个线程(称为微线程)的调用和等待都是通过明确代码组织的。协程就像目标明确地执行一个又一个任务,而多线程则会在竞争过程中性能有所降低。