一、mutex
mutex(互斥锁)是 C++ 标准库中用于实现线程同步的重要工具,定义在 <mutex> 头文件中。它的主要作用是保护共享资源,防止多个线程同时访问共享资源而导致的数据竞争(Race Condition)问题。
1. mutex 的基本概念
- 互斥锁:
mutex是一种同步原语,用于确保同一时间只有一个线程可以访问共享资源。 - 锁的机制:
- 当一个线程持有锁时,其他线程必须等待锁被释放后才能获取锁。
- 锁的获取和释放是成对出现的,确保资源的安全访问。
2. mutex 的常用方法
lock():尝试获取锁。如果锁已被其他线程持有,则当前线程阻塞,直到锁被释放。unlock():释放锁,允许其他线程获取锁。try_lock():尝试获取锁,如果锁已被其他线程持有,则立即返回false,不会阻塞。
3. mutex 的基本用法
以下是一个简单的示例,展示了如何使用 mutex 保护共享资源:
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx; // 定义互斥锁
int sharedData = 0; // 共享资源
void increment() {
for (int i = 0; i < 10000; ++i) {
mtx.lock(); // 加锁
++sharedData; // 操作共享资源
mtx.unlock(); // 解锁
}
}
int main() {
thread t1(increment);
thread t2(increment);
t1.join();
t2.join();
cout << "Shared data: " << sharedData << endl; // 输出 20000
return 0;
}
4. lock_guard 和 unique_lock
手动调用 lock() 和 unlock() 容易出错(如忘记解锁),因此 C++ 提供了 lock_guard 和 unique_lock 来自动管理锁的生命周期。
(1)lock_guard
- 是一个 RAII 风格的锁管理器,构造时自动加锁,析构时自动解锁。
- 适用于简单的加锁场景。
示例:
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx;
int sharedData = 0;
void increment() {
for (int i = 0; i < 10000; ++i) {
lock_guard<mutex> lock(mtx); // 自动加锁和解锁
++sharedData;
}
}
int main() {
thread t1(increment);
thread t2(increment);
t1.join();
t2.join();
cout << "Shared data: " << sharedData << endl; // 输出 20000
return 0;
}
(2)unique_lock
- 比
lock_guard更灵活,支持手动加锁和解锁。 - 可以延迟加锁、转移锁的所有权等。
示例:
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx;
int sharedData = 0;
void increment() {
for (int i = 0; i < 10000; ++i) {
unique_lock<mutex> lock(mtx); // 自动加锁和解锁
++sharedData;
}
}
int main() {
thread t1(increment);
thread t2(increment);
t1.join();
t2.join();
cout << "Shared data: " << sharedData << endl; // 输出 20000
return 0;
}
5. try_lock 的使用
try_lock() 尝试获取锁,如果锁已被其他线程持有,则立即返回 false,不会阻塞。
示例:
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx;
void task() {
if (mtx.try_lock()) { // 尝试获取锁
cout << "Thread " << this_thread::get_id() << " acquired the lock." << endl;
this_thread::sleep_for(chrono::seconds(1)); // 模拟耗时操作
mtx.unlock(); // 释放锁
} else {
cout << "Thread " << this_thread::get_id() << " failed to acquire the lock." << endl;
}
}
int main() {
thread t1(task);
thread t2(task);
t1.join();
t2.join();
return 0;
}
6. 死锁问题
死锁是指多个线程互相等待对方释放锁,导致程序无法继续执行。常见的死锁场景包括:
- 多个线程以不同的顺序获取锁。
- 线程持有锁的同时再次尝试获取锁。
示例:死锁
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx1, mtx2;
void task1() {
mtx1.lock();
this_thread::sleep_for(chrono::milliseconds(100)); // 模拟耗时操作
mtx2.lock(); // 等待 mtx2
cout << "Task 1 completed." << endl;
mtx2.unlock();
mtx1.unlock();
}
void task2() {
mtx2.lock();
this_thread::sleep_for(chrono::milliseconds(100)); // 模拟耗时操作
mtx1.lock(); // 等待 mtx1
cout << "Task 2 completed." << endl;
mtx1.unlock();
mtx2.unlock();
}
int main() {
thread t1(task1);
thread t2(task2);
t1.join();
t2.join();
return 0;
}
解决方法:
- 按固定顺序加锁。
- 使用
std::lock()一次性锁定多个互斥锁。
示例:避免死锁
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx1, mtx2;
void task1() {
lock(mtx1, mtx2); // 一次性锁定多个互斥锁
lock_guard<mutex> lock1(mtx1, adopt_lock); // 接管 mtx1
lock_guard<mutex> lock2(mtx2, adopt_lock); // 接管 mtx2
cout << "Task 1 completed." << endl;
}
void task2() {
lock(mtx1, mtx2); // 一次性锁定多个互斥锁
lock_guard<mutex> lock1(mtx1, adopt_lock); // 接管 mtx1
lock_guard<mutex> lock2(mtx2, adopt_lock); // 接管 mtx2
cout << "Task 2 completed." << endl;
}
int main() {
thread t1(task1);
thread t2(task2);
t1.join();
t2.join();
return 0;
}
7. recursive_mutex
- 允许同一个线程多次获取同一个锁。
- 适用于递归调用场景。
示例:
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
recursive_mutex mtx;
void recursiveFunction(int n) {
if (n <= 0) return;
mtx.lock();
cout << "Locked by thread " << this_thread::get_id() << ", n = " << n << endl;
recursiveFunction(n - 1); // 递归调用
mtx.unlock();
}
int main() {
thread t(recursiveFunction, 3);
t.join();
return 0;
}
二、wait
wait 是 C++ 中用于线程同步的重要机制,通常与条件变量(std::condition_variable)一起使用。它的作用是让当前线程等待某个条件满足,直到被其他线程通知(notify)或超时。
1. wait 的基本概念
-
条件变量:
std::condition_variable是一种同步原语,用于线程间的通信。它允许一个或多个线程等待某个条件成立。 -
wait的作用:- 让当前线程进入等待状态,直到被其他线程唤醒。
- 在等待期间,线程会释放锁(避免死锁),并在被唤醒后重新获取锁。
2. wait 的常用形式
std::condition_variable 提供了两种 wait 方法:
-
wait(lock):- 让当前线程等待,直到被
notify_one()或notify_all()唤醒。 - 需要传入一个
std::unique_lock<std::mutex>对象。
- 让当前线程等待,直到被
-
wait(lock, predicate):- 让当前线程等待,直到条件满足(
predicate返回true)。 - 避免虚假唤醒(Spurious Wakeup)。
- 让当前线程等待,直到条件满足(
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
mutex mtx;
condition_variable cv;
bool ready = false; // 条件变量
void waitForReady() {
unique_lock<mutex> lock(mtx);
cv.wait(lock, [] { return ready; }); // 等待 ready 为 true
cout << "Thread is ready!" << endl;
}
void setReady() {
this_thread::sleep_for(chrono::seconds(2)); // 模拟准备工作
{
lock_guard<mutex> lock(mtx);
ready = true;
}
cv.notify_one(); // 通知等待的线程
}
int main() {
thread t1(waitForReady);
thread t2(setReady);
t1.join();
t2.join();
return 0;
}
3.wait 的工作流程
- 线程调用
wait,释放锁并进入等待状态。 - 其他线程修改条件并调用
notify_one()或notify_all()。 - 等待的线程被唤醒,重新获取锁并检查条件。
- 如果条件满足,线程继续执行;否则,再次进入等待状态。
4.wait_for 和 wait_until
std::condition_variable 还提供了超时版本的 wait:
wait_for(lock, duration):等待一段时间,超时后返回。wait_until(lock, time_point):等待到某个时间点,超时后返回。
示例:wait_for 的使用
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
mutex mtx;
condition_variable cv;
bool ready = false;
void waitForReady() {
unique_lock<mutex> lock(mtx);
if (cv.wait_for(lock, chrono::seconds(3), [] { return ready; })) {
cout << "Thread is ready!" << endl;
} else {
cout << "Timeout!" << endl;
}
}
void setReady() {
this_thread::sleep_for(chrono::seconds(5)); // 模拟准备工作
{
lock_guard<mutex> lock(mtx);
ready = true;
}
cv.notify_one(); // 通知等待的线程
}
int main() {
thread t1(waitForReady);
thread t2(setReady);
t1.join();
t2.join();
return 0;
}