Raspberry Pi 3 & Python 開発ブログ☆彡

Raspberry Pi 3の使い方、設定をわかりやすく解説。Raspberry Pi3 Model B(Element14版)、Raspbian 8.0(NOOBS Ver1.9.2)を使用して開発中。

【スポンサーリンク】

Pythonプログラム入門(スレッド 排他制御)

【スポンサーリンク】

前回に引き続き、スレッドについて記載しようと思います。スレッドを使うとき、異なるスレッドから同タイミングで同じ変数にアクセスすると予期せぬ動作をすることがあります。これを防ぐ方法が排他制御です。

 

簡単に説明すると、フラグを上げている間は他のスレッドからアクセスを禁止します。フラグを下げると他のスレッドからアクセス可能になります。これにより、異なるスレッドから同じタイミングで同じ変数にアクセスすることを防ぐことができます。

 

注意しなければいけないことは、フラグを上げている状態が長いと、その期間は順次処理となりますので、処理時間が長くなります。ですので、排他制御を行う部分は最小限にしましょう。排他制御をしている方が安全だからといって、大きな範囲で排他制御を行ってしまっては並列処理をしている意味がありません。

 

複数スレッドでも変数を読み込む時、排他制御を使う必要はありません。変数に値を書き込む時だけ、排他制御が必要です。また、当たり前ですが、ひとつのスレッドでしか使っていない変数に排他制御を使う必要はありません。

 

それでは動作確認のサンプルコードを見てみましょう。まずは、排他制御をしない場合です。

# -*- 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
        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