Python是否支持多线程?可以加快执行时间吗?


95

我对多线程是否可以在Python中工作感到有些困惑。

我知道对此有很多疑问,我已经阅读了很多,但是我仍然很困惑。我从我自己的经验中知道,并且看到其他人在StackOverflow上发表了自己的答案和示例,在Python中确实可以实现多线程。那么,为什么每个人都在说Python被GIL锁定并且一次只能运行一个线程呢?显然可以。还是我不来这里有什么区别?

许多张贴者/受访者还不断提到线程是有限的,因为它不使用多个核心。但是我会说它们仍然有用,因为它们可以同时工作,因此可以更快地完成合并的工作量。我的意思是为什么还要有Python线程模块呢?

更新:

到目前为止,感谢您提供所有答案。据我了解,多线程只能并行运行某些IO任务,而一次只能运行一个CPU绑定的多个核心任务。

我并不完全确定这对我实际上意味着什么,所以我仅举一个我想进行多线程的任务示例。例如,假设我要遍历很长的字符串列表,并且希望对每个列表项执行一些基本的字符串操作。如果拆分列表,将每个要由循环/字符串代码处理的子列表发送到新线程中,然后将结果发送回队列中,这些工作负载是否会大致同时运行?最重要的是,从理论上讲,这会加快运行脚本的时间吗?

另一个例子可能是,如果我可以在四个不同的线程中使用PIL渲染和保存四张不同的图片,并且这比一张又一张地处理图片要快吗?我想这个速度要素是我真正想知道的,而不是正确的术语。

我也了解多处理模块,但是我现在的主要兴趣是中小型任务负载(10-30秒),因此我认为多线程将更合适,因为子进程的启动速度很慢。


4
这是一个非常漂亮的问题。我想答案就在于什么你想拥有的线程做。在大多数情况下,GIL会阻止同时运行多个线程。但是,在某些情况下会释放GIL(例如,从文件中读取),因此可以并行完成。另请注意,GIL是Cpython 的实现细节(最常见的实现)。python的其他任何实现(Jython,PyPy等)都没有GIL(AFAIK)
mgilson 2014年

2
@mgilson PyPy有一个GIL。

2
@delnan-您似乎是正确的。谢谢。
mgilson 2014年

1
“子流程启动可能很慢” –您可以创建一个准备好执行的任务池。开销可以限制在大约开始对任务开始工作所需的数据进行序列化/反序列化所花费的时间。
Brian Cain

1
@KarimBahgat,这正是我的意思。
Brian Cain 2014年

Answers:


132

GIL不会阻止线程化。GIL所做的全部工作就是确保一次只有一个线程在执行Python代码。控制仍然在线程之间切换。

GIL当时阻止的事情是利用多个CPU内核或单独的CPU并行运行线程。

这仅适用于Python代码。C扩展可以并且确实会发布GIL,以允许C代码的多个线程和一个Python线程跨多个内核运行。这扩展到由内核控制的I / O,例如select()对套接字读写的调用,使Python在多线程多核设置中合理有效地处理网络事件。

然后,许多服务器部署将执行多个Python进程,以使OS处理进程之间的调度,以最大程度地利用CPU内核。如果适合您的用例,您还可以使用该multiprocessing来处理来自一个代码库和父进程的多个进程的并行处理。

注意,GIL仅适用于CPython实现。Jython和IronPython使用不同的线程实现(分别是本机Java VM和.NET公共运行时线程)。

直接解决更新问题:任何尝试使用纯Python代码从并行执行中提高速度的任务都不会看到加速,因为线程化的Python代码一次只能锁定一个线程。但是,如果混用C扩展名和I / O(例如PIL或numpy操作),则任何C代码都可以与一个活动的Python线程并行运行。

Python线程非常适合创建响应式GUI或处理多个简短的Web请求,其中I / O比Python代码更多地成为瓶颈。它不适用于并行化计算量大的Python代码,不适合执行multiprocessing此类任务的模块或委托给专用的外部库。


感谢@MartijnPieters,对于问题是否可以用于加速诸如for循环之类的代码(不是)的问题,我有一个更清晰的答案。也许您或某人可以写一个我可以接受的新答案,其中提供了一些通用模块/代码/操作的特定示例,其中GIL允许线程运行并行运行,因此运行速度更快(例如,这些I / O和网络/已提及的套接字读取操作,以及在Python中使用多线程的其他情况)。如果可能的话,也许列出了很多常见的多线程用法以及一些编程示例?
卡里姆·巴格加特

4
不,我认为这样的回答不是很有帮助;老实说。您永远都无法创建详尽的列表,但是根据经验法则,任何I / O(文件读写,网络套接字,管道)都在C中处理,并且许多C库也为其发布GIL。操作,但是由库来为您进行记录。
马丁·皮特斯

1
我的不好,直到现在为止都没有看到更新的答案,在这里您给出了一些很好的线程用法示例。这些包括(如果我错了,请纠正我)网络编程(例如urllib.urlopen()?),以便从Python GUI中调用一个Python脚本,并使用线程调用多个PIL(例如Image.transform())和numpy(例如numpy.array())操作。并且您在注释中提供了更多示例,例如使用多个线程读取文件(例如,f.read()?)。我知道不可能有一个详尽的列表,只是想要您在更新中提供的示例类型。无论哪种方式,都接受您的回答:)
Karim Bahgat 2014年

2
@KarimBahgat:是的,urllib.urlopen()将调用网络套接字,等待套接字I / O是切换线程并执行其他操作的绝好机会。
马丁·皮特斯

4
尽管它与该问题没有直接关系,但值得注意的是,有时线程根本与性能无关;将您的代码编写为多个独立的执行线程可能更简单。例如,您可能有一个线程在播放背景音乐,一个线程在为UI提供服务,而另一个则在忙于最终必须完成但并不急于进行的计算。尝试使用UI Runloop顺序播放下一个音频缓冲区,或者将计算分解为足够小的块而不会影响交互性,可能比使用线程要困难得多。
2014年

4

是。:)

您具有低级线程模块和高级线程模块。但是您只需要使用多核计算机,多处理模块就是您的理想之选。

文档引用:

在CPython中,由于具有全局解释器锁,因此只有一个线程可以一次执行Python代码(即使某些面向性能的库可能克服了此限制)。如果希望您的应用程序更好地利用多核计算机的计算资源,建议您使用多处理。但是,如果您要同时运行多个I / O绑定任务,则线程化仍然是合适的模型。


3

Python允许线程化,唯一的问题是GIL将确保一次只执行一个线程(无并行性)。

因此,基本上,如果您想对代码进行多线程处理以加快计算速度,那么一次只能执行一个线程就不会加快代码的处理速度,但是例如,如果您使用它与数据库进行交互,它就会加快速度。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.