Python多线程和多进程

来源:互联网 发布:shell在哪编程 编辑:程序博客网 时间:2024/05/18 20:10

如果你已经在Python领域混了很久,那么你肯定遇到过GIL这个词,而且知道它多么讨厌。GIL是指Global Interpreter Lock,当CPython每次要执行字节码时都要先申请这个锁。但是,这意味着,如果试图通过多线程扩展应用程序,将总是被这个全局锁所限制。
所以尽管多线程看上去是一个理想的解决方案,但实际上我看到大多数应用程序都很难获取到150%的CPU利用率,也就是使用1.5个核(core)。考虑到现如今计算节点通常至少2个或4个核,这是很没面子的。这都归咎于GIL。
然而,CPython只是Python的可用实现之一。例如,Jython就没有全局解释锁,这意味着可以有效地并行运行多个线程。遗憾的是,这些项目相对于CPython都非常滞后,所以实际上并不能作为目标平台来使用。

  注意   PyPy是另一个Python实现,但是是使用Python开发的。PyPy  也有GIL。但目前有一个非常有意思的工作正在试图用STM(Softwate Transactional Memory)的实现替换它。这对于未来构建和运行多线程软件是非常值得期待的变化。某些处理器正在试图提供硬件支持,而Linux内核的开发者也在寻求废弃内核锁的方法。这些都是积极的信号。

没有好的方案是不是我们又回到了最初的场景呢的?并非如此,至少还有以下两种方案可用。
(1)如果需要运行后台任务,最容易的方式是基于事件循环构建应用程序。许多不同的Python模块都提供这一机制,甚至有一个标准库中的模块—-asyncore,它是PEP 3156(https://www.python.org/dev/peps/pep-3156/)中标准化这一功能的成果。有些框架就是基于这一概念构建的,如Twisted。最高级的框架应该是提供基于信号量、计时器和文件描述符活动来访问时间。
(2)如果需要分散工作负载,使用多进程会更简单有效。
处理好多线程是很难的。其复杂程度意味着与其他方式相比较它是bug的更大来源,而且考虑到通常能够获得的好处很少,所以最好不要在多线程上浪费太多精力。
2 多进程与多线程
正如前面解释的,因为GIL的问题,多线程并非好的可扩展性方案。更好的方案是Python中提供的multiprocessing包。
它提供了类似multithreading模块中的接口,区别在于它会启动一个新的进程(通过fork(2))而不是一个新的系统线程。
下面是一个简单的例子,计算100万个随机整数的和8次,同时将其分散到8个线程中。
使用多线程的worker

import randomimport threadingresults = []def compute():    results.append(sum(        [random.randint(1, 100) for i in range(1000000)]))worker = [threading.Thread(target=compute) for x in rang(8)]      for worker in workers:     worker.start()for worker in workers:     worker.join()print("Results: %s" % results)    

程序的运行结果如图:
这里写图片描述
这个程序运行在四核的CPU上,这意味着Python最多可以利用400%的CPU能力。但显然它做不到,即使并行运行8个进程,它仍然卡在了129%,这只是硬件能力的32%。
现在,我们使用multiprocessing重写一下

import randomimport multiprocessingdef compute(n):     return sum(        random.randint(1, 100) for i in   range(1000000)])#start 8 workerspool = multiprocessing.Pool(8)print("Results: %s" % pool.map(compute,range(8)))    

在同样的条件下运行这个程序,结果如图所示
time python worker.py的结果
这里写图片描述
执行时间减少到60%,这次程序可以消耗363%的CPU能力。超过CPU能力的90%
此外,multiprocessing模块不仅可以有效地将负载分散到多个本地处理器上,而且可以通过它的multiproceing.managers对象在网络中分散负载。它还提供了双向传输,以使进程间可以彼此交换信息。
每次考虑在一定的时间内并行处理一些工作时,最好是依靠多进程创建(fork)多个作业,以便能够在多个CPU核之间分散负载。

—-本文摘自《Python高手之路》第十一章

原创粉丝点击