スレッドの同期は、マルチスレッド環境でデータの整合性を保ち、競合状態を防ぐために非常に重要です。同期のための基本的な方法には「ミューテックス」と「セマフォ」があります。今回はこれらの概念と、それぞれのサンプルプログラムと実行結果について説明します。
目次
基本概念の説明
ミューテックス (Mutex): ミューテックスは、同時に一つのスレッドしかアクセスできない排他制御を提供するための同期プリミティブです。ロックがかかっている間、他のスレッドはそのリソースにアクセスできません。
セマフォ (Semaphore): セマフォは、指定した数だけのスレッドが同時にリソースにアクセスできる仕組みを提供します。カウンタによってアクセス可能なスレッド数が制御されます。
各言語でのサンプルコードと実行結果
Pythonでのミューテックスの例
import threading
counter = 0
lock = threading.Lock()
def increment():
global counter
for _ in range(1000000):
with lock:
counter += 1
threads = []
for _ in range(2):
thread = threading.Thread(target=increment)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
print(f"最終カウンタ値: {counter}")
実行結果:
最終カウンタ値: 2000000
Pythonでのセマフォの例
import threading
import time
semaphore = threading.Semaphore(3)
def task(thread_num):
with semaphore:
print(f"スレッド{thread_num}が実行中...")
time.sleep(1)
print(f"スレッド{thread_num}が終了")
threads = []
for i in range(5):
thread = threading.Thread(target=task, args=(i,))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
実行結果:
スレッド0が実行中...
スレッド1が実行中...
スレッド2が実行中...
スレッド0が終了
スレッド1が終了
スレッド2が終了
スレッド3が実行中...
スレッド4が実行中...
スレッド3が終了
スレッド4が終了
C#でのミューテックスの例
using System;
using System.Threading;
class Program
{
static int counter = 0;
static Mutex mutex = new Mutex();
static void Increment()
{
for (int i = 0; i < 1000000; i++)
{
mutex.WaitOne();
counter++;
mutex.ReleaseMutex();
}
}
static void Main()
{
Thread t1 = new Thread(Increment);
Thread t2 = new Thread(Increment);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.WriteLine($"最終カウンタ値: {counter}");
}
}
実行結果:
最終カウンタ値: 2000000
C#でのセマフォの例
using System;
using System.Threading;
class Program
{
static Semaphore semaphore = new Semaphore(3, 3);
static void Task(int threadNum)
{
semaphore.WaitOne();
Console.WriteLine($"スレッド{threadNum}が実行中...");
Thread.Sleep(1000);
Console.WriteLine($"スレッド{threadNum}が終了");
semaphore.Release();
}
static void Main()
{
for (int i = 0; i < 5; i++)
{
int threadNum = i;
Thread thread = new Thread(() => Task(threadNum));
thread.Start();
}
}
}
実行結果:
スレッド0が実行中...
スレッド1が実行中...
スレッド2が実行中...
スレッド0が終了
スレッド1が終了
スレッド2が終了
スレッド3が実行中...
スレッド4が実行中...
スレッド3が終了
スレッド4が終了
C++でのミューテックスの例
#include <iostream>
#include <thread>
#include <mutex>
int counter = 0;
std::mutex mtx;
void increment() {
for (int i = 0; i < 1000000; ++i) {
std::lock_guard<std::mutex> lock(mtx);
++counter;
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "最終カウンタ値: " << counter << std::endl;
return 0;
}
実行結果:
最終カウンタ値: 2000000
C++でのセマフォの例
#include <iostream>
#include <thread>
#include <chrono>
#include <semaphore>
std::counting_semaphore<3> semaphore(3);
void task(int thread_num) {
semaphore.acquire();
std::cout << "スレッド" << thread_num << "が実行中...\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "スレッド" << thread_num << "が終了\n";
semaphore.release();
}
int main() {
std::thread threads[5];
for (int i = 0; i < 5; ++i) {
threads[i] = std::thread(task, i);
}
for (auto& th : threads) {
th.join();
}
return 0;
}
実行結果:
スレッド0が実行中...
スレッド1が実行中...
スレッド2が実行中...
スレッド0が終了
スレッド1が終了
スレッド2が終了
スレッド3が実行中...
スレッド4が実行中...
スレッド3が終了
スレッド4が終了
Javaでのミューテックスの例 (synchronizedキーワード)
public class MutexExample {
private static int counter = 0;
public static synchronized void increment() {
for (int i = 0; i < 1000000; i++) {
counter++;
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(MutexExample::increment);
Thread t2 = new Thread(MutexExample::increment);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最終カウンタ値: " + counter);
}
}
実行結果:
最終カウンタ値: 2000000
Javaでのセマフォの例
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static final Semaphore semaphore = new Semaphore(3);
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
final int threadNum = i;
new Thread(() -> {
try {
semaphore.acquire();
System.out.println("スレッド" + threadNum + "が実行中...");
Thread.sleep(1000);
System.out.println("スレッド" + threadNum + "が終了");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
}
実行結果:
スレッド0が実行中...
スレッド1が実行中...
スレッド2が実行中...
スレッド0が終了
スレッド1が終了
スレッド2が終了
スレッド3が実行中...
スレッド4が実行中...
スレッド3が終了
スレッド4が終了
JavaScriptでのミューテックスの例 (async/await)
JavaScriptにはミューテックスの直接的なサポートはありませんが、async
/await
で並行性を制御することができます。
let counter = 0;
const mutex = {
locked: false,
async lock() {
while (this.locked) {
await new Promise(resolve => setTimeout(resolve, 10));
}
this.locked = true;
},
unlock() {
this.locked = false;
}
};
async function increment() {
for (let i = 0; i < 100000; i++) {
await mutex.lock();
counter++;
mutex.unlock();
}
}
async function main() {
await Promise.all([increment(), increment()]);
console.log("最終カウンタ値:", counter);
}
main();
実行結果:
最終カウンタ値: 200000
まとめ
ミューテックスは、リソースに同時に1つのスレッドしかアクセスできないように制御し、データの競合を防ぎます。セマフォは、複数のスレッドが同時にリソースにアクセスできるようにしますが、同時にアクセスできるスレッド数を制限します。各プログラミング言語でこれらの機構を利用して、マルチスレッドプログラムの安定性と信頼性を向上させることができます。
コメント