【ITニュース解説】What is threading in Python?
2025年09月15日に「Dev.to」が公開したITニュース「What is threading in Python?」について初心者にもわかりやすく解説しています。
ITニュース概要
Pythonのthreadingは、1つのプログラム内で複数の処理を並行して実行する技術だ。これにより、ウェブサーバーでの複数リクエスト対応やバックグラウンド処理が可能になり、プログラムの応答性も向上する。threading.local()でスレッド固有のデータを安全に管理できる。
ITニュース解説
システムエンジニアを目指す上で、プログラムがどのように動作し、複数の処理を効率的にこなしているのかを理解することは非常に重要だ。その中でも「スレッディング」は、一つのプログラムが複数の仕事を同時に進めるための基本的な考え方の一つとして挙げられる。
スレッディングとは、簡単に言えば、一つのプログラム(これを「プロセス」と呼ぶ)の中で、同時に複数の「実行の筋道」(これを「スレッド」と呼ぶ)を走らせる技術のことだ。スレッドは、プログラムが独立して実行できる最も小さな単位であり、これによりプログラムは複数のタスクを並行して処理できるようになる。
なぜこのような技術が必要なのだろうか。具体的な利用シーンをいくつか見てみよう。例えば、ウェブサーバーは、世界中の何十、何百、何千というユーザーからのリクエストを同時に受け付ける必要がある。もし一つのリクエストを処理している間、他のリクエストがすべて待たされてしまうと、ユーザー体験は著しく悪くなるだろう。スレッディングを使うことで、ウェブサーバーはそれぞれのユーザーからのリクエストを異なるスレッドで処理し、多くのユーザーに同時にサービスを提供できる。
また、プログラムの「応答性」を高めるためにもスレッディングは役立つ。例えば、ユーザーがボタンをクリックしたときに、その裏で時間のかかるデータ処理やファイル保存が行われるとする。もしこの処理がメインのスレッドで行われると、処理が終わるまで画面がフリーズしたように見え、ユーザーは何も操作できなくなる可能性がある。しかし、この時間のかかる処理を別のスレッドに任せることで、メインのスレッドはユーザーインターフェースの更新や他の操作を受け付け続け、プログラムは「フリーズした」ように見えなくなるのだ。他にも、メールの送信やログの記録、データのバックグラウンド処理など、ユーザーが直接結果を待つ必要のないタスクを別のスレッドで動かすことで、プログラム全体の効率と応答性を向上させることができる。
Pythonでスレッディングを扱うのは比較的簡単だ。threadingモジュールを使うことで、新しいスレッドを作成し、実行することができる。例えば、次のようなコードを考えてみよう。
1import threading 2import time 3 4def worker(name): 5 print(f"{name} started") 6 time.sleep(2) 7 print(f"{name} finished") 8 9t1 = threading.Thread(target=worker, args=("Thread-1",)) 10t2 = threading.Thread(target=worker, args=("Thread-2",)) 11 12t1.start() 13t2.start()
このコードでは、workerという関数が定義されている。この関数は、引数で受け取った名前を表示し、2秒間一時停止してから、終了メッセージを表示するというシンプルな処理を行う。threading.Threadを使って、このworker関数を実行する二つのスレッドt1とt2を作成している。target引数には実行したい関数を、args引数にはその関数に渡す引数をタプルの形で指定する。そして、t1.start()とt2.start()を呼び出すと、それぞれのスレッドがほぼ同時に実行を開始する。これにより、「Thread-1 started」と「Thread-2 started」がほぼ同時に表示され、それぞれが2秒間の待機に入り、2秒後にそれぞれ「finished」メッセージが表示される。もしスレッディングを使わずにこれらの処理を順番に実行すると、合計で4秒かかるところが、スレッディングを使うことで、合計でおよそ2秒で完了するのだ。これは、二つのタスクが「同時に」(正確には、非常に短い時間で切り替わりながら)実行されていることを示している。
特にDjangoのようなウェブフレームワークや、GunicornやUvicornといったウェブサーバーでは、スレッディングが不可欠な役割を果たす。これらのシステムは、同時に多数のユーザーからのリクエストを処理する必要があるため、各リクエストを個別のスレッドで処理することが一般的だ。これにより、一つのリクエストが完了するのを待たずに、次のリクエストの処理を開始できる。
ここで一つ重要な問題が出てくる。もし複数のスレッドが同時に実行されているとして、それぞれのスレッドが独自の情報を持ちたい場合、どのようにすれば良いのだろうか?例えば、ウェブサーバーが「どのユーザーからのリクエストなのか」や「どのウェブサイト(ドメイン)に対するリクエストなのか」といった情報を、そのリクエストを処理しているスレッドだけで保持したいと考える。もしこれらの情報がすべてのスレッドで共有されてしまうと、あるスレッドが処理している情報が別のスレッドの情報と混ざってしまい、誤った結果を返す可能性がある。
このような問題を解決するために、「スレッドローカルストレージ」という仕組みが提供されている。Pythonではthreading.local()を使ってこれを利用できる。threading.local()は、スレッドローカルストレージオブジェクトを作成する。このオブジェクトの属性に値を保存すると、その値はそのスレッドからしかアクセスできなくなる。つまり、各スレッドは他のスレッドに影響を与えることなく、自分自身の属性(変数)を保持できるのだ。
例えば、_thread_local = threading.local()と定義した場合、_thread_localはスレッドローカルなデータストアとなる。Pythonにおける変数名の先頭に付くアンダースコア_は、プログラミング上の慣習として「これは内部的な用途で使われるべきプライベートな変数である」ということを示すもので、プログラムの外部から直接アクセスすることを推奨しないという意味合いがある。
実際にthreading.local()を使った例を見てみよう。
1import threading 2 3local_data = threading.local() 4 5def worker(name): 6 local_data.value = name 7 print(f"In {name}, local_data.value = {local_data.value}") 8 9t1 = threading.Thread(target=worker, args=("Thread-1",)) 10t2 = threading.Thread(target=worker, args=("Thread-2",)) 11 12t1.start() 13t2.start()
このコードでは、local_data = threading.local()という行でスレッドローカルなオブジェクトが作成されている。worker関数の中では、local_data.valueにスレッドの名前("Thread-1"や"Thread-2")を割り当てている。そして、その値を表示している。ここで注目すべきは、t1が実行するworker関数内でlocal_data.valueに「Thread-1」が設定され、t2が実行するworker関数内でlocal_data.valueに「Thread-2」が設定されるが、これらは互いに干渉しないということだ。それぞれのスレッドは、local_data.valueという同じ名前の属性を持っているにもかかわらず、その値はそれぞれのスレッドに固有のものとなる。結果として、「In Thread-1, local_data.value = Thread-1」と「In Thread-2, local_data.value = Thread-2」のように、それぞれのスレッドが自分自身の値を正しく保持していることが確認できる。
このように、threading.local()を使うことで、ウェブサーバーのリクエスト処理のような場面で、特定のリクエストに関するデータをそのリクエストを処理しているスレッドに「紐付けて」保存することが可能になる。これは、まるでリクエストごとに独立したグローバル変数があるかのように振る舞うため、非常に便利で安全なデータ管理方法と言える。例えば、_thread_local.current_websiteのようにして、現在処理しているウェブサイトのドメイン名を各スレッドが個別に保持する、といった使い方ができるのだ。
まとめると、スレッディングとは、一つのプログラム内で複数のタスクを並行して実行することで、プログラムの効率や応答性を高める技術だ。そして、threading.local()は、複数のスレッドが互いに干渉せずに、それぞれ固有のデータを安全に保持するための強力なツールである。これらの概念を理解することは、複雑なシステムを構築する上で非常に役立つだろう。