C++中的条件变量(condition_variable)详解:小白版
在编程中,我们经常需要处理多个任务,这些任务可能需要同时运行,也可能需要按照一定的顺序运行。这就涉及到了线程的概念。线程就像是一个小程序,它可以在程序中独立运行,而且可以和其他线程并行执行。
但是,有时候我们需要控制线程的执行顺序,比如有两个线程A和B,我们希望A执行完后,B才能开始执行。这就需要一种机制来同步线程的执行,这就是条件变量(std::condition_variable
)的作用。
1. 什么是条件变量?
条件变量是一种特殊的变量,它可以让一个线程在某个条件成立之前等待,当条件成立时,这个线程就可以继续执行。条件变量通常和另一种叫做互斥锁(std::mutex
)的东西一起使用,互斥锁可以保证在同一时间只有一个线程能访问某个资源。
2. 条件变量是如何工作的?
假设我们有两个线程A和B,我们希望A执行完后,B才能开始执行。我们可以这样做:
- 创建一个条件变量和一个互斥锁。
- 在A线程中,我们先锁定互斥锁,然后执行A线程的任务,任务完成后,我们解锁互斥锁,并通知条件变量。
- 在B线程中,我们也先锁定互斥锁,然后让B线程等待条件变量。当A线程通知条件变量后,B线程就会被唤醒,然后执行B线程的任务。
3. 条件变量的主要方法
条件变量有三个主要的方法:
wait
:这个方法会让当前线程等待,直到条件变量被通知。notify_one
:这个方法会唤醒一个等待的线程。notify_all
:这个方法会唤醒所有等待的线程。
4. 条件变量的使用实例
让我们通过一个简单的实例来理解条件变量的使用。假设我们有两个线程,一个生产者线程和一个消费者线程。生产者线程负责生成数据,消费者线程负责处理数据。我们希望当生产者线程生成了数据后,消费者线程才开始处理数据。
首先,我们需要包含必要的头文件,并定义一些全局变量:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false; // 用于表示数据是否已经生成
然后,我们定义生产者线程的函数:
void producer() {
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟数据生成过程
std::lock_guard<std::mutex> lock(mtx);
ready = true; // 数据已经生成
cv.notify_one(); // 通知消费者线程
}
接着,我们定义消费者线程的函数:
void consumer() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{return ready;}); // 等待数据生成
std::cout << "Consumer thread is processing data...\n";
// 模拟数据处理过程
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Data processed.\n";
}
最后,我们在主函数中启动这两个线程:
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
运行这个程序,你会看到消费者线程会等待生产者线程生成数据,当数据生成后,消费者线程才开始处理数据。
5. 生产者消费者问题的扩展
在上述的生产者消费者问题中,我们只有一个生产者和一个消费者。但在实际的应用中,我们可能会有多个生产者和消费者。此时,我们需要使用一个队列来存储数据,生产者将数据放入队列,消费者从队列中取出数据。
首先,我们需要定义一个队列和一些全局变量:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue; // 数据队列
bool finished = false; // 用于表示生产者是否已经完成数据生成
然后,我们定义生产者线程的函数:
void producer(int id) {
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟数据生成过程
std::lock_guard<std::mutex> lock(mtx);
data_queue.push(i);
std::cout << "Producer " << id << " produced data " << i << std::endl;
cv.notify_one(); // 通知消费者线程
}
}
接着,我们定义消费者线程的函数:
void consumer(int id) {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{return !data_queue.empty() || finished;}); // 等待数据生成
while (!data_queue.empty()) {
int data = data_queue.front();
data_queue.pop();
std::cout << "Consumer " << id << " consumed data " << data << std::endl;
// 模拟数据处理过程
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if (data_queue.empty() && finished) {
break;
}
}
}
最后,我们在主函数中启动这些线程:
int main() {
std::thread producers[2];
std::thread consumers[2];
for (int i = 0; i < 2; ++i) {
producers[i] = std::thread(producer, i + 1);
consumers[i] = std::thread(consumer, i + 1);
}
for (auto &p : producers) {
p.join();
}
finished = true;
cv.notify_all();
for (auto &c : consumers) {
c.join();
}
return 0;
}
在这个例子中,我们有两个生产者和两个消费者。生产者将数据放入队列,消费者从队列中取出数据。当所有的生产者都完成数据生成后,我们设置finished
为true
,并通知所有的消费者线程。
这就是如何使用条件变量来解决多生产者和多消费者的问题。通过使用条件变量,我们可以实现更复杂的线程同步需求。