【Python】マルチスレッドによる並列処理の実装

Pythonでマルチスレッドによる並列処理を実装する方法について解説します。通常、プログラムでは逐次処理といって先に書かれたものから順番に1つずつ実行していきますが、マルチスレッドを使えば複数の処理を同時に進めることができます。

目次

スレッドとは

スレッドとはプログラムの最小の実行単位です。

通常、プログラムでは特に記述がない限り逐次処理といって、先に書かれたものから順番に実行していき、処理が終わり次第次の処理に取り掛かる…といった具合で処理を実行していきます。

例えば次のカレーライスを作るプログラムは普通にPythonで記述したものですが、マルチスレッドでの並列処理を命じていないためシングルスレッドで処理が1つずつ順番に実行されていきます。

import time

def cook_rice():
    print("ご飯を炊きます")
    time.sleep(3)
    print("ご飯が炊けました")

def make_curry():
  print("カレーを作ります")
  time.sleep(2)
  print("カレーができました")

# 実行部分
print("調理開始")
cook_rice()
make_curry()
print("盛り付けます")
print("いただきます")

これでもカレーライスを作ることはできますが、ご飯が炊きあがってからカレーを作り始めてしまうため時間がかかってしまいます。典型的な料理下手の人です。

スレッドを2つ使って並列処理で実行すればご飯を炊いている間にカレーを作ることができるので、もっと効率的にカレーを作ることができますよね。

threadingモジュールによる並列処理

それでは実際に並列処理を使って先ほどのプログラムを効率化してみましょう。

Pythonの標準ライブラリに用意されているthreadingモジュールを使えば簡単に並列処理を実現することができます。

import time
import threading

def cook_rice():
    print("ご飯を炊きます")
    time.sleep(3)
    print("ご飯が炊けました")

def make_curry():
  print("カレーを作ります")
  time.sleep(2)
  print("カレーができました")

# 実行部分
print("調理開始")
thread1 = threading.Thread(target=cook_rice)
thread2 = threading.Thread(target=make_curry)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print("盛り付けます")
print("いただきます")

threadingモジュールのthreading.Threadクラスを使い、引数に関数を指定してthread1、thread2というインスタンスをそれぞれ生成。

生成したインスタンスのstartメソッドを呼び出せば別のスレッド上で処理が行われるため、すぐに次の処理を実行することができます。

joinメソッドはその処理が終わるまで、次の処理の実行を待ち状態にする命令です。まだご飯が炊きあがっていないのにカレーを盛り付け始めると困るので、ここで一旦両方の処理が完了するのを待って、盛り付けを開始しています。

Tkinterで並列処理をしてみよう

さきほどの例ではtime.sleepを使って意図的に処理に時間をかけていましたが、実際そこまで処理が大量でなかったり、多少時間がかかっても問題ない場合は逐次処理でもさほど気になりません。

しかしTkinterを使ってみると、単に処理を効率化するためだけでなく、同時に処理を進行するために並列処理を使いたい場面が出てきます。

例えば、次のプログラムを実行してみてください。

※実行ファイルと同じ階層にbutton.mp3という1秒以上の音声ファイルを用意する必要があります。

import tkinter as tk
from playsound import playsound # playsoundモジュールからplaysoundメソッドをインポート

 # tk.Frameを継承したApplicationクラスを作成
class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)

        # ウィンドウの設定
        self.master.title("ウィンドウのタイトル")

        # 実行内容
        self.pack()
        self.create_widget()

    # create_widgetメソッドを定義
    def create_widget(self):

        # label1ウィジェット
        self.label1 = tk.Label(self.master, text="「スイッチ」を押させるなーーーーッ", font=("MSゴシック", "20", "bold"))
        self.label1.pack()

        # button1ウィジェット
        self.button1_text = tk.StringVar()
        self.button1_text.set("いいや!限界だ押すね!")

        self.button1 = tk.Button(self.master, textvariable=self.button1_text, font=("MSゴシック", "20", "bold"), command=self.button1_clicked)
        self.button1.pack()

    # button1をクリックした時の処理
    def button1_clicked(self):
        playsound("button.mp3") # button.mp3を再生
        self.button1_text.set("びろ~~~ん") # button1_textの中身を変更

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master=root)
    app.mainloop()

これ自体は 【Tkinter】ボタン(Button)の使い方 で解説している、ボタンを押すとボタンに書かれている文字列が変化するアプリケーションなのですが、ボタンをクリックした際に効果音が鳴る処理を書き加えています。

実行してみるとわかるのですが、ピコッという音が鳴ったところから鳴り終わるまでの間、アプリが固まってしまいます。

逐次処理では『音声ファイルを実行する』という処理が終わるまで次の処理に移らないため、その次に書かれている『ボタンの文字列を変更する』という処理が待ち状態になって実行されていないためです。

また、mainloopの処理も止まってしまっているため、音が鳴っている間にアプリのウィンドウを移動しようとしたり、アプリをXボタンで終了しようとしても固まったまま、音が鳴り終わるまで反応しません。

この問題は次のように音声ファイルの実行を別のスレッドで処理してあげることで解決できます。

import tkinter as tk
from playsound import playsound
import threading

 # tk.Frameを継承したApplicationクラスを作成
class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)

        # ウィンドウの設定
        self.master.title("ウィンドウのタイトル")

        # 実行内容
        self.pack()
        self.create_widget()

    # create_widgetメソッドを定義
    def create_widget(self):

        # label1ウィジェット
        self.label1 = tk.Label(self.master, text="「スイッチ」を押させるなーーーーッ", font=("MSゴシック", "20", "bold"))
        self.label1.pack()

        # button1ウィジェット
        self.button1_text = tk.StringVar()
        self.button1_text.set("いいや!限界だ押すね!")

        self.button1 = tk.Button(self.master, textvariable=self.button1_text, font=("MSゴシック", "20", "bold"), command=self.button1_clicked)
        self.button1.pack()

    # button1をクリックした時の処理
    def button1_clicked(self):
        thread1 = threading.Thread(target=self.play_button_sound)
        thread1.start()
        self.button1_text.set("びろ~~~ん")

    # 音声ファイルの実行
    def play_button_sound(self):
        playsound("button.mp3")

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master=root)
    app.mainloop()

音声ファイルを実行する処理として play_button_sound メソッドを定義し、thread1インスタンスを作成する際の引数として指定。

thread1インスタンスのstartメソッドで実行することで別スレッドで処理させ、音声が鳴っている間も他の処理が滞らないようにしています。

このエントリーをはてなブックマークに追加

コメントを残す

頂いたコメントは一読した後表示させて頂いております。
反映まで数日かかる場合もございますがご了承下さい。