3

なぜmath.factorialはスレッドでそれほど奇妙に動作するのですか?

次に例を示します。3つのスレッドを作成します。

  • しばらく眠るだけのスレッド
  • しばらくの間intをインクリメントするスレッド
  • 多数でmath.factorialを実行するスレッド。

startスレッドを呼び出しjoin、タイムアウトします

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()

そして、CentOSx64でのPython2.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

CentOSx64のpython2.6.5、Windows x86の2.7.2でこれを試しましたが、スレッドの実行が完了するまで、どちらの場合も階乗スレッドは最初から戻りません。

Windowsx86のPyPy1.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
4

2 に答える 2

5

グローバルインタープリターロックのため、スレッドは多くの場合、Pythonで異なるものをインターリーブすることのみを許可し、同時に異なるものをインターリーブすることはできません。

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        

ご覧のとおり、toの呼び出しmath.factorialはPythonバイトコードレベル(6 CALL_FUNCTION)での単一の操作です。Cで実装されています。実行するfactorial作業の種類が原因でGILが解放されないため(私の回答のコメントを参照)、 Pythonは、実行中に他のスレッドに切り替わることはなく、観察した結果が得られます。

于 2012-03-21T22:32:35.267 に答える
2

Pythonにはグローバルインタープリターロック(GIL)があり、CPUにバインドされたスレッドを同時に実行するのではなく、順番に実行する必要があります。階乗関数はCで記述されており、GILを解放しないため、設定でもsys.setswitchintervalスレッドを連携させるには不十分です。

このmultiprocessingモジュールは、スレッドに似ていますが、別々のアドレス空間で機能するプロセスオブジェクトを提供します。multiprocessingCPUにバインドされたタスクの場合、モジュールの使用を強く検討する必要があります。

于 2012-03-21T22:34:56.740 に答える