前回に引き続き、スレッドについて記載しようと思います。スレッドを使うとき、異なるスレッドから同タイミングで同じ変数にアクセスすると予期せぬ動作をすることがあります。これを防ぐ方法が排他制御です。
簡単に説明すると、フラグを上げている間は他のスレッドからアクセスを禁止します。フラグを下げると他のスレッドからアクセス可能になります。これにより、異なるスレッドから同じタイミングで同じ変数にアクセスすることを防ぐことができます。
注意しなければいけないことは、フラグを上げている状態が長いと、その期間は順次処理となりますので、処理時間が長くなります。ですので、排他制御を行う部分は最小限にしましょう。排他制御をしている方が安全だからといって、大きな範囲で排他制御を行ってしまっては並列処理をしている意味がありません。
複数スレッドでも変数を読み込む時、排他制御を使う必要はありません。変数に値を書き込む時だけ、排他制御が必要です。また、当たり前ですが、ひとつのスレッドでしか使っていない変数に排他制御を使う必要はありません。
それでは動作確認のサンプルコードを見てみましょう。まずは、排他制御をしない場合です。
# -*- coding: utf-8 -*-
import threading, time,datetime
g_cnt = 0
g_lock = threading.Lock()
class MyThread(threading.Thread):
def __init__(self,name):
threading.Thread.__init__(self)
self.name = name
def run(self):
global g_cnt
# read
print(self.name + ':value -> ' + str(g_cnt)) + ' :readtime -> ' + str(datetime.datetime.now())
time.sleep(5)
# Lock
#g_lock.acquire()
# write
g_cnt = g_cnt + 1
print(self.name + ':value -> ' + str(g_cnt)) + ' :writetime -> ' + str(datetime.datetime.now())
time.sleep(1)
print(self.name + ':value -> ' + str(g_cnt)) + ' :printtime -> ' + str(datetime.datetime.now())
# unLock
#g_lock.release()
thread1 = MyThread('thread1')
thread2 = MyThread('thread2')
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print('Result' + ':value -> ' + str(g_cnt)) + ' :time -> ' + str(datetime.datetime.now())
この実行結果は以下のようになります。thread1とthread2が交互に処理されています。
thread1:value -> 0 :readtime -> 2016-09-05 09:39:54.525777
thread2:value -> 0 :readtime -> 2016-09-05 09:39:54.526775
thread1:value -> 1 :writetime -> 2016-09-05 09:39:59.531561
thread2:value -> 2 :writetime -> 2016-09-05 09:39:59.531826
thread1:value -> 2 :printtime -> 2016-09-05 09:40:00.532891
thread2:value -> 2 :printtime -> 2016-09-05 09:40:00.533543
Result:value -> 2 :time -> 2016-09-05 09:40:00.534009
大抵の場合は、writeしてその値をそのままprintしたいはずです。writeからprintまでの処理を排他制御してみましょう。上記コードの#g_lock.acquire() と #g_lock.release()のコメントを外して(#を削除する)、実行してみてください。
実行結果は以下のようになります。thread1のwriteとprint処理が終わった後に、thread2の処理が実行されています。これが排他制御の動作となります。
thread1:value -> 0 :readtime -> 2016-09-05 09:44:55.417641
thread2:value -> 0 :readtime -> 2016-09-05 09:44:55.418573
thread1:value -> 1 :writetime -> 2016-09-05 09:45:00.423330
thread1:value -> 1 :printtime -> 2016-09-05 09:45:01.424652
thread2:value -> 2 :writetime -> 2016-09-05 09:45:01.425119
thread2:value -> 2 :printtime -> 2016-09-05 09:45:02.426380
Result:value -> 2 :time -> 2016-09-05 09:45:02.426945