Python 线程与 Linux 中的多处理
Posted
技术标签:
【中文标题】Python 线程与 Linux 中的多处理【英文标题】:Python threading vs. multiprocessing in Linux 【发布时间】:2013-06-29 18:15:10 【问题描述】:基于这个question,我假设创建新进程应该几乎与在Linux中创建新线程一样快。然而,很少的测试显示出非常不同的结果。这是我的代码:
from multiprocessing import Process, Pool
from threading import Thread
times = 1000
def inc(a):
b = 1
return a + b
def processes():
for i in xrange(times):
p = Process(target=inc, args=(i, ))
p.start()
p.join()
def threads():
for i in xrange(times):
t = Thread(target=inc, args=(i, ))
t.start()
t.join()
测试:
>>> timeit processes()
1 loops, best of 3: 3.8 s per loop
>>> timeit threads()
10 loops, best of 3: 98.6 ms per loop
因此,创建过程几乎要慢 40 倍!为什么会这样?它是特定于 Python 还是这些库?还是我只是误解了上面的答案?
UPD 1. 使其更清晰。我知道这段代码实际上并没有引入任何并发性。这里的目标是测试创建进程和线程所需的时间。要在 Python 中使用真正的并发,可以使用如下方式:
def pools():
pool = Pool(10)
pool.map(inc, xrange(times))
它的运行速度确实比线程版本快得多。
UPD 2.我添加了带有os.fork()
的版本:
for i in xrange(times):
child_pid = os.fork()
if child_pid:
os.waitpid(child_pid, 0)
else:
exit(-1)
结果是:
$ time python test_fork.py
real 0m3.919s
user 0m0.040s
sys 0m0.208s
$ time python test_multiprocessing.py
real 0m1.088s
user 0m0.128s
sys 0m0.292s
$ time python test_threadings.py
real 0m0.134s
user 0m0.112s
sys 0m0.048s
【问题讨论】:
嗯,您链接到的问题是比较仅调用fork(2)
与pthread_create(3)
的成本,而您的代码做得更多。比较os.fork()
和thread.start_new_thread()
怎么样?
@Aya: 我在thread
模块中找不到任何类型的join
来创建类似的测试,但即使与带有os.fork()
的高级threading
版本相比仍然很多慢点。事实上,它是最慢的(尽管附加条件可能会影响性能)。查看我的更新。
如果你使用低级thread
模块,你必须使用互斥锁来等待线程,这就是高级threading
模块实现join()
的方式。但是,如果您只是想测量创建新进程/线程所需的时间,那么您不应该调用join()
。另请参阅下面的答案。
【参考方案1】:
您链接到的问题是比较仅调用 fork(2)
与 pthread_create(3)
的成本,而您的代码做得更多,例如使用join()
等待进程/线程终止。
如果如你所说……
这里的目标是测试创建进程和线程所需的时间。
...那么您不应该等待它们完成。您应该使用更像这些的测试程序...
fork.py
import os
import time
def main():
for i in range(100):
pid = os.fork()
if pid:
#print 'created new process %d' % pid
continue
else:
time.sleep(1)
return
if __name__ == '__main__':
main()
thread.py
import thread
import time
def dummy():
time.sleep(1)
def main():
for i in range(100):
tid = thread.start_new_thread(dummy, ())
#print 'created new thread %d' % tid
if __name__ == '__main__':
main()
...给出以下结果...
$ time python fork.py
real 0m0.035s
user 0m0.008s
sys 0m0.024s
$ time python thread.py
real 0m0.032s
user 0m0.012s
sys 0m0.024s
...所以线程和进程的创建时间差别不大。
【讨论】:
但是您的fork.py
不会只是创建新线程并退出,而不等待子进程完成吗?
另外,您启动下一个线程/进程而不等待前一个完成,因此它们同时运行,而顺序启动它们似乎更正确以避免 GIL 和所有此类事情。
@ffriend 好吧,你的问题是(强调我的)“我认为 创建 新进程应该几乎与在 Linux 中创建新线程一样快”,确实如此。使用线程的全部意义在于并发性,那么顺序运行线程的意义何在?您到底想在这里实现什么目标?
我正在尝试比较运行新线程和新进程的开销。我强调创建将线程/进程与 GIL、函数调用等其他细节分开。当然,将其重新加入也很重要。顺序运行许多线程/进程只是找出平均时间的另一种方法。有关详细信息,请参阅我的第一次更新。
@ffriend 好吧,如果你包括拆除时间,那么进程比线程花费的时间要长得多,但无论哪种方式,开销仍然在毫秒范围内。但是,在实践中,如果设置和拆除进程/线程所需的时间大于进程/线程工作的时间,那么使用它们就没有多大意义了。否则,开销是无关紧要的,在两者之间进行选择应该基于哪个更适合您要实现的实际目标。【参考方案2】:
根据我的经验,创建线程(使用pthread_create
)和分叉进程之间存在显着差异。
例如,我使用如下线程代码创建了一个类似于您的 python 测试的 C 测试:
pthread_t thread;
pthread_create(&thread, NULL, &test, NULL);
void *res;
pthread_join(thread, &res);
并像这样处理分叉代码:
pid_t pid = fork();
if (!pid)
test(NULL);
exit(0);
int res;
waitpid(pid, &res, 0);
在我的系统上,分叉代码的执行时间大约是原来的 8 倍。
然而,值得注意的是,python 的实现速度更慢——对我来说,它的速度大约慢了 16 倍。我怀疑这是因为除了创建新进程的常规开销之外,还有更多与新进程相关的 python 开销。
【讨论】:
【参考方案3】:是的,这是真的。启动一个新流程(称为重量级流程)的成本很高。
作为概述...
操作系统必须(在 linux 的情况下)派生第一个进程,为新进程设置记帐,设置新堆栈,进行上下文切换,复制任何被更改的内存,然后拆除所有这些当新进程返回时。
线程只是分配一个新的栈和线程结构,做上下文切换,工作完成后返回。
...这就是我们使用线程的原因。
【讨论】:
你倒退了。一个过程只是一个过程。线程是轻量级进程:) 我想您可以将进程称为重量级线程,但我认为没有人这样做。什么是重量级进程? @thang 叹了口气。如果你不知道某事,那么至少你可以谷歌它。尝试谷歌搜索“重量级进程”,看看是否有人这样做。以上是关于Python 线程与 Linux 中的多处理的主要内容,如果未能解决你的问题,请参考以下文章
Linux 操作系统原理 — NUMA 架构中的多线程调度开销与性能优化