概要
前のセクションでは、mysql.connector の概要、mysql.connector の機能、mysql.connector の使用方法など、Python で MySQL を使用する方法を紹介しました。このセクションでは、Python でマルチスレッドを使用する方法を紹介します。マルチスレッドとは、複数のスレッドを同時に実行し、各スレッドが異なるタスクを個別に実行するプログラムを指します。今日のコンピューター サイエンスの分野では、マルチスレッド テクノロジは、高い同時実行性やパフォーマンスの最適化などの問題を解決する重要な手段となっています。Python は、組み込みのスレッド モジュールを通じて強力なマルチスレッド サポートを提供します。実際のアプリケーションでは、マルチスレッドを合理的に使用することで、プログラムの実行効率を向上させ、並列コンピューティングを実現し、リソースの使用率とユーザー エクスペリエンスを最適化することができます。
Python のスレッド モジュールを使用すると、スレッドを作成および管理できます。スレッドはプロセスの基本的な実行単位であり、プロセス内で並行して実行されます。Python では、グローバル インタープリター ロック (GIL) が存在するため、マルチスレッドによって CPU 集中型のタスクの実行速度が向上しない可能性があります。ただし、ネットワーク リクエスト、ファイルの読み取りと書き込みなど、IO 集中型のタスクでは、マルチスレッドを使用すると、プログラムの実行効率が大幅に向上します。
スレッドモジュール
Python のスレッド モジュールは、スレッド サポートを提供するために使用されます。スレッド モジュールで一般的に使用される関数とクラスを次に示します。
threading.Thread(target, name, args, kwargs) : スレッドを作成する主なメソッド。target は実行される関数、name はスレッドの名前、args と kwargs は関数に渡されるパラメータです。
threading.current_thread() : 現在のスレッド オブジェクトを返します。
threading.enumerate() : 現在アクティブなすべての Thread オブジェクトのリストを返します。
threading.active_count() : 現在アクティブな Thread オブジェクトの数を返します。
threading.Lock() : スレッド ロック。複数のスレッドが特定のリソースに同時にアクセスしてデータの混乱を引き起こすのを防ぐために使用されます。
threading.RLock() : 再入可能なスレッド ロック。スレッドが既にロックを保持している間に同じロックを再度取得できるようにします。
threading.Event() : スレッド間の通信用のイベント オブジェクトを作成するために使用されます。
threading.Condition() : 条件変数。特定の条件が満たされるまでスレッドを待機させるために使用されます。
threading.Semaphore() : 特定のリソースに同時にアクセスするスレッドの数を制限するために使用されるセマフォ。
threading.BoundedSemaphore() : 有界セマフォであり、セマフォとは異なり、セマフォの上限を制限します。
threading.Timer(interval, function, args, kwargs) : 指定された時間間隔の後に操作を実行します。
threading.local() : スレッドローカル データ オブジェクトを作成します。各スレッドはデータの独自のコピーを持ちます。
スレッドを使用する
Python のスレッド モジュールでは、Thread クラスはスレッドを作成するために使用されるオブジェクトです。基本的な Thread オブジェクトは、以下のサンプルコードを参照して作成できます。
import time
import threading
def print_numbers():
for i in range(5):
time.sleep(1)
print('number is:', i)
# 创建线程
t = threading.Thread(target = print_numbers)
# 启动线程
t.start()
# 等待线程结束
t.join()
上記のコード例では、関数 print_numbers() を定義し、新しい Thread オブジェクトを作成します (ターゲット関数は print_numbers() です)。t.start() を呼び出すとこのスレッドが開始され、関数が新しいスレッドで自動的に実行されます。t.join() を呼び出すと、スレッドの実行が完了するまで待機します。この例では、print_numbers() 関数の for ループの実行が完了するまで待機します。
もちろん、複数のスレッドを同時に作成することもできます。以下のサンプル コードを参照してください。
import time
import threading
def print_numbers():
name = threading.current_thread().name
for i in range(5):
time.sleep(1)
print('%s, number is: %d'%(name, i))
# 创建线程
t1 = threading.Thread(target = print_numbers, name = 'thread 1')
t2 = threading.Thread(target = print_numbers, name = 'thread 2')
# 启动线程
t1.start()
t2.start()
# 等待线程结束
t1.join()
t2.join()
ご覧のとおり、上記のコード例を実行すると、スレッド 1 とスレッド 2 が交互に情報を出力します。出力は次のとおりです。
thread 2, number is: 0
thread 1, number is: 0
thread 1, number is: 1
thread 2, number is: 1
thread 1, number is: 2
thread 2, number is: 2
thread 1, number is: 3
thread 2, number is: 3
thread 1, number is: 4
thread 2, number is: 4
threading.Thread に加えて、threading.Timer を使用して、指定されたタスクをスレッドで実行することもできます。threading.Timer は主に、指定された時間間隔の後に操作を実行するタイマーを作成するために使用されます。threading.Timer は新しいスレッドで実行されることに注意してください。関数に共有データとリソースの変更が含まれる場合は、同時実行の問題を回避するために適切な同期メカニズムを使用する必要がある場合があります。
import threading
def print_msg():
print("Hello CSDN")
# 创建定时器,3秒后执行print_msg函数
timer = threading.Timer(3, print_msg)
# 开始计时器
timer.start()
上記のコード例では、プログラムは 3 秒待ってから、「Hello CSDN」という文字列を出力します。
カスタムスレッドを作成する
Python では、threading.Thread クラスを継承してカスタム スレッド クラスを作成できます。カスタム スレッド クラスでは、通常、run() 関数などの一部の関数がオーバーライドされ、デフォルトの動作を変更します。
import threading
# 自定义线程类
class MyThread(threading.Thread):
def __init__(self, data):
threading.Thread.__init__(self)
self.data = data
def run(self):
# 输出:Hello CSDN
print(f"Hello {self.data}")
# 创建自定义线程类的对象
my_thread = MyThread('CSDN')
# 启动线程
my_thread.start()
# 等待线程结束
my_thread.join()
上記のサンプル コードでは、MyThread という新しいクラスを作成し、threading.Thread を継承しました。MyThread クラスの init 関数では、最初に親クラス threading.Thread の init 関数が呼び出されて初期化され、次に data という名前の属性が設定されます。my_thread.start() が呼び出されたときに、親クラスの代わりにカスタム run() 関数が実行されるように、run() 関数を書き直しました。
以下は、カスタム スレッド クラスの作成と使用のより複雑な例です。
import time
import threading
class MyThread2(threading.Thread):
def __init__(self, thread_id, name):
threading.Thread.__init__(self)
self.thread_id = thread_id
self.name = name
def run(self):
print(f"start thread: {self.name}")
for i in range(5):
time.sleep(1)
print(f"thread {self.name} is running")
print(f"exit sub thread: {self.name}")
# 创建线程对象
threads = []
for i in range(3):
thread = MyThread2(i, f"Thread-{i}")
thread.start()
threads.append(thread)
# 等待所有线程完成
for t in threads:
t.join()
print('exit main thread')
上記のコード例では、MyThread2 クラスにはスレッド ID と名前をパラメータとして受け取り、親クラス threading.Thread のコンストラクタを内部で呼び出すコンストラクタがあります。run() 関数は、スレッドに実行させたいタスクを実行するように書き換えられます。つまり、メッセージを出力し、1 秒間スリープし、別のメッセージを出力し、これを 5 回繰り返します。最後に、3 つのスレッドを作成して開始し、すべてのスレッドがタスクを完了するまで待ちます。
スレッドの同期
マルチスレッド プログラミングでは、データの不整合や競合状態など、潜在的な問題が発生する可能性があります。これらの問題を解決するには、スレッド同期技術を使用する必要があります。スレッド同期は、複数のスレッドの実行を調整して、リソースを共有したり、正確かつ効率的に連携したりするためのメカニズムです。Python は、ロック、イベント、条件、セマフォなど、いくつかのスレッド同期メカニズムを提供します。以下、個別に紹介していきます。
1. ロックは、最も基本的なスレッド同期メカニズムです。Python では、threading.Lock クラスを使用してこれを実現できます。ロックには、ロックとロック解除の 2 つの状態があります。1 つのスレッドがロックを取得すると、ロックを取得しようとする他のスレッドは、ロックが解放されるまでブロックされます。
import threading
lock = threading.Lock()
def thread_func():
with lock:
# 线程安全的代码块
pass
def thread_func2():
lock.acquire()
# 线程安全的代码块
lock.release()
上記のコード例では、thread_func() 関数は with lock を使用して共有リソースをロックし、thread_func() 関数は lock.acquire() および lock.release() を使用して共有リソースをロックします。
マルチスレッドでロックを使用するサンプルコードを見てみましょう。
import time
import threading
class Counter:
def __init__(self):
self.count = 0
self.lock = threading.Lock()
def increment(self):
with self.lock:
self.count += 1
print(f"Count: {self.count}")
def worker(counter):
for _ in range(10):
counter.increment()
counter = Counter()
threads = []
for _ in range(3):
t = threading.Thread(target = worker, args = (counter,))
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"Final Count: {counter.count}")
上記のサンプル コードでは、カウンターとロックを含む Counter クラスを定義しました。インクリメント関数は、カウンターをインクリメントする前にロックを取得し、完了時にロックを解放します。これにより、常に 1 つのスレッドだけがカウンターを変更できるようになります。次に、3 つのスレッドを作成し、それぞれがカウンターを 10 回インクリメントしようとしました。ロックがあるため、すべての増分は正しくシリアル化され、最終的なカウントは常に 30 になります。
2. イベントはスレッド間の通信に使用されます。threading.Event クラスは、スレッドが設定できるシグナル フラグを提供し、他のスレッドはこのフラグが設定されるのを待つことができます。
import threading
event = threading.Event()
def thread_func():
# 阻塞线程,直到事件被设置
event.wait()
# 当事件被设置后执行的代码
print('event waited')
event.set()
thread_func()
上記のサンプル コードでは、event.wait() 関数が呼び出されたときにブロックされますが、別の場所でevent.set() を呼び出してイベント シグナルを設定した場合にのみ、ブロックが解除され、コードは実行を継続します。
3. 条件 (Condition) は、より複雑なスレッド同期の問題に使用されます。threading.Condition クラスは、特定の条件が満たされるまで待機する方法を提供し、通常はロックとともに使用されます。
import time
import threading
class SharedData:
def __init__(self):
self.lock = threading.Lock()
self.condition = threading.Condition(self.lock)
self.value = 0
def increment(self):
while True:
self.condition.acquire()
while self.value >= 10:
self.condition.wait()
self.value += 1
print(f"Value increased to {self.value}")
self.condition.notify_all()
self.condition.release()
time.sleep(1)
def decrement(self):
while True:
self.condition.acquire()
while self.value <= 0:
self.condition.wait()
self.value -= 1
print(f"Value decreased to {self.value}")
sleep_time = 2 if self.value <= 5 else 0.5
self.condition.notify_all()
self.condition.release()
time.sleep(sleep_time)
sd = SharedData()
thread1 = threading.Thread(target = sd.increment)
thread2 = threading.Thread(target = sd.decrement)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
上記のサンプル コードでは、SharedData クラスに value 属性と Condition オブジェクトがあります。インクリメント メソッドとデクリメント メソッドは両方とも、Condition を使用して、値が常に 0 ~ 10 の間にあることを保証します。値がこの範囲を超える場合、現在のスレッドは wait メソッドを呼び出し、自分自身を待機キューに入れ、ロックを解放して他のスレッドに実行の機会を与えます。値が有効な範囲に戻ると、スレッドはnotify_allメソッドを呼び出して、待機キュー内のすべてのスレッドを起動します。
4. セマフォはリソースへのアクセスを制限するために使用されます。threading.Semaphore クラスは、リソースに同時にアクセスできるスレッドの数を制御するカウンターを提供します。
import time
import threading
# 创建一个Semaphore,最大允许3个线程同时访问共享资源
semaphore = threading.Semaphore(3)
def MyWorker():
# 获取Semaphore
semaphore.acquire()
# 访问共享资源的代码
for i in range(6):
print("MyWorker {} is working: {}".format(threading.current_thread().name, i))
time.sleep(1)
# 释放Semaphore
semaphore.release()
# 创建5个线程
threads = []
for i in range(5):
t = threading.Thread(target = MyWorker, name = str(i))
t.start()
threads.append(t)
# 等待所有线程完成
for t in threads:
t.join()
上記のサンプル コードでは、最大 3 つのスレッドが同時に共有リソースにアクセスできるようにするセマフォを作成しました。各スレッドは、共有リソースにアクセスする前にセマフォを取得し、完了するとセマフォを解放します。このようにして、常に最大 3 つのスレッドのみが共有リソースにアクセスするようにすることができます。