スレッドの同期:ミューテックスとセマフォの使い方

thread-synchronization

今日のトピックは「スレッドの同期」についてです。マルチスレッドプログラミングでは、複数のスレッドが同時に共有リソースにアクセスする際、データの整合性を保つために適切な同期が必要です。ミューテックスやセマフォは、こうした同期を実現するための重要なツールです。

目次

基本概念の説明

ミューテックス (Mutex)

ミューテックス (Mutex): ミューテックスは、排他制御を行うための同期プリミティブです。特定のリソースが同時に複数のスレッドからアクセスされないように制御します。あるスレッドがミューテックスを取得すると、他のスレッドはそのミューテックスが解放されるまで待機します。

セマフォ (Semaphore)

セマフォ (Semaphore): セマフォは、複数のスレッドが同時にリソースを使用できるようにするための同期プリミティブです。セマフォにはカウンタがあり、このカウンタがゼロになるまでスレッドはリソースにアクセスできます。カウンタがゼロになると、他のスレッドはリソースが解放されるまで待機します。

各言語でのミューテックスとセマフォの使用例

Python:

import threading

# ミューテックスの使用
mutex = threading.Lock()

def critical_section():
    with mutex:  # ミューテックスで排他制御
        print("スレッドがリソースを使用しています")

threads = []
for _ in range(5):
    thread = threading.Thread(target=critical_section)
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

# セマフォの使用
semaphore = threading.Semaphore(2)  # 同時に2つのスレッドがアクセス可能

def limited_section():
    with semaphore:
        print("セマフォによって制限されたリソースにアクセスしています")
        threading.Event().wait(1)  # リソースを使用中

threads = []
for _ in range(5):
    thread = threading.Thread(target=limited_section)
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

C#:

using System;
using System.Threading;

class Program
{
    static Mutex mutex = new Mutex();

    static void CriticalSection()
    {
        mutex.WaitOne();  // ミューテックスで排他制御
        Console.WriteLine("スレッドがリソースを使用しています");
        Thread.Sleep(1000);  // リソースを使用中
        mutex.ReleaseMutex();
    }

    static Semaphore semaphore = new Semaphore(2, 2);  // 同時に2つのスレッドがアクセス可能

    static void LimitedSection()
    {
        semaphore.WaitOne();
        Console.WriteLine("セマフォによって制限されたリソースにアクセスしています");
        Thread.Sleep(1000);  // リソースを使用中
        semaphore.Release();
    }

    static void Main()
    {
        Thread[] threads = new Thread[5];

        // ミューテックスの使用
        for (int i = 0; i < 5; i++)
        {
            threads[i] = new Thread(CriticalSection);
            threads[i].Start();
        }
        foreach (Thread t in threads) t.Join();

        // セマフォの使用
        for (int i = 0; i < 5; i++)
        {
            threads[i] = new Thread(LimitedSection);
            threads[i].Start();
        }
        foreach (Thread t in threads) t.Join();
    }
}

C++:

#include <iostream>
#include <thread>
#include <mutex>
#include <semaphore.h>

std::mutex mtx;

void critical_section() {
    std::lock_guard<std::mutex> lock(mtx);  // ミューテックスで排他制御
    std::cout << "スレッドがリソースを使用しています" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));  // リソースを使用中
}

std::counting_semaphore<2> semaphore(2);  // 同時に2つのスレッドがアクセス可能

void limited_section() {
    semaphore.acquire();
    std::cout << "セマフォによって制限されたリソースにアクセスしています" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));  // リソースを使用中
    semaphore.release();
}

int main() {
    std::thread threads[5];

    // ミューテックスの使用
    for (int i = 0; i < 5; ++i)
        threads[i] = std::thread(critical_section);
    for (auto& t : threads) t.join();

    // セマフォの使用
    for (int i = 0; i < 5; ++i)
        threads[i] = std::thread(limited_section);
    for (auto& t : threads) t.join();

    return 0;
}

Java:

import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class SyncExample {
    private static final Lock lock = new ReentrantLock();

    public static void criticalSection() {
        lock.lock();  // ミューテックスで排他制御
        try {
            System.out.println("スレッドがリソースを使用しています");
            Thread.sleep(1000);  // リソースを使用中
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    private static final Semaphore semaphore = new Semaphore(2);  // 同時に2つのスレッドがアクセス可能

    public static void limitedSection() {
        try {
            semaphore.acquire();
            System.out.println("セマフォによって制限されたリソースにアクセスしています");
            Thread.sleep(1000);  // リソースを使用中
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();
        }
    }

    public static void main(String[] args) {
        Thread[] threads = new Thread[5];

        // ミューテックスの使用
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(SyncExample::criticalSection);
            threads[i].start();
        }
        for (Thread t : threads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // セマフォの使用
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(SyncExample::limitedSection);
            threads[i].start();
        }
        for (Thread t : threads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

各言語の解説

Python: threading.Lockでミューテックス、threading.Semaphoreでセマフォを実装しています。特定のリソースに同時アクセスを制御することができます。

C#: Mutexクラスでミューテックス、Semaphoreクラスでセマフォを使用して、排他制御や制限付きリソースの管理を行っています。

C++: std::mutexでミューテックス、std::counting_semaphoreでセマフォを実装しています。C++20でセマフォが標準ライブラリに追加されました。

Java: ReentrantLockでミューテックス、Semaphoreクラスでセマフォを実装して、スレッドの同期を行います。

まとめ

ミューテックスとセマフォは、マルチスレッドプログラミングで重要な同期プリミティブです。ミューテックスは一度に1つのスレッドしかリソースにアクセスできないようにするために使用され、セマフォは複数のスレッドが同時にリソースにアクセスできるように制御します。

次回は、スレッド間の通信方法や共有データの管理について学び、さらに高度な並行プログラミング技術を身につけましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次