条件变量(Condition Variable)详解

角色与功能

条件变量在多线程编程中扮演着非常重要的角色,它用于阻塞一个或多个线程,直到某个特定条件为真。这种机制允许线程在等待某个条件成立时释放其占用的资源(如互斥锁),从而避免了忙等待(busy-waiting)造成的资源浪费。当条件满足时,其他线程或同一线程可以被唤醒并继续执行。

条件变量通常与互斥锁(mutex)一起使用,因为多个线程可能会同时等待同一个条件,且需要确保在检查条件(predicate)和修改条件变量状态时互斥访问共享资源。

工作原理
  1. 等待(Wait):线程在调用条件变量的wait函数前,必须先锁定一个互斥锁。wait函数会原子地释放互斥锁并使当前线程进入睡眠状态,直到另一个线程调用条件变量的notify_onenotify_all函数来唤醒它。被唤醒后,线程会重新尝试获取之前释放的互斥锁,然后继续执行。

  2. 通知(Notify):当条件变量的条件变为真时,另一个线程(或可能是同一个线程在条件被改变后)可以调用notify_one来唤醒等待该条件变量的一个线程,或者调用notify_all来唤醒所有等待的线程。注意,被唤醒的线程不会立即继续执行,它们必须重新获取互斥锁。

使用条件变量进行线程同步的示例

以下是一个使用C++标准库中的std::condition_variablestd::mutex进行线程同步的示例。在这个例子中,我们有一个生产者线程和多个消费者线程,它们通过条件变量来同步对共享资源的访问。

#include <iostream>  
#include <thread>  
#include <mutex>  
#include <condition_variable>  
#include <queue>  
  
std::mutex mtx;  
std::condition_variable cv;  
std::queue<int> q;  
bool ready = false; // 用来控制是否开始消费  
  
void producer(int id) {  
    for (int i = 0; i < 10; ++i) {  
        std::unique_lock<std::mutex> lck(mtx);  
        q.push(i);  
        std::cout << "Producer " << id << " produced " << i << std::endl;  
        ready = true; // 通知可能有消费者可以开始消费  
        cv.notify_one(); // 唤醒一个等待的消费者  
        lck.unlock();  
  
        // 模拟生产耗时  
        std::this_thread::sleep_for(std::chrono::milliseconds(100));  
    }  
}  
  
void consumer(int id) {  
    std::unique_lock<std::mutex> lck(mtx);  
    while (!ready) cv.wait(lck); // 等待直到有产品可以消费  
  
    while (true) {  
        while (q.empty()) {  
            ready = false; // 如果没有产品,重置ready标志  
            cv.wait(lck); // 等待产品到来  
        }  
  
        int value = q.front();  
        q.pop();  
        std::cout << "Consumer " << id << " consumed " << value << std::endl;  
  
        // 模拟消费耗时  
        std::this_thread::sleep_for(std::chrono::milliseconds(200));  
    }  
}  
  
int main() {  
    std::thread producers[10];  
    std::thread consumers[5];  
  
    // 创建生产者线程  
    for (int i = 0; i < 10; ++i) {  
        producers[i] = std::thread(producer, i);  
    }  
  
    // 创建消费者线程  
    for (int i = 0; i < 5; ++i) {  
        consumers[i] = std::thread(consumer, i);  
    }  
  
    // 等待所有生产者完成  
    for (auto& t : producers) {  
        t.join();  
    }  
  
    // 注意:在实际应用中,可能需要一种机制来优雅地停止消费者线程  
    // 这里仅作为示例,未实现停止消费者的逻辑  
  
    return 0;  
}

注意:这个示例为了简化而省略了一些重要的细节,比如如何优雅地停止消费者线程。在实际应用中,你可能需要设置一个额外的条件来通知消费者线程停止工作,并且需要确保所有线程都正确处理了共享资源的访问。

此外,由于ready标志在多个线程之间共享,并且用于控制消费者的启动和停止,因此必须谨慎使用以避免竞

07-12 00:07