【Python 中級編】マルチスレッドを使って速度改善をしよう

何の記事??

python で並行処理を行う方法を紹介します。
マルチスレッドの方法の一つである threading モジュールを活用する方法を紹介します。

あまり応用の難しいところは触れずに基礎的な部分のみ説明します。


threading モジュール

threading モジュールには以下のオブジェクトが含まれます。

– Thread オブジェクト : いわゆるスレッドのオブジェクト
– Lock オブジェクト : スレッドのロックを管理するオブジェクト
– Condition オブジェクト : ロックの獲得や解放を行うオブジェクト
– Semaphore オブジェクト : セマフォの獲得や解放を行うオブジェクト
– Event オブジェクト : スレッド間で通信を行うための内部フラグを管理するオブジェクト
– Timer オブジェクト : 一定時間後に実行される活動を表すオブジェクト
– Barrier オブジェクト : バリアを管理するオブジェクト

特に

Thrading オブジェクト、Lock オブジェクト

の説明をメインに行なっていきます。

 

Thread オブジェクト

Thread オブジェクトを生成するには、Thread クラスをインスタンス化するか Thread クラスのサブクラスを宣言してインスタンス化します。
まずは以下のコードで Thread クラスをインスタンス化して、マルチスレッドの挙動を確認してみましょう。

 

import threading

def function(number):
    for i in range(5):
        print(str(number + 1) + '番目のスレッド : ' + str(i))

def main():
    for i in range(3):
        threadTest = threading.Thread(target=function, args=(i,))
        threadTest.start()
    threadTest.join()

if __name__ == "__main__":
    main()

コードの説明です。

一つ目の def 文でスレッド処理の実態となる関数の定義を行います。要はスレッドの中で行われる処理ですね。

二つ目の main の中で Thread クラスのインスタンス化を行います。
この際、引数にスレッド処理の実態となる関数、関数に渡す値を指定します。
Thread オブジェクトを生成したら start メソッドを呼び出してメソッドを開始します。
for ループが終了した際の join メソッドは生成したスレッドの終了を待つためのメソッドです。

このコードの実行例はこちらです。マルチスレッドなので毎回同じ結果にはなりません。

1番目のスレッド : 0
1番目のスレッド : 1
2番目のスレッド : 0
1番目のスレッド : 2
2番目のスレッド : 1
1番目のスレッド : 3
2番目のスレッド : 2
1番目のスレッド : 4
2番目のスレッド : 3
3番目のスレッド : 0
2番目のスレッド : 4
3番目のスレッド : 1
3番目のスレッド : 2
3番目のスレッド : 3
3番目のスレッド : 4

それでは続いてサブクラスで実装する方法を見ていきましょう。

import threading

class TestThread(threading.Thread):
    def __init__(self, number):
        super(TestThread, self).__init__()
        self.number = number

    def run(self):
        for i in range(5):
            print(str(self.number + 1) + '番のスレッド : ' + str(i))

def main():
    for i in range(3):
        threadTest = TestThread(i)
        threadTest.start()
    threadTest.join()

if __name__ == "__main__":
    main()

実行結果例は以下です。

1番のスレッド : 0
1番のスレッド : 1
2番のスレッド : 0
1番のスレッド : 2
2番のスレッド : 1
1番のスレッド : 3
2番のスレッド : 2
1番のスレッド : 4
2番のスレッド : 3
3番のスレッド : 0
2番のスレッド : 4
3番のスレッド : 1
3番のスレッド : 2
3番のスレッド : 3
3番のスレッド : 4

まずは、スレッドとなるサブクラス TestThread を定義しましょう。

サブクラス定義の中では、必ず super クラスの __init__() を呼ぶのを忘れないようにしてください。

スレッドで行う処理については run メソッドをオーバーライドして定義します。
join メソッド等は、先ほどの説明と同じです。


Lock オブジェクト

複数のスレッドで同期をとる場合は、Lock オブジェクトを使用します。
ロックには「ロック」と「アンロック」の状態があり、acquire メソッドを呼び出すとロックし、release メソッドでアンロック状態に戻ります。

既にロック状態の場合、acquire メソッドは他のスレッドが release メソッドを呼び出してアンロック状態になるまでブロックします。

それでは Lock オブジェクトを利用して各スレッドを個別に実行してみましょう。

 

import threading

def function(lockTest, number):
    lockTest.acquire()
    for i in range(5):
        print(str(number + 1) + '番のスレッド : ' + str(i))
    lockTest.release()

def main():
    lockTest = threading.Lock()
    for i in range(3):
        threadTest = threading.Thread(target=function, args=(lockTest, i))
        threadTest.start()
    threadTest.join()

if __name__ == "__main__":
    main()

一つ目の def 文にある acquire メソッドでロックを獲得します。
獲得できない場合、獲得できるまでブロックを行います。

main では Lock オブジェクトを生成しています。
実行結果は以下の通りです。

1番のスレッド : 0
1番のスレッド : 1
1番のスレッド : 2
1番のスレッド : 3
1番のスレッド : 4
2番のスレッド : 0
2番のスレッド : 1
2番のスレッド : 2
2番のスレッド : 3
2番のスレッド : 4
3番のスレッド : 0
3番のスレッド : 1
3番のスレッド : 2
3番のスレッド : 3
3番のスレッド : 4

確かに実行中のスレッドの終了を待ってから別のスレッドがスタートしていますね。

最後に

web アプリやバッチなんかで大きなデータを扱う時にマルチスレッドは必須ですね。

いまいち理解しきっていない箇所もあるので、間違いなどあったらご指摘ください!!