在多线程编程中,线程之间的同步是一个非常重要的问题。C++11引入了std::condition_variable
,它是一种用于线程间同步的工具,允许一个或多个线程等待某个条件成立,或者通知其他线程条件已经成立。本文将详细介绍std::condition_variable
的使用方法、典型应用场景以及注意事项。
std::condition_variable
是C++11标准库中提供的一种同步原语,用于在多线程环境中实现线程间的条件等待和通知。它通常与std::mutex
一起使用,以确保线程在等待条件时能够正确地释放锁,并在条件满足时重新获取锁。
条件变量的核心思想是:一个线程可以等待某个条件成立,而另一个线程可以在条件成立时通知等待的线程。这种机制非常适合用于实现生产者-消费者模型、线程池等并发模式。
要使用std::condition_variable
,首先需要包含头文件<condition_variable>
,然后创建一个std::condition_variable
对象:
#include <condition_variable> std::condition_variable cv;
线程可以使用std::condition_variable::wait
函数来等待某个条件成立。wait
函数需要一个std::unique_lock<std::mutex>
对象作为参数,并且在等待时会自动释放锁,直到条件满足时重新获取锁。
std::mutex mtx; std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{ return condition; });
在上面的代码中,cv.wait
会一直等待,直到condition
为true
。wait
函数会自动释放lock
,并在条件满足时重新获取锁。
当一个线程满足某个条件时,可以使用std::condition_variable::notify_one
或std::condition_variable::notify_all
来通知等待的线程。
notify_one
:唤醒一个等待的线程。notify_all
:唤醒所有等待的线程。{ std::lock_guard<std::mutex> lock(mtx); condition = true; } cv.notify_one(); // 或者 cv.notify_all();
在上面的代码中,notify_one
会唤醒一个等待的线程,而notify_all
会唤醒所有等待的线程。
生产者-消费者模型是多线程编程中的一个经典问题。生产者线程生成数据并将其放入缓冲区,而消费者线程从缓冲区中取出数据并进行处理。条件变量可以很好地解决生产者和消费者之间的同步问题。
#include <iostream> #include <thread> #include <queue> #include <mutex> #include <condition_variable> std::queue<int> buffer; std::mutex mtx; std::condition_variable cv; const int max_buffer_size = 10; void producer() { for (int i = 0; i < 20; ++i) { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{ return buffer.size() < max_buffer_size; }); buffer.push(i); std::cout << "Produced: " << i << std::endl; lock.unlock(); cv.notify_all(); } } void consumer() { while (true) { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{ return !buffer.empty(); }); int value = buffer.front(); buffer.pop(); std::cout << "Consumed: " << value << std::endl; lock.unlock(); cv.notify_all(); } } int main() { std::thread prod(producer); std::thread cons(consumer); prod.join(); cons.join(); return 0; }
在上面的代码中,生产者线程在缓冲区未满时生成数据并放入缓冲区,消费者线程在缓冲区不为空时从缓冲区中取出数据。条件变量cv
用于同步生产者和消费者线程。
线程池是一种常见的并发模式,它通过预先创建一组线程来处理任务。条件变量可以用于线程池中的任务调度。
#include <iostream> #include <thread> #include <vector> #include <queue> #include <mutex> #include <condition_variable> #include <functional> class ThreadPool { public: ThreadPool(size_t num_threads) { for (size_t i = 0; i < num_threads; ++i) { workers.emplace_back([this] { while (true) { std::function<void()> task; { std::unique_lock<std::mutex> lock(this->queue_mutex); this->condition.wait(lock, [this]{ return this->stop || !this->tasks.empty(); }); if (this->stop && this->tasks.empty()) { return; } task = std::move(this->tasks.front()); this->tasks.pop(); } task(); } }); } } template<class F, class... Args> void enqueue(F&& f, Args&&... args) { { std::unique_lock<std::mutex> lock(queue_mutex); tasks.emplace([=]{ return f(args...); }); } condition.notify_one(); } ~ThreadPool() { { std::unique_lock<std::mutex> lock(queue_mutex); stop = true; } condition.notify_all(); for (std::thread &worker : workers) { worker.join(); } } private: std::vector<std::thread> workers; std::queue<std::function<void()>> tasks; std::mutex queue_mutex; std::condition_variable condition; bool stop = false; }; int main() { ThreadPool pool(4); for (int i = 0; i < 8; ++i) { pool.enqueue([i] { std::cout << "Task " << i << " is running" << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "Task " << i << " is done" << std::endl; }); } return 0; }
在上面的代码中,线程池中的线程会等待任务队列中的任务,并在任务到来时执行任务。条件变量condition
用于通知线程有新任务到来。
虚假唤醒是指线程在没有收到通知的情况下从wait
函数中返回。为了防止虚假唤醒,通常需要在wait
函数中使用一个谓词来检查条件是否真正满足。
cv.wait(lock, []{ return condition; });
在上面的代码中,wait
函数会在条件满足时返回,从而避免虚假唤醒。
在使用条件变量时,如果锁的使用不当,可能会导致死锁。例如,如果一个线程在持有锁的情况下调用wait
,而另一个线程在持有锁的情况下调用notify
,可能会导致死锁。
为了避免死锁,通常需要在调用wait
之前释放锁,并在wait
返回时重新获取锁。
条件变量的性能通常比忙等待(busy-waiting)要好,因为它可以让线程在等待时进入睡眠状态,从而减少CPU的占用。然而,条件变量的性能也受到锁的竞争和上下文切换的影响,因此在设计多线程程序时需要仔细考虑锁的粒度和条件变量的使用。
std::condition_variable
提供了wait_for
和wait_until
函数,允许线程在等待条件时设置超时时间。
std::cv_status status = cv.wait_for(lock, std::chrono::seconds(1)); if (status == std::cv_status::timeout) { // 超时处理 }
在上面的代码中,wait_for
函数会在1秒后返回,如果条件仍未满足,则返回std::cv_status::timeout
。
条件变量通常与std::mutex
或std::unique_lock
结合使用,以确保线程在等待条件时能够正确地释放锁,并在条件满足时重新获取锁。
std::mutex mtx; std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{ return condition; });
在上面的代码中,wait
函数会自动释放lock
,并在条件满足时重新获取锁。
std::condition_variable
是C++11中用于线程间同步的重要工具,它允许线程等待某个条件成立,并在条件满足时通知其他线程。通过合理地使用条件变量,可以实现复杂的多线程同步机制,如生产者-消费者模型、线程池等。然而,在使用条件变量时也需要注意虚假唤醒、死锁等问题,以确保程序的正确性和性能。
希望本文能够帮助你更好地理解和使用C++11中的std::condition_variable
。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。