C++11で導入された<mutex>ヘッダーは、共有リソースへの排他的アクセスを保証するためのメカニズムを提供します。
C++11で利用可能なミューテックス型
<mutex>ヘッダーには、以下のミューテックス型が定義されています。
| 型名 | 説明 |
|---|---|
std::mutex |
基本的なミューテックス |
std::recursive_mutex |
再帰的なロックを許可するミューテックス |
std::timed_mutex |
指定時間待機するロック機能を持つミューテックス |
std::recursive_timed_mutex |
再帰ロックと時間待機機能を併せ持つミューテックス |
std::mutex
最も基本的なミューテックスで、所有権を排他的に管理します。初期状態ではロックされていません。コピーや移動はできません。
主要なメンバ関数は以下の通りです。
lock(): ミューテックスがロックされていない場合、現在のスレッドがロックを取得します。他のスレッドがロックを保持している場合は、ブロックされます。既に現在のスレッドがロックを保持している場合は、デッドロックが発生します。try_lock(): ミューテックスがロックされていない場合、ロックを取得しtrueを返します。他のスレッドがロックを保持している場合は、ブロックせずにfalseを返します。unlock(): ミューテックスのロックを解除します。
例:共有変数 counter のインクリメントを保護する。
std::mutex data_mutex;
int shared_counter = 0;
// ...
if (data_mutex.try_lock()) {
shared_counter++;
data_mutex.unlock();
}
std::recursive_mutex
同じスレッドが複数回ロックを試みてもデッドロックを起こさないミューテックスです。ロックの解除は、ロックした回数と同じ回数行う必要があります。
std::timed_mutex
std::mutex の機能に加え、以下のメンバ関数を持ちます。
try_lock_for(duration): 指定された時間だけロックの取得を試みます。時間内にロックを取得できなければfalseを返します。try_lock_until(time_point): 指定された時間点までロックの取得を試みます。時間点までにロックを取得できなければfalseを返します。
例:200ミリ秒間ロックを試みる。
std::timed_mutex timed_mtx;
// ...
while (!timed_mtx.try_lock_for(std::chrono::milliseconds(200))) {
// ロック取得に失敗した場合の処理
}
// ロック取得成功後
timed_mtx.unlock();
std::recursive_timed_mutex
std::recursive_mutex と std::timed_mutex の両方の機能を提供します。
RAIIによるミューテックス管理
C++11では、RAII (Resource Acquisition Is Initialization) パターンを利用して、ミューテックスのロック管理を簡潔かつ安全に行うためのクラスが提供されています。
ロック管理用タグ型
lock_guard や unique_lock と共に使用され、コンストラクタの動作を指定するためのタグ型です。
std::adopt_lock_t: ミューテックスが既にロックされていることを示します。std::defer_lock_t: ミューテックスのロックを遅延させます。std::try_to_lock_t: ミューテックスのロックを非ブロックで試みます。
これらのタグ型に対応する定数オブジェクト(例: std::adopt_lock)が提供されています。
std::lock_guard
RAIIに基づいたクラスで、スコープを抜ける際に自動的にミューテックスのロックを解除します。例外安全性を向上させます。
コンストラクタ:
explicit lock_guard(mutex_type& m): ミューテックスmをロックして初期化します。lock_guard(mutex_type& m, adopt_lock_t tag):mは既にロックされているものとして初期化します。
lock_guard はコピーや移動ができません。
std::mutex mtx;
// ...
{
std::lock_guard<std::mutex> guard(mtx); // mtxをロック
// critical section
} // guardが破棄され、mtxがアンロックされる
std::unique_lock
lock_guard よりも柔軟なロック管理機能を提供します。ロックの取得・解放を明示的に制御でき、ロックの所有権を移動することも可能です。
コンストラクタ:
explicit unique_lock(mutex_type& m): ミューテックスmをロックします。unique_lock(mutex_type& m, try_to_lock_t tag):mのロックを試みます。unique_lock(mutex_type& m, defer_lock_t tag): ロックせずにmを管理します。unique_lock(mutex_type& m, adopt_lock_t tag):mは既にロックされているものとして初期化します。template<class Rep, class Period> unique_lock(mutex_type& m, const chrono::duration<Rep, Period>& rtime):try_lock_forを使用してロックを試みます。template<class Clock, class Duration> unique_lock(mutex_type& m, const chrono::time_point<Clock, Duration>& timeout):try_lock_untilを使用してロックを試みます。
ムーブコンストラクタとムーブ代入演算子により、ロックの所有権を移動できます。コピーはできません。
主要メンバ関数:
lock(): ロックを取得します。try_lock(): ロックを非ブロックで試みます。try_lock_for(): 指定時間ロックを試みます。try_lock_until(): 指定時間点までロックを試みます。unlock(): ロックを解除します。release(): ミューテックスの管理を解除し、ポインタを返します。owns_lock(): 現在ロックを所有しているか返します。
例:try_lock を使用してロックを試みる。
std::mutex data_mutex;
int shared_data = 0;
void process_data(int value) {
std::unique_lock<std::mutex> lock(data_mutex, std::try_to_lock);
if (lock.owns_lock()) {
shared_data += value;
// ... critical section ...
} else {
// ロックを取得できなかった場合の処理
}
}
<mutex>ヘッダーのその他のユーティリティ関数
std::lock(mutex1, mutex2, ...): 複数のミューテックスをデッドロックフリーで同時にロックします。std::try_lock(mutex1, mutex2, ...): 複数のミューテックスのロックを同時に試みます。std::call_once(flag, function): 指定された関数を、複数のスレッドから呼び出された場合でも一度だけ実行することを保証します。
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
std::mutex cout_mutex; // coutへのアクセスを保護
void worker_function(int id) {
// 独占的なアクセスが必要な処理
std::unique_lock<std::mutex> lock(cout_mutex); // cout_mutexをロック
std::cout << "Worker " << id << " is processing." << std::endl;
// lockはスコープを抜けるときに自動的にアンロックされる
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(worker_function, i);
}
for (auto& t : threads) {
t.join();
}
return 0;
}