Python线程意外变慢

5 投票
3 回答
7772 浏览
提问于 2025-04-16 00:27

我决定学习一下在Python中如何进行多线程编程,并且做了一个对比,想看看在双核CPU上能获得什么样的性能提升。我发现我的简单多线程代码实际上比顺序执行的代码还要慢,我搞不明白为什么。

我设计的测试是生成一个很大的随机数列表,然后打印出其中的最大值。

from random import random
import threading

def ox():
    print max([random() for x in xrange(20000000)])

ox()在我的Intel Core 2 Duo上大约需要6秒完成,而ox();ox()则需要大约12秒。

接着,我尝试从两个线程中调用ox(),看看这样完成的速度如何。

def go():
    r = threading.Thread(target=ox)
    r.start()
    ox()

go()大约需要18秒完成,两个结果的打印时间相差不到1秒。为什么会更慢呢?

我怀疑ox()可能是自动并行处理的,因为如果我在Windows任务管理器的性能标签中查看,当我在Python控制台中调用ox()时,两个处理器的使用率都跳到了大约75%,直到完成。Python会自动并行处理像max()这样的操作吗?

3 个回答

0

正如Yann所说,Python的全局解释器锁(GIL)会阻止这个例子中的并行处理。你可以使用Python的多进程模块来解决这个问题,或者如果你愿意使用其他开源库,Ray也是一个很好的选择,它能绕过GIL的问题,而且比Python的多进程库更容易使用,功能也更多。

下面是如何用Ray来实现你的代码并行化的示例:

from random import random
import ray

ray.init()

@ray.remote
def ox():
    print(max([random() for x in range(20000000)]))

%time x = ox.remote(); y = ox.remote(); ray.get([x, y])

在我的机器上,你发的单线程ox()代码运行需要1.84秒,而用Ray调用两次的总时间是1.87秒,所以我们几乎实现了完美的并行处理。

Ray还让任务之间的数据共享变得非常高效,在单台机器上,它会在后台使用共享内存,具体可以查看这个链接

你还可以在你的集群或云端的不同机器上运行相同的程序,而无需修改程序,具体可以参考文档(这个链接这个链接)。

免责声明:我是Ray的开发者之一。

2

问题出在函数 random() 上。如果你把 random 从你的代码中去掉,两个处理器核心就会尝试访问共享的 random 函数的状态。这两个核心会一个接一个地工作,花费很多时间在缓存的同步上。这种情况被称为“虚假共享”。想了解更多,可以看看这篇文章 虚假共享

9
  1. Python有个叫做GIL的东西。简单来说,Python的代码一次只能在一个处理器上运行。只有一些特定的C语言模块(这些模块不管理Python的状态)才能同时运行。
  2. Python的GIL在不同线程之间锁定状态时会消耗很多资源。新版本或者正在开发的版本中有一些解决方案,至少可以让多线程的CPU密集型代码运行得和单线程一样快。

要在Python中实现并行处理,你需要使用多进程的框架。幸运的是,Python自带的multiprocessing模块让这件事变得相对简单。

很少有编程语言能够自动并行处理表达式。如果你想要这样的功能,我建议你试试Haskell(数据并行Haskell)。

撰写回答