为什么Python的math.factorial不能与线程一起使用?

时间:2012-03-21 22:22:23

标签: python multithreading blocking factorial gil

为什么math.factorial在一个线程中表现得如此奇怪?

这是一个例子,它创建了三个线程:

  • 只是睡了一会儿的线程
  • 一段时间递增int的线程
  • 在一个大数字上执行math.factorial的线程。

它在线程上调用start,然后在join调用超时

sleep和spin线程按预期工作,并立即从start返回,然后在join中暂停。

另一方面,析因线程不会从start返回,直到它运行到最后!

import sys
from threading import Thread
from time import sleep, time
from math import factorial

# Helper class that stores a start time to compare to
class timed_thread(Thread):
    def __init__(self, time_start):
        Thread.__init__(self)
        self.time_start = time_start

# Thread that just executes sleep()
class sleep_thread(timed_thread):
    def run(self):
        sleep(15)
        print "st DONE:\t%f" % (time() - time_start)

# Thread that increments a number for a while       
class spin_thread(timed_thread):
    def run(self):
        x = 1
        while x < 120000000:
            x += 1
        print "sp DONE:\t%f" % (time() - time_start)

# Thread that calls math.factorial with a large number
class factorial_thread(timed_thread):
    def run(self):
        factorial(50000)
        print "ft DONE:\t%f" % (time() - time_start)

# the tests

print
print "sleep_thread test"
time_start = time()

st = sleep_thread(time_start)
st.start()
print "st.start:\t%f" % (time() - time_start)
st.join(2)
print "st.join:\t%f" % (time() - time_start)
print "sleep alive:\t%r" % st.isAlive()


print
print "spin_thread test"
time_start = time()

sp = spin_thread(time_start)
sp.start()
print "sp.start:\t%f" % (time() - time_start)
sp.join(2)
print "sp.join:\t%f" % (time() - time_start)
print "sp alive:\t%r" % sp.isAlive()

print
print "factorial_thread test"
time_start = time()

ft = factorial_thread(time_start)
ft.start()
print "ft.start:\t%f" % (time() - time_start)
ft.join(2)
print "ft.join:\t%f" % (time() - time_start)
print "ft alive:\t%r" % ft.isAlive()

这是CentOS x64上Python 2.6.5的输出:

sleep_thread test
st.start:       0.000675
st.join:        2.006963
sleep alive:    True

spin_thread test
sp.start:       0.000595
sp.join:        2.010066
sp alive:       True

factorial_thread test
ft DONE:        4.475453
ft.start:       4.475589
ft.join:        4.475615
ft alive:       False
st DONE:        10.994519
sp DONE:        12.054668

我在CentOS x64上的python 2.6.5上试过这个,在Windows x86上使用2.7.2,并且在线程完成执行之前,因子线程不会从它们的任何一个开始返回。

我也在Windows x86上尝试使用PyPy 1.8.0,结果略有不同。开始会立即返回,但之后连接不会超时!

sleep_thread test
st.start:       0.001000
st.join:        2.001000
sleep alive:    True

spin_thread test
sp.start:       0.000000
sp DONE:        0.197000
sp.join:        0.236000
sp alive:       False

factorial_thread test
ft.start:       0.032000
ft DONE:        9.011000
ft.join:        9.012000
ft alive:       False
st DONE:        12.763000

也尝试了IronPython 2.7.1,它产生了预期的结果。

sleep_thread test
st.start:       0.023003
st.join:        2.028122
sleep alive:    True

spin_thread test
sp.start:       0.003014
sp.join:        2.003128
sp alive:       True

factorial_thread test
ft.start:       0.002991
ft.join:        2.004105
ft alive:       True
ft DONE:        5.199295
sp DONE:        5.734322
st DONE:        10.998619

2 个答案:

答案 0 :(得分:5)

线程通常只允许在Python中交错不同的东西,而不是同时发生的不同事情,因为Global Interpreter Lock

如果你看一下Python字节码:

from math import factorial

def fac_test(x):
    factorial(x)

import dis
dis.dis(fac_test)

你得到:

  4           0 LOAD_GLOBAL              0 (factorial)
              3 LOAD_FAST                0 (x)
              6 CALL_FUNCTION            1
              9 POP_TOP             
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE        

正如您所看到的,对math.factorial的调用是Python字节码级别(6 CALL_FUNCTION)的单个操作 - 它在C中实现。factorial不释放GIL因为它所做的工作类型(参见我的回答的评论),所以Python在运行时不会切换到其他线程,并且你得到了你观察到的结果。

答案 1 :(得分:2)

Python有一个全局解释器锁(GIL),它要求CPU绑定的线程轮流而不是并发运行。由于阶乘函数是用C语言编写的,并且不会释放GIL,因此即使设置sys.setswitchinterval也不足以让线程合作。

multiprocessing模块提供Process对象,它们类似于线程,但在不同的地址空间中工作。对于CPU绑定的任务,您应该强烈考虑使用multiprocessing模块。