std::thread线程通知、等待、让渡
代码
看到很多c++ 程序员使用线程循环的时候使用sleep 去取队列中的数据,这个做法没有大问题,但很多时候会浪费cpu,很多更会把效率降低,因为我们实在不知道要等待多长时间,不能随便写个值,常规的做法还是要使用线程通知。如下代码所示
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
// 全局互斥锁和条件变量
std::mutex mutex_;
std::condition_variable cv;
bool signal = false;
// 线程函数
void threadFunction() {
std::unique_lock<std::mutex> lock(mutex_);
while (true) {
//cv.wait(lock);
cv.wait(lock, [] { return signal; });
// 收到信号后执行的代码
std::cout << "Thread received signal and proceeding." << std::endl;
// 在这里可以执行线程的主要任务
// 执行完任务后重置信号,准备下一次等待
signal = false;
}
}
int main() {
std::thread t(threadFunction);
for (int i = 0; i < 150; ++i)
{
{
std::lock_guard<std::mutex> guard(mutex_);
signal = true;
}
cv.notify_one();
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
t.detach();
return 0;
}
知识
1 cv.wait(lock, [] { return signal; });是用于线程等待一个条件满足的操作。
它会暂时阻塞当前线程的执行,直到满足特定的条件(在这里是signal为true)。lock是一个std::unique_lockstd::mutex对象。std::unique_lock是一种智能锁,它与std::mutex(互斥锁)配合使用,用于管理互斥锁的所有权。在调用cv.wait时,这个锁会被自动释放(这是为了允许其他线程获取互斥锁并修改共享资源,从而有可能使等待的条件满足),当条件满足后,线程被唤醒,锁会再次被获取,以保证线程对共享资源的访问是安全的。
2 [] { return signal; }参数(谓词):
这是一个 lambda 表达式,作为cv.wait的谓词(predicate)。它定义了等待的条件。cv.wait的工作方式是,当线程执行到这个函数时,它首先检查谓词返回的值。如果谓词返回true,那么线程就不会进入等待状态,而是继续执行;如果谓词返回false,则线程会释放锁并进入等待状态,直到被其他线程通过cv.notify_one()或cv.notify_all()唤醒。唤醒后,线程会重新获取锁,并再次检查谓词。只有当谓词返回true时,线程才会真正继续执行后续的代码。在这个例子中,当signal变量为true时,谓词返回true,线程就可以继续执行了。
3 与条件变量的关系
条件变量(cv,std::condition_variable类型)用于实现线程间的同步,它允许一个线程等待某个条件变为真。cv.wait是条件变量的关键操作之一,通过结合互斥锁和谓词,它能够有效地实现线程的等待和唤醒机制,确保线程能够在合适的时机执行,避免了忙等待(即线程不断地检查条件是否满足,浪费 CPU 资源)的情况。
4 其他做法
需要线程yield的时候,可以让渡线程
void threadFunction2() {
for (int i = 0; i < 5; ++i) {
std::cout << "Thread is running: " << i << std::endl;
// 线程主动让出执行权
std::this_thread::yield();
}
}
int main() {
std::thread t(threadFunction2);
for (int i = 0; i < 5; ++i) {
std::cout << "Main thread is running: " << i << std::endl;
std::this_thread::yield();
}
t.join();
return 0;
}
这个示例中,无论是在自定义的threadFunction线程中还是在主线程中,当调用std::this_thread::yield时,当前线程会主动暂停自己的执行,使得操作系统有机会调度其他线程运行。这样可以在一定程度上实现线程之间的公平性或者用于一些特定的同步场景,比如当一个线程需要等待某个资源被其他线程释放,但又不想进入阻塞状态时,可以通过yield来让其他线程有机会获取 CPU 时间片来完成资源的释放操作。也就是让操作系统公平对待线程,这样避免不必要的线程浪费。