2017-08-19 18 views
0

私はPythonのMTを理解しようとしています。 threading.Lockを使用した例が見つかりました。しかし、それはPython 2.7とPython 3.6で異なる出力を生成しました。本当に混乱しました。ここでスレッドモジュールがPython 3で変更されましたか?そしてもしそうなら、どうですか?

はコードです:

import threading 
import time 
import random 

class meThread(threading.Thread): 
    def run(self): 
     global num 
     time.sleep(random.randint(1,3)) 
     num += 1 
     print(self.name+'set num to '+str(num)) 

num = 0 

threads = [] 
for i in range(5): 
    t = meThread() 
    threads.append(t) 

for i in range(5): 
    threads[i].start() 

for i in range(5): 
    threads[i].join() 

とPython3.6での出力:

Thread-4set num to 1 
Thread-2set num to 2 
Thread-1set num to 3 
Thread-3set num to 4 
Thread-5set num to 5 

とPython2.7での出力:

Thread-1set num to 1 
Thread-4set num to 2 
Thread-3set num to 4 
Thread-2set num to 5 
Thread-5set num to 3 

出力が常にあります3.6では同じですが、2.7では、threading.Lockを使用しないと予期しないことが起こります。どうして? Pythonは3.6で自動的にスレッドにロックを追加しますか?

+1

第2のものが最初に間違っているのは何ですか? 2.6でコードを数回実行しましたか? –

+1

ランダムな時間間隔を使用して各スレッドをスリープさせているときに、同じ結果が期待されるのはなぜですか?ランダムな時間間隔は実行時に異なるかもしれませんが、Pythonの同じバージョンでコードを実行すると、別の結果を返す可能性があります –

+0

3.6では、出力は常に1,2,3,4,5ですが、1,3,4 、2,5など、2.7のように、昇順ではありません。私はthreading.Lockを使用していない場合、異なるスレッドがグローバル変数varを同時に変更できることを意味し、出力は2.7のように任意でなければなりません。しかし出力は3.6と常に同じです。 – Bruce

答えて

1

ロックを使用せずにPythonのバージョン間でスレッドの振る舞いが変更された場合、複数の非同期スレッド間でnumをインクリメントする動作は、最高でも非決定的になります。同じPC上の同じインタプリタで複数回実行しても、異なる結果が生じる可能性があります。なぜなら、スレッド上のコンテキストスイッチがいつ発生するのかを決して知らないからです。

この文:

num += 1 

は、実行時にこのほぼ同等何かのためだけの省略形です。

REGISTER = num   # read memory location into a local register 
REGISTER = REGISTER + 1 # increment the value 
num = REGISTER   # store back to memory 

そして、いずれかのスレッドが別のスレッドによって割り込ま得ることができ、異なるコア上でスケジュールを取得、または印刷コール自体が奇妙なタイミングの問題を導入する可能性以来。すべてのキャッシュコヒーレンシには複数のコアがあります。実行時にこれが起こっている可能性があります。

THREAD 1: 
     REGISTER = num   # T1 reads 0 into register 

<context switch> 

THREAD 2: 
    REGISTER = num   #T2 reads "0" into register 
    REGISTER = REGISTER + 1 #T2 increments register to "1" 
    num = REGISTER   #T2 copies register value back to memory 

<context switch back to thread 1, REGISTER is restored to "0" from before> 
<but the memory location for num is now 1> 

THREAD 1: 
    REGISTER = REGISTER + 1 #T1 increments register to "1" 
    num = REGISTER   #T1 copy register value ("1") back to memory 

上記のように、2つのスレッドが変数に重複してアクセスすることは非常に簡単です。

あなたが5に簡単に更新インクリメント取得numの一貫性のある動作をする場合は、ロックを必要とする:

lock = Lock() 

class meThread(threading.Thread): 
    def run(self): 
     global num 
     global lock 
     time.sleep(random.randint(1,3)) 
     # -------------------------------- 
     lock.acquire() 
     num += 1 
     tmp = num   # save value a local tmp value for the subsequent print 
     lock.release() 
     # -------------------------------- 
     print(self.name+'set num to '+str(tmp)) # print the value of what num was incremented to while in the lock, not what num is now 

をあなたが知る必要があるすべてはhereです。

+0

すばらしい答え!それは私が考えていることです。しかし、3.6では、numはロックなしでも一貫して5に増えます。 – Bruce

+0

他の何も実行していない小さな独立したコンソールアプリケーションの特定の構成では、決定論的であるように見えるかもしれません。しかし、実行中の他のアプリケーションからのさまざまな負荷で、異なる数のCPUコアを持つ別のコンピュータで動作の違いを見ることができます。あるいは、Pythonの次の反復では、バイトコードの生成方法を微妙に最適化して、これを再び変更します。それは設計によって決定的ではありません。 – selbie

+0

実際にPython 2とPython 3で違った働きをする深刻な技術的理由があるかもしれませんが(例えば、さまざまなコンパイラの最適化、さまざまな変数キャッシングスキーム)、これは言語に関する文書化された動作ではありません。また、Python開発チームは、これも変更できないという保証は一切しません。 – selbie

関連する問題