目录
A、线程/多线程基础
一、C++11 创建线程的几种方式
1.1 使用函数指针
1.2 使用 lambda 表达式
1.3 使用成员函数
1.4 使用可调用对象 (Functor)
二、定义一个线程类
三、join() 与 detach() 的详细用法及区别
3.1 join() 的用法
3.2 detach() 的用法
3.3 join() 与 detach() 的区别总结
四、std::this_thread
4.1、主要功能
std::this_thread::get_id()
std::this_thread::yield()
std::this_thread::sleep_for()
std::this_thread::sleep_until()
4.2、各方法的用法及适用场景
1. std::this_thread::get_id()
2. std::this_thread::yield()
3. std::this_thread::sleep_for()
4. std::this_thread::sleep_until()
4.3、std::this_thread::sleep_for 与 std::this_thread::sleep_until 的区别
注:sleep_for 与 sleep_until 对比示例
五、std::mutex 详解
主要功能
使用场景
代码示例
1. 基本使用 std::mutex
2. 使用 try_lock()
3. 使用 std::lock_guard 简化互斥锁管理
4. 使用 std::scoped_lock 同时锁定多个互斥量
重要注意事项
总结
六、std::unique_lock
主要功能:
构造函数与锁定策略
使用示例
1. 基本使用:自动锁定与解锁
2. 手动锁定与解锁
3. 使用 try_lock 尝试锁定
4. 使用 adopt_lock 接管已经锁定的互斥量
std::unique_lock 与 std::lock_guard 的区别
总结
七、std::condition_variable::wait
std::condition_variable::wait 的原理
wait() 的使用场景
代码示例
1. 基本使用 wait() 和 notify_one()
2. 使用 notify_all() 唤醒所有等待线程
3. 防止虚假唤醒
4. wait_for() 和 wait_until()
4.1 使用 wait_for() 超时等待
4.2 使用 wait_until() 等待到指定时间点
总结
这是最基本的线程创建方式,通过将一个普通的函数指针传递给
std::thread
。
#include
#include
void threadFunction(int x) {
std::cout << "Thread function called with value: " << x << std::endl;
}
int main() {
std::thread t(threadFunction, 10);
t.join(); // 等待线程执行完毕
return 0;
}
C++11 引入了 lambda 表达式,可以简化代码结构,使用匿名函数作为线程的入口。
#include
#include
int main() {
std::thread t([](int x) {
std::cout << "Lambda thread called with value: " << x << std::endl;
}, 20);
t.join(); // 等待线程执行完毕
return 0;
}
如果你需要在线程中调用类的成员函数,可以将成员函数和对象指针一起传递给
std::thread
。
#include
#include
class MyClass {
public:
void memberFunction(int x) {
std::cout << "Member function called with value: " << x << std::endl;
}
};
int main() {
MyClass obj;
std::thread t(&MyClass::memberFunction, &obj, 30);
t.join(); // 等待线程执行完毕
return 0;
}
一个可调用对象是一个重载了
operator()
的类对象,它可以像函数一样被调用。
#include
#include
class Functor {
public:
void operator()(int x) {
std::cout << "Functor called with value: " << x << std::endl;
}
};
int main() {
Functor functor;
std::thread t(functor, 40);
t.join(); // 等待线程执行完毕
return 0;
}
我们可以封装线程操作,定义一个线程类来简化线程管理。下面是一个简单的线程类示例:
#include
#include
class ThreadClass {
private:
std::thread t;
public:
// 构造函数,接受函数和参数来启动线程
template
explicit ThreadClass(Callable&& func, Args&&... args) {
t = std::thread(std::forward(func), std::forward(args)...);
}
// 加入线程(等待线程完成)
void join() {
if (t.joinable()) {
t.join();
}
}
// 分离线程(让线程独立运行)
void detach() {
if (t.joinable()) {
t.detach();
}
}
// 析构函数,确保线程对象在销毁前已被处理
~ThreadClass() {
if (t.joinable()) {
t.join(); // 通常在析构时,选择 join 或 detach 来处理未处理的线程
}
}
};
void threadFunction(int x) {
std::cout << "Thread function running with value: " << x << std::endl;
}
int main() {
// 使用类来管理线程
ThreadClass threadObj(threadFunction, 50);
threadObj.join(); // 等待线程完成
return 0;
}
解释:
ThreadClass
使用模板构造函数来创建线程,并且使用std::forward
保证参数的完美转发。- 提供了
join()
和detach()
方法来管理线程的生命周期。- 在析构函数中确保未处理的线程被
join()
,防止程序崩溃。
join()
与 detach()
的详细用法及区别join()
的用法
join()
的作用是阻塞当前线程(通常是主线程),等待被join
的子线程执行完毕。如果一个线程对象在退出前没有调用join()
或detach()
,程序会抛出异常。
#include
#include
void task() {
std::cout << "Task is running in thread." << std::endl;
}
int main() {
std::thread t(task);
// 主线程等待子线程完成
std::cout << "Waiting for thread to finish..." << std::endl;
t.join(); // 阻塞,直到子线程完成
std::cout << "Thread has finished." << std::endl;
return 0;
}
解释:
join()
会使主线程等待t
所代表的子线程结束。如果没有join()
,主线程可能会提前退出,从而导致异常。
detach()
的用法
detach()
会使线程与主线程分离,子线程在后台独立运行。主线程不再等待子线程的完成。调用detach()
后,子线程的资源将在其完成时由系统自动回收。
示例:
#include
#include
#include
void task() {
std::cout << "Detached thread is running." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
std::cout << "Detached thread finished." << std::endl;
}
int main() {
std::thread t(task);
// 分离线程,允许其在后台运行
t.detach();
// 主线程不等待子线程完成,继续执行
std::cout << "Main thread is not waiting for detached thread." << std::endl;
// 模拟主线程的其他操作
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Main thread finished." << std::endl;
return 0;
}
解释:
detach()
后,线程将独立于主线程运行。即使主线程退出,子线程也会继续执行直到完成。- 一旦线程被
detach()
,不能再调用join()
,并且主线程不能再追踪该线程的状态。
join()
与 detach()
的区别总结
join()
:阻塞当前线程,直到被join
的线程完成。主线程必须等待子线程执行完毕。detach()
:线程与主线程分离,线程会在后台独立运行,主线程不再等待子线程,也无法再追踪该子线程的状态。
四、std::this_thread
是 C++11 标准库引入的一个命名空间,包含了一组与当前线程相关的函数。它提供了控制和查询当前线程(即执行这些代码的线程)的功能,例如让线程休眠、获取当前线程的 ID 等。
std::this_thread
类本身并不是真正的类,它是一个命名空间,其作用是组织与当前线程相关的函数。所有函数都是针对当前线程执行的,不需要创建线程对象即可使用。
std::this_thread::get_id()
- 获取当前线程的 ID。
- 返回类型为
std::thread::id
,它是线程的唯一标识符,可以用来比较两个线程是否相同。#include
#include int main() { // 获取当前线程的 ID std::thread::id this_id = std::this_thread::get_id(); std::cout << "Current thread ID: " << this_id << std::endl; return 0; } 输出示例:
Current thread ID: 140735758188288
std::this_thread::yield()
- 将当前线程的执行权暂时让出,让操作系统可以将 CPU 分配给其他线程。这并不意味着当前线程会终止,而是它可以在下一次被调度时恢复执行。
- 这个函数通常用于避免某些线程长时间占用 CPU,给其他线程一个执行的机会。
#include
#include void task() { for (int i = 0; i < 5; ++i) { std::cout << "Task executing...\n"; std::this_thread::yield(); // 让出 CPU } } int main() { std::thread t(task); t.join(); return 0; } 输出示例:
Task executing... Task executing... Task executing... Task executing... Task executing...
std::this_thread::sleep_for()
- 让当前线程休眠(暂停执行)一段指定的时间。
- 参数是
std::chrono::duration
类型,可以传递秒、毫秒、微秒等不同的时间单位。- 该函数是将线程挂起一段相对时间(从现在开始等多久)。
#include
#include #include int main() { std::cout << "Sleeping for 3 seconds...\n"; std::this_thread::sleep_for(std::chrono::seconds(3)); // 休眠3秒 std::cout << "Awake now!\n"; return 0; } 输出示例:
Sleeping for 3 seconds... Awake now!
std::this_thread::sleep_until()
- 让当前线程休眠直到某个指定的绝对时间点。
- 参数是
std::chrono::time_point
类型,表示绝对时间点,常与std::chrono::system_clock::now()
或其他时间点一起使用。- 和
sleep_for
不同,sleep_until
是指定一个未来的时间点,线程休眠直到那个时间点到达。#include
#include #include int main() { using std::chrono::system_clock; // 获取当前时间点,并加上 5 秒 auto wake_time = system_clock::now() + std::chrono::seconds(5); std::cout << "Sleeping until the next 5 seconds...\n"; std::this_thread::sleep_until(wake_time); // 休眠到指定的时间点 std::cout << "Awake now!\n"; return 0; } 输出示例:
Sleeping until the next 5 seconds... Awake now!
1.
std::this_thread::get_id()
- 用途:当你需要区分不同线程时,
get_id()
能够返回当前线程的唯一 ID。尤其是在调试或日志记录中,跟踪哪个线程在执行特定任务是非常有用的。- 示例场景:多线程下载任务,你可以在日志中记录每个线程的 ID,帮助排查问题。
2.
std::this_thread::yield()
- 用途:在某些场景下,你可能希望当前线程暂时让出 CPU 资源,让系统调度其他线程执行。这样可以避免当前线程长时间占用 CPU,尤其是在没有显式同步机制时,这有助于提升多线程应用的公平性。
- 示例场景:在编写需要轮询等待某个条件满足的代码时,
yield()
可以帮助让出 CPU,而不是长时间阻塞在一个无效的循环中。
3.
std::this_thread::sleep_for()
- 用途:当你需要让线程暂停执行一段时间后再继续执行时,可以使用
sleep_for()
。常见的用途包括定时任务、轮询操作、间隔式的数据更新等。- 示例场景:编写一个定时器,每隔 10 秒检查一次服务器状态。
4.
std::this_thread::sleep_until()
- 用途:当你需要让线程休眠到某个特定的时间点时,
sleep_until()
是非常合适的选择。例如你想要在某个时间戳开始执行某些任务。- 示例场景:编写一个程序,每天早上 8:00 执行任务。可以通过
sleep_until()
让程序休眠到指定的时间。
4.3、std::this_thread::sleep_for
与std::this_thread::sleep_until
的区别
sleep_for
是相对时间休眠。它让线程休眠一个相对时间长度,从现在起计算需要休眠多久。例如休眠 5 秒。sleep_until
是绝对时间休眠。它让线程休眠到一个确定的时间点。你可以传递一个绝对时间点,线程会休眠直到这个时间点。例如让线程休眠到今天的 12:00。
注:sleep_for
与sleep_until
对比示例#include
#include #include int main() { using namespace std::chrono; // 使用 sleep_for 休眠 2 秒 std::cout << "Sleeping for 2 seconds...\n"; std::this_thread::sleep_for(seconds(2)); // 相对休眠 std::cout << "Awake after 2 seconds.\n"; // 使用 sleep_until 休眠到未来的绝对时间点 auto future_time = system_clock::now() + seconds(3); std::cout << "Sleeping until the next 3 seconds...\n"; std::this_thread::sleep_until(future_time); // 绝对时间点休眠 std::cout << "Awake at the specified time point.\n"; return 0; } 输出示例:
Sleeping for 2 seconds... Awake after 2 seconds. Sleeping until the next 3 seconds... Awake at the specified time point.
总结
std::this_thread
提供了一个简单而强大的接口,用于控制当前线程的行为,包括:
yield()
提高 CPU 使用效率,允许系统调度其他线程。sleep_for
或 sleep_until
暂时暂停线程的执行,应用场景包括定时任务、延迟执行等。这些工具使得 C++11 开始的多线程编程变得更加灵活和易于控制。
std::mutex
详解std::mutex
是 C++11 引入的一种线程同步机制,用于在多线程环境下防止数据竞争(race condition)。在多线程程序中,如果多个线程同时访问并修改同一个共享数据,会导致数据竞争问题,造成不可预知的错误。std::mutex
(互斥量)提供了一种机制,确保每次只有一个线程能够访问共享资源,其他线程必须等待,直到前一个线程释放互斥量。
std::mutex
类提供了几个基本的方法来控制线程对共享资源的访问:
lock()
:锁定互斥量。如果互斥量已经被其他线程锁定,则调用 lock()
的线程将会阻塞,直到该互斥量被解锁。unlock()
:解锁互斥量,允许其他线程锁定它。try_lock()
:尝试锁定互斥量。如果互斥量已被其他线程锁定,则返回 false
,否则锁定并返回 true
。
std::mutex
通常用于保护临界区(critical section),即多个线程可能会同时访问的共享资源。
std::mutex
这是一个简单的例子,展示了如何使用 std::mutex
来保护共享数据(计数器),防止多个线程同时修改它。
#include
#include
#include
std::mutex mtx; // 全局互斥量,用于同步对共享数据的访问
int counter = 0; // 共享资源
void increaseCounter() {
for (int i = 0; i < 10000; ++i) {
mtx.lock(); // 锁定互斥量,进入临界区
++counter; // 对共享数据进行操作
mtx.unlock(); // 解锁互斥量,离开临界区
}
}
int main() {
// 创建两个线程,执行 increaseCounter 函数
std::thread t1(increaseCounter);
std::thread t2(increaseCounter);
// 等待两个线程完成
t1.join();
t2.join();
// 输出最终的计数器值
std::cout << "Final counter value: " << counter << std::endl;
return 0;
}
解释:
mtx
:在每个线程访问共享资源之前使用 lock()
来锁定互斥量,保证在任何时候只有一个线程可以修改 counter
变量。mtx.lock()
和 mtx.unlock()
之间的代码区域,即对共享资源的访问部分。counter
,因此保证了线程安全。输出示例:
Final counter value: 20000
如果没有使用 std::mutex
,两个线程可能同时访问并修改 counter
,导致最后输出的值小于预期的 20000
,因为多个线程可能在同一时刻操作同一内存位置,发生了数据竞争。
try_lock()
try_lock()
是 std::mutex
提供的另一个方法,它不会阻塞线程。如果互斥量已经被锁定,try_lock()
会立即返回 false
,而不是阻塞等待。
#include
#include
#include
std::mutex mtx; // 全局互斥量
void printMessage(const std::string& msg) {
if (mtx.try_lock()) { // 尝试获取锁,如果成功则执行
std::cout << msg << std::endl;
mtx.unlock(); // 解锁
} else {
std::cout << "Lock busy, couldn't print: " << msg << std::endl;
}
}
int main() {
std::thread t1(printMessage, "Hello from Thread 1");
std::thread t2(printMessage, "Hello from Thread 2");
t1.join();
t2.join();
return 0;
}
解释:
try_lock()
方法尝试锁定互斥量。如果成功获取锁,它就进入临界区执行输出操作;如果未能获取锁,说明其他线程已锁定了互斥量,则输出 "Lock busy"。输出示例:
Hello from Thread 1
Lock busy, couldn't print: Hello from Thread 2
std::lock_guard
简化互斥锁管理C++11 提供了 std::lock_guard
,它是一个 RAII 风格的锁管理器,在作用域结束时自动释放锁,避免了手动 lock()
和 unlock()
可能引发的错误。
#include
#include
#include
std::mutex mtx; // 全局互斥量
int counter = 0; // 共享资源
void increaseCounter() {
for (int i = 0; i < 10000; ++i) {
std::lock_guard lock(mtx); // 自动锁定互斥量,作用域结束时自动解锁
++counter;
}
}
int main() {
std::thread t1(increaseCounter);
std::thread t2(increaseCounter);
t1.join();
t2.join();
std::cout << "Final counter value: " << counter << std::endl;
return 0;
}
解释:
std::lock_guard
是一个模板类,管理互斥锁的生命周期。它在构造时自动锁定互斥量,在作用域结束时(例如函数返回或异常抛出时)自动解锁,避免了忘记解锁或出现死锁的情况。输出示例:
Final counter value: 20000
std::scoped_lock
同时锁定多个互斥量C++17 引入了 std::scoped_lock
,它不仅可以同时锁定多个互斥量,还能防止死锁。std::scoped_lock
是 std::lock_guard
的改进版,特别适用于需要同时锁定多个资源的场景。
#include
#include
#include
std::mutex mtx1, mtx2; // 两个互斥量
void task1() {
std::scoped_lock lock(mtx1, mtx2); // 同时锁定 mtx1 和 mtx2
std::cout << "Task 1 has locked both mutexes." << std::endl;
}
void task2() {
std::scoped_lock lock(mtx1, mtx2); // 同时锁定 mtx1 和 mtx2
std::cout << "Task 2 has locked both mutexes." << std::endl;
}
int main() {
std::thread t1(task1);
std::thread t2(task2);
t1.join();
t2.join();
return 0;
}
解释:
std::scoped_lock
同时锁定多个互斥量,并且能避免死锁。它会确保在解锁时,以相反顺序解锁,从而避免了交叉锁定不同互斥量时可能发生的死锁问题。输出示例:
Task 1 has locked both mutexes.
Task 2 has locked both mutexes.
std::scoped_lock
可以简化锁定多个资源时的死锁管理。lock_guard
和 scoped_lock
的推荐使用:lock_guard
和 scoped_lock
是 RAII 风格的锁管理工具,推荐使用这些工具来管理锁的生命周期,避免手动 lock()
和 unlock()
可能引发的错误。std::mutex
是 C++11 提供的基本线程同步工具,用于解决多线程编程中的数据竞争问题。通过互斥量可以确保同一时刻只有一个线程能够访问共享资源,防止多个线程同时修改数据而导致不可预测的行为。结合 std::lock_guard
和 std::scoped_lock
,可以更简便地管理锁的生命周期,确保代码更加安全和高效。
六、std::unique_lock
std::unique_lock
是 C++11 提供的一种灵活的锁管理工具。它与std::lock_guard
类似,都用于管理互斥锁的生命周期,确保锁的正确释放。与std::lock_guard
不同的是,std::unique_lock
提供了更多的灵活性,包括:
std::try_lock
、std::defer_lock
和 std::adopt_lock
等高级锁定策略。
- 自动锁管理:和
std::lock_guard
一样,std::unique_lock
在构造时锁定互斥量,在作用域结束时自动解锁,防止忘记解锁的错误。- 手动锁定与解锁:你可以手动调用
lock()
、unlock()
方法,控制锁的生命周期。- 延迟锁定:你可以选择延迟锁定互斥量,也就是说,创建
std::unique_lock
对象时并不立即锁定互斥量,稍后根据需要手动锁定。- 移动语义支持:
std::unique_lock
可以被移动,这对于实现线程间的互斥锁传递很有用。
构造函数与锁定策略
std::defer_lock
:不自动锁定互斥量,稍后由程序员手动锁定。std::try_to_lock
:尝试锁定互斥量,若无法锁定,则不阻塞并返回false
。std::adopt_lock
:假定互斥量已经被锁定,不再锁定。
std::unique_lock
可以像 std::lock_guard
一样,在构造时自动锁定互斥量,并在析构时自动解锁。
#include
#include
#include
std::mutex mtx; // 全局互斥量
int counter = 0; // 共享资源
void increaseCounter() {
for (int i = 0; i < 10000; ++i) {
std::unique_lock lock(mtx); // 自动锁定互斥量
++counter; // 临界区
// lock 会在作用域结束时自动解锁
}
}
int main() {
std::thread t1(increaseCounter);
std::thread t2(increaseCounter);
t1.join();
t2.join();
std::cout << "Final counter value: " << counter << std::endl;
return 0;
}
解释:
std::unique_lock
的构造函数会立即锁定互斥量 mtx
,并且当 lock
作用域结束时,它会自动解锁。这种机制避免了手动调用 unlock()
的必要。你可以通过 std::unique_lock
对互斥量进行手动锁定和解锁操作,控制更为灵活。
#include
#include
#include
std::mutex mtx;
void task() {
std::unique_lock lock(mtx, std::defer_lock); // 延迟锁定互斥量
std::cout << "Before locking" << std::endl;
lock.lock(); // 手动锁定互斥量
std::cout << "Locked and working..." << std::endl;
lock.unlock(); // 手动解锁
std::cout << "Unlocked" << std::endl;
}
int main() {
std::thread t(task);
t.join();
return 0;
}
解释:
std::defer_lock
,std::unique_lock
在构造时不会自动锁定互斥量。随后,我们手动调用了 lock()
和 unlock()
来控制互斥量的锁定和解锁过程。try_lock
尝试锁定std::unique_lock
还可以用于尝试锁定互斥量,而不阻塞线程。如果锁定失败,线程可以继续执行其他任务。
#include
#include
#include
std::mutex mtx;
void task() {
std::unique_lock lock(mtx, std::try_to_lock); // 尝试锁定互斥量
if (lock.owns_lock()) {
std::cout << "Lock acquired, working..." << std::endl;
} else {
std::cout << "Couldn't acquire lock, doing other work..." << std::endl;
}
}
int main() {
std::thread t1(task);
std::thread t2(task);
t1.join();
t2.join();
return 0;
}
解释:
std::try_to_lock
,线程会尝试锁定互斥量。如果无法锁定,程序可以继续执行其他任务,而不是阻塞等待锁的释放。owns_lock()
可以检查 std::unique_lock
是否成功锁定互斥量。adopt_lock
接管已经锁定的互斥量如果你手动锁定了互斥量,但希望将它交给 std::unique_lock
来管理,可以使用 std::adopt_lock
。这会告诉 std::unique_lock
,互斥量已经被锁定,不再需要再次锁定。
#include
#include
#include
std::mutex mtx;
void task() {
mtx.lock(); // 手动锁定互斥量
std::unique_lock lock(mtx, std::adopt_lock); // 接管锁
std::cout << "Lock adopted, working..." << std::endl;
// lock 会在作用域结束时自动解锁
}
int main() {
std::thread t(task);
t.join();
return 0;
}
解释:
std::adopt_lock
告诉std::unique_lock
,互斥量已经被锁定,因此不需要再次调用lock()
。但是,当std::unique_lock
对象销毁时,它会自动调用unlock()
。
std::unique_lock
与 std::lock_guard
的区别灵活性:
std::lock_guard
在构造时立即锁定互斥量,并在销毁时自动解锁。它适合用于简单的锁管理。std::unique_lock
提供了更多灵活的功能,如延迟锁定、尝试锁定、手动解锁等。它适用于需要复杂锁定逻辑的场景。锁的控制:
std::lock_guard
不允许手动解锁,只能在作用域结束时解锁。std::unique_lock
允许在代码中手动调用 unlock()
,并且可以手动重新锁定。性能:
std::lock_guard
更简单,通常它的性能要比 std::unique_lock
稍高一些。但如果你的程序不需要 std::unique_lock
的灵活性,优先使用 std::lock_guard
会更高效。std::unique_lock
是一个灵活的锁管理工具,适合处理复杂的线程同步场景。它与 std::lock_guard
的区别在于其灵活的控制能力:支持延迟锁定、手动解锁、尝试锁定等操作。此外,std::unique_lock
可以使用 std::defer_lock
、std::try_to_lock
和 std::adopt_lock
等锁定策略,适用于需要更精细控制的场景。在简单的锁定场景中,std::lock_guard
更适合,而 std::unique_lock
则适用于需要更多灵活性的场景。
七、std::condition_variable::wait
是 C++11 中引入的一个机制,用于在线程同步时协调多个线程之间的执行。它与 std::mutex
和 std::unique_lock
配合使用,用于阻塞线程直到特定条件满足。
wait()
的主要用途是在某个条件不满足时让线程进入等待状态,等到另一个线程通知条件发生变化时再继续执行。通过wait()
,可以有效避免忙等待(busy waiting),减少 CPU 资源浪费。
std::condition_variable::wait
的原理wait()
必须与一个互斥锁(std::unique_lock
)一起使用。它在调用 wait()
时会自动释放锁,进入等待状态,并在被通知时重新获取锁。wait()
用于阻塞线程,直到另一个线程通过条件变量调用 notify_one()
或 notify_all()
来通知等待线程可以继续执行。notify_one()
:唤醒一个等待的线程。notify_all()
:唤醒所有等待的线程。wait()
的使用场景典型的 wait()
使用场景是生产者-消费者问题。在这种情况下,消费者等待生产者生成数据,而生产者在生成数据后通知消费者继续处理。
wait()
和 notify_one()
这是一个简单的示例,其中一个线程等待某个条件满足,而另一个线程在一定时间后设置条件并通知等待的线程继续执行。
#include
#include
#include
#include
std::mutex mtx; // 互斥锁
std::condition_variable cv; // 条件变量
bool ready = false; // 条件标志
void waitForWork() {
std::unique_lock lock(mtx); // 获取锁
std::cout << "Waiting for work to be ready...\n";
// 当 ready 为 false 时,线程进入等待状态,锁被自动释放
cv.wait(lock, [] { return ready; });
// 当 ready 为 true 时,wait 结束,线程重新获取锁
std::cout << "Work is ready, proceeding...\n";
}
void prepareWork() {
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟工作准备
{
std::lock_guard lock(mtx); // 获取锁并设置条件
ready = true;
std::cout << "Work is prepared, notifying worker...\n";
}
cv.notify_one(); // 通知等待线程可以继续执行
}
int main() {
std::thread worker(waitForWork); // 创建等待线程
std::thread preparer(prepareWork); // 创建准备线程
worker.join();
preparer.join();
return 0;
}
解释:
wait()
:在 waitForWork()
函数中,调用 cv.wait(lock, [] { return ready; })
,它将当前线程阻塞,直到 ready
变为 true
。wait()
在等待期间自动释放锁,避免其他线程无法获取锁。notify_one()
:在 prepareWork()
函数中,调用 cv.notify_one()
通知等待的线程(waitForWork
)可以继续执行。wait()
和 notify_one()
都需要与互斥锁配合使用,以保证线程之间的同步。输出示例:
Waiting for work to be ready...
Work is prepared, notifying worker...
Work is ready, proceeding...
notify_all()
唤醒所有等待线程notify_all()
可以唤醒所有因条件不满足而阻塞的线程。这对于多个线程等待同一个条件的场景非常有用。
#include
#include
#include
#include
#include
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker(int id) {
std::unique_lock lock(mtx);
cv.wait(lock, [] { return ready; }); // 所有线程都等待 ready 为 true
std::cout << "Worker " << id << " is proceeding.\n";
}
void prepareWork() {
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟工作准备
{
std::lock_guard lock(mtx);
ready = true;
std::cout << "Work is ready, notifying all workers...\n";
}
cv.notify_all(); // 通知所有等待的线程
}
int main() {
std::vector workers;
// 创建多个线程
for (int i = 1; i <= 5; ++i) {
workers.emplace_back(worker, i);
}
std::thread preparer(prepareWork); // 创建准备线程
// 等待所有线程完成
for (auto& t : workers) {
t.join();
}
preparer.join();
return 0;
}
解释:
notify_all()
:notify_all()
会唤醒所有等待的线程,这里唤醒了 5 个 worker
线程。worker
线程都会在 cv.wait()
中等待,直到 ready
变为 true
并且收到通知。输出示例:
Work is ready, notifying all workers...
Worker 1 is proceeding.
Worker 2 is proceeding.
Worker 3 is proceeding.
Worker 4 is proceeding.
Worker 5 is proceeding.
虚假唤醒(spurious wakeups)是指线程在没有被条件变量显式唤醒的情况下,也可能会被唤醒。这是操作系统的特性,因此我们在使用 wait()
时应该总是配合条件判断来避免这种情况。
为了避免虚假唤醒,C++ 中的 wait()
提供了一种安全的模式,即使用条件判断的 lambda 表达式来确保条件满足时才继续执行。
cv.wait(lock, [] { return ready; });
这个版本的 wait()
会在 ready
为 false
时继续阻塞线程,直到条件真正满足。
wait_for()
和 wait_until()
除了 wait()
之外,C++11 还提供了 wait_for()
和 wait_until()
方法,允许我们指定等待的时间。如果在指定的时间内条件未满足,线程会超时并继续执行。
wait_for()
:阻塞线程一段时间,超时后线程自动返回。wait_until()
:阻塞线程直到某个时间点,超时后线程自动返回。wait_for()
超时等待#include
#include
#include
#include
#include
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker() {
std::unique_lock lock(mtx);
if (cv.wait_for(lock, std::chrono::seconds(3), [] { return ready; })) {
std::cout << "Work is ready, proceeding...\n";
} else {
std::cout << "Timeout, work is not ready.\n";
}
}
void prepareWork() {
std::this_thread::sleep_for(std::chrono::seconds(5)); // 模拟准备时间
{
std::lock_guard lock(mtx);
ready = true;
std::cout << "Work is prepared, notifying worker...\n";
}
cv.notify_one();
}
int main() {
std::thread t1(worker);
std::thread t2(prepareWork);
t1.join();
t2.join();
return 0;
}
解释:
wait_for()
:等待 3 秒,如果 ready
在 3 秒内没有变为 true
,线程会超时并继续执行。输出示例:
Timeout, work is not ready.
Work is prepared, notifying worker...
wait_until()
等待到指定时间点#include
#include
#include
#include
#include
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker() {
auto timeout = std::chrono::system_clock::now() + std::chrono::seconds(3);
std::unique_lock lock(mtx);
if (cv.wait_until(lock, timeout, [] { return ready; })) {
std::cout << "Work is ready, proceeding...\n";
} else {
std::cout << "Timeout, work is not ready.\n";
}
}
void prepareWork() {
std::this_thread::sleep_for(std::chrono::seconds(5)); // 模拟准备时间
{
std::lock_guard lock(mtx);
ready = true;
std::cout << "Work is prepared, notifying worker...\n";
}
cv.notify_one();
}
int main() {
std::thread t1(worker);
std::thread t2(prepareWork);
t1.join();
t2.join();
return 0;
}
解释:
wait_until()
:等待到指定时间点,如果在这个时间点前 ready
变为 true
,线程会继续执行;否则超时。输出示例:
Timeout, work is not ready.
Work is prepared, notifying worker...
std::condition_variable::wait
用于协调线程之间的同步,它可以让一个线程在等待某个条件满足时进入等待状态,并通过条件变量的 notify_one()
或 notify_all()
来唤醒线程。wait()
需要与 std::mutex
和 std::unique_lock
配合使用,以保证线程在等待期间不会造成数据竞争。wait_for()
和 wait_until()
提供了超时等待机制,允许线程在指定的时间内等待条件满足,否则超时返回。