在Linux多线程编程中,线程同步是确保多个线程有序协作的关键技术。当多个线程共享资源时,如果没有适当的同步机制,就会出现数据竞争、死锁等问题。条件变量(Condition Variable)是POSIX线程库提供的一种高效同步机制,它允许线程在特定条件不满足时主动等待,而不是忙等待或不公平地竞争锁资源。
在实际开发中,我们经常会遇到这样的场景:多个线程需要协作完成某项任务,但某些线程却"独占"了执行机会。以售票系统为例,可能出现只有thread 1在处理所有售票请求的情况。这种现象通常由以下原因导致:
条件变量是一种同步原语,它允许线程在某个条件不满足时挂起等待,直到其他线程改变条件并发出通知。条件变量总是与互斥锁配合使用,主要提供三种基本操作:
条件变量的典型使用模式如下:
cpp复制pthread_mutex_lock(&mutex);
while (condition_is_false) {
pthread_cond_wait(&cond, &mutex);
}
// 执行条件满足后的操作
pthread_mutex_unlock(&mutex);
条件变量的初始化有两种方式:
动态初始化:
cpp复制pthread_cond_t cond;
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
静态初始化:
cpp复制pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
销毁条件变量:
cpp复制int pthread_cond_destroy(pthread_cond_t *cond);
注意:只有在没有线程等待该条件变量时才能安全销毁,静态初始化的条件变量不需要显式销毁。
cpp复制int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
pthread_cond_wait执行原子性操作:
mutex当线程被唤醒时:
mutexpthread_cond_wait返回唤醒单个线程:
cpp复制int pthread_cond_signal(pthread_cond_t *cond);
唤醒所有线程:
cpp复制int pthread_cond_broadcast(pthread_cond_t *cond);
下面是一个完整的使用条件变量的示例程序:
cpp复制#include <iostream>
#include <pthread.h>
#include <unistd.h>
#define NUM_THREADS 5
int counter = 0;
pthread_mutex_t counter_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t counter_cond = PTHREAD_COND_INITIALIZER;
void* worker(void* arg) {
long tid = (long)arg;
pthread_mutex_lock(&counter_lock);
while (counter < 10) {
std::cout << "Thread " << tid << " waiting, counter=" << counter << std::endl;
pthread_cond_wait(&counter_cond, &counter_lock);
}
std::cout << "Thread " << tid << " finished, counter=" << counter << std::endl;
pthread_mutex_unlock(&counter_lock);
return NULL;
}
int main() {
pthread_t threads[NUM_THREADS];
// 创建工作线程
for (long i = 0; i < NUM_THREADS; i++) {
pthread_create(&threads[i], NULL, worker, (void*)i);
}
sleep(1); // 确保所有线程都进入等待状态
// 主线程递增计数器并通知等待线程
pthread_mutex_lock(&counter_lock);
for (int i = 0; i < 10; i++) {
counter++;
std::cout << "Main thread increment counter to " << counter << std::endl;
if (counter == 5) {
pthread_cond_broadcast(&counter_cond);
}
}
pthread_cond_broadcast(&counter_cond);
pthread_mutex_unlock(&counter_lock);
// 等待所有线程完成
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
pthread_mutex_destroy(&counter_lock);
pthread_cond_destroy(&counter_cond);
return 0;
}
生产者-消费者模型是多线程编程中的经典问题,它描述了两种不同类型的线程通过共享缓冲区进行协作的过程:
该模型需要解决三个关键问题:
阻塞队列(Blocking Queue)是实现生产者-消费者模型的理想数据结构。它提供了线程安全的入队和出队操作,并在队列满或空时自动阻塞线程。
cpp复制#include <queue>
#include <pthread.h>
template <typename T>
class BlockingQueue {
public:
BlockingQueue(size_t capacity) : capacity_(capacity) {
pthread_mutex_init(&mutex_, NULL);
pthread_cond_init(¬_full_, NULL);
pthread_cond_init(¬_empty_, NULL);
}
~BlockingQueue() {
pthread_mutex_destroy(&mutex_);
pthread_cond_destroy(¬_full_);
pthread_cond_destroy(¬_empty_);
}
void Put(const T& item) {
pthread_mutex_lock(&mutex_);
while (queue_.size() >= capacity_) {
pthread_cond_wait(¬_full_, &mutex_);
}
queue_.push(item);
pthread_cond_signal(¬_empty_);
pthread_mutex_unlock(&mutex_);
}
T Take() {
pthread_mutex_lock(&mutex_);
while (queue_.empty()) {
pthread_cond_wait(¬_empty_, &mutex_);
}
T item = queue_.front();
queue_.pop();
pthread_cond_signal(¬_full_);
pthread_mutex_unlock(&mutex_);
return item;
}
private:
std::queue<T> queue_;
size_t capacity_;
pthread_mutex_t mutex_;
pthread_cond_t not_full_;
pthread_cond_t not_empty_;
};
cpp复制#include "BlockingQueue.hpp"
#include <unistd.h>
void* Producer(void* arg) {
BlockingQueue<int>* queue = (BlockingQueue<int>*)arg;
for (int i = 0; ; i++) {
queue->Put(i);
printf("Produced: %d\n", i);
sleep(1); // 模拟生产耗时
}
return NULL;
}
void* Consumer(void* arg) {
BlockingQueue<int>* queue = (BlockingQueue<int>*)arg;
while (true) {
int item = queue->Take();
printf("Consumed: %d\n", item);
sleep(2); // 模拟消费耗时
}
return NULL;
}
int main() {
BlockingQueue<int> queue(5); // 容量为5的阻塞队列
pthread_t producer, consumer;
pthread_create(&producer, NULL, Producer, &queue);
pthread_create(&consumer, NULL, Consumer, &queue);
pthread_join(producer, NULL);
pthread_join(consumer, NULL);
return 0;
}
阻塞队列的实现天然支持多生产者和多消费者场景,因为所有对共享队列的访问都通过互斥锁保护。下面是多生产多消费的示例:
cpp复制#define NUM_PRODUCERS 3
#define NUM_CONSUMERS 2
void* MultiProducer(void* arg) {
BlockingQueue<int>* queue = (BlockingQueue<int>*)arg;
int thread_id = *(int*)arg;
for (int i = 0; ; i++) {
int item = thread_id * 1000 + i;
queue->Put(item);
printf("Producer %d produced: %d\n", thread_id, item);
usleep(500000); // 500ms
}
return NULL;
}
void* MultiConsumer(void* arg) {
BlockingQueue<int>* queue = (BlockingQueue<int>*)arg;
int thread_id = *(int*)arg;
while (true) {
int item = queue->Take();
printf("Consumer %d consumed: %d\n", thread_id, item);
usleep(1000000); // 1s
}
return NULL;
}
int main() {
BlockingQueue<int> queue(10);
pthread_t producers[NUM_PRODUCERS];
pthread_t consumers[NUM_CONSUMERS];
int producer_ids[NUM_PRODUCERS];
int consumer_ids[NUM_CONSUMERS];
for (int i = 0; i < NUM_PRODUCERS; i++) {
producer_ids[i] = i + 1;
pthread_create(&producers[i], NULL, MultiProducer, &producer_ids[i]);
}
for (int i = 0; i < NUM_CONSUMERS; i++) {
consumer_ids[i] = i + 1;
pthread_create(&consumers[i], NULL, MultiConsumer, &consumer_ids[i]);
}
// 主线程等待
sleep(10);
return 0;
}
虚假唤醒是指线程在没有收到明确信号的情况下从pthread_cond_wait返回的现象。POSIX标准允许这种行为,原因包括:
为了防止虚假唤醒导致的问题,必须始终在循环中检查条件:
cpp复制pthread_mutex_lock(&mutex);
while (condition_is_false) { // 必须用while而不是if
pthread_cond_wait(&cond, &mutex);
}
// 处理条件满足的情况
pthread_mutex_unlock(&mutex);
条件变量必须与互斥锁配合使用,主要原因包括:
错误的实现方式可能导致信号丢失:
cpp复制// 错误的实现 - 可能导致信号丢失
pthread_mutex_lock(&mutex);
if (condition_is_false) {
pthread_mutex_unlock(&mutex);
pthread_cond_wait(&cond, &mutex); // 这里存在竞态条件
pthread_mutex_lock(&mutex);
}
pthread_mutex_unlock(&mutex);
为了提高代码的可重用性和安全性,我们可以封装条件变量:
cpp复制class Condition {
public:
explicit Condition(Mutex& mutex) : mutex_(mutex) {
pthread_cond_init(&cond_, NULL);
}
~Condition() {
pthread_cond_destroy(&cond_);
}
void Wait() {
pthread_cond_wait(&cond_, mutex_.GetPthreadMutex());
}
bool WaitForSeconds(int seconds) {
struct timespec abstime;
clock_gettime(CLOCK_REALTIME, &abstime);
abstime.tv_sec += seconds;
return ETIMEDOUT == pthread_cond_timedwait(&cond_, mutex_.GetPthreadMutex(), &abstime);
}
void Notify() {
pthread_cond_signal(&cond_);
}
void NotifyAll() {
pthread_cond_broadcast(&cond_);
}
private:
Mutex& mutex_;
pthread_cond_t cond_;
};
pthread_cond_signal:唤醒一个线程,开销较小pthread_cond_broadcast:唤醒所有线程,开销较大死锁问题:
pthread_cond_wait前已获取互斥锁信号丢失问题:
性能瓶颈:
| 同步机制 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 条件变量 | 复杂的条件等待 | 高效等待,不消耗CPU | 使用较复杂,容易出错 |
| 互斥锁 | 简单的临界区保护 | 简单直接 | 无法处理复杂同步需求 |
| 信号量 | 资源计数 | 灵活,可处理多种场景 | 不如条件变量高效 |
| 自旋锁 | 短时间等待 | 无上下文切换开销 | 长时间等待浪费CPU |
在线程池实现中,条件变量常用于工作线程的休眠与唤醒:
cpp复制class ThreadPool {
public:
ThreadPool(size_t thread_num) : stop_(false) {
for (size_t i = 0; i < thread_num; ++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>
auto Enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex_);
if(stop_)
throw std::runtime_error("enqueue on stopped ThreadPool");
tasks_.emplace([task](){ (*task)(); });
}
condition_.notify_one();
return res;
}
~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_;
};
条件变量可用于实现读写锁,支持多个读者或单个写者:
cpp复制class RWLock {
public:
RWLock() : readers_(0), writers_(0), active_writers_(0) {}
void ReadLock() {
std::unique_lock<std::mutex> lock(mutex_);
while (writers_ > 0 || active_writers_ > 0) {
reader_cond_.wait(lock);
}
readers_++;
}
void ReadUnlock() {
std::unique_lock<std::mutex> lock(mutex_);
readers_--;
if (readers_ == 0) {
writer_cond_.notify_one();
}
}
void WriteLock() {
std::unique_lock<std::mutex> lock(mutex_);
writers_++;
while (readers_ > 0 || active_writers_ > 0) {
writer_cond_.wait(lock);
}
writers_--;
active_writers_++;
}
void WriteUnlock() {
std::unique_lock<std::::mutex> lock(mutex_);
active_writers_--;
if (writers_ > 0) {
writer_cond_.notify_one();
} else {
reader_cond_.notify_all();
}
}
private:
std::mutex mutex_;
std::condition_variable reader_cond_;
std::condition_variable writer_cond_;
int readers_;
int writers_;
int active_writers_;
};
屏障(barrier)是一种同步原语,它允许多个线程在某个点等待,直到所有线程都到达该点:
cpp复制class Barrier {
public:
explicit Barrier(size_t count) : threshold_(count), count_(count), generation_(0) {}
void Wait() {
std::unique_lock<std::mutex> lock(mutex_);
size_t gen = generation_;
if (--count_ == 0) {
generation_++;
count_ = threshold_;
cond_.notify_all();
return;
}
while (gen == generation_) {
cond_.wait(lock);
}
}
private:
std::mutex mutex_;
std::condition_variable cond_;
size_t threshold_;
size_t count_;
size_t generation_;
};
| 特性 | Linux (pthreads) | Windows |
|---|---|---|
| 初始化 | pthread_cond_init或静态初始化 |
InitializeConditionVariable |
| 销毁 | pthread_cond_destroy |
无显式销毁 |
| 等待 | pthread_cond_wait |
SleepConditionVariableCS或SleepConditionVariableSRW |
| 信号 | pthread_cond_signal |
WakeConditionVariable |
| 广播 | pthread_cond_broadcast |
WakeAllConditionVariable |
C++11引入了<condition_variable>头文件,提供了跨平台的条件变量实现:
cpp复制#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
void data_preparation_thread() {
for (int i = 0; i < 10; ++i) {
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<std::mutex> lk(mtx);
data_queue.push(i);
std::cout << "Prepared data " << i << std::endl;
}
cv.notify_one();
}
}
void data_processing_thread() {
while (true) {
std::unique_lock<std::mutex> lk(mtx);
cv.wait(lk, []{return !data_queue.empty();});
int data = data_queue.front();
data_queue.pop();
lk.unlock();
std::cout << "Processed data " << data << std::endl;
if (data == 9) break;
}
}
int main() {
std::thread t1(data_preparation_thread);
std::thread t2(data_processing_thread);
t1.join();
t2.join();
return 0;
}
问题现象:程序偶尔会挂起,不再响应
排查步骤:
ps -eLf查看线程状态gdb attach附加到进程可能原因:
在某些高性能场景中,可以将无锁数据结构与条件变量结合使用:
cpp复制template<typename T>
class HybridBlockingQueue {
public:
void Push(const T& item) {
// 无锁操作
while (true) {
Node* old_tail = tail.load();
if (old_tail->next.compare_exchange_weak(nullptr, new Node(item))) {
tail.compare_exchange_weak(old_tail, old_tail->next);
std::lock_guard<std::mutex> lock(cv_mutex);
cv.notify_one();
return;
}
}
}
T Pop() {
// 需要条件变量等待
std::unique_lock<std::mutex> lock(cv_mutex);
while (empty()) {
cv.wait(lock);
}
// 无锁出队操作
// ...
}
private:
struct Node {
T data;
Node* next;
Node(const T& data) : data(data), next(nullptr) {}
};
std::atomic<Node*> head;
std::atomic<Node*> tail;
std::mutex cv_mutex;
std::condition_variable cv;
};
在网络编程中,可以将条件变量与epoll/kqueue等IO多路复用机制结合:
cpp复制class EventLoop {
public:
void WakeUp() {
{
std::lock_guard<std::mutex> lock(mutex_);
pending_ = true;
}
cv_.notify_one();
}
void Run() {
while (!quit_) {
// 处理IO事件
int num_events = epoll_wait(epoll_fd_, events_, MAX_EVENTS, -1);
// 处理定时器
// 处理唤醒事件
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [this]{ return pending_; });
pending_ = false;
}
}
private:
int epoll_fd_;
struct epoll_event events_[MAX_EVENTS];
std::mutex mutex_;
std::condition_variable cv_;
bool pending_ = false;
bool quit_ = false;
};
std::condition_variable_any是更灵活的条件变量实现,可以与任何满足BasicLockable要求的锁类型一起使用:
cpp复制#include <iostream>
#include <mutex>
#include <shared_mutex>
#include <condition_variable>
#include <thread>
std::condition_variable_any cv;
std::shared_mutex mtx;
bool ready = false;
void reader(int id) {
std::shared_lock<std::shared_mutex> lock(mtx);
cv.wait(lock, []{ return ready; });
std::cout << "Reader " << id << " got the signal\n";
}
void writer() {
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::unique_lock<std::shared_mutex> lock(mtx);
ready = true;
}
cv.notify_all();
}
int main() {
std::thread readers[5];
for (int i = 0; i < 5; ++i) {
readers[i] = std::thread(reader, i);
}
std::thread writer_thread(writer);
for (auto& t : readers) t.join();
writer_thread.join();
return 0;
}
对于简单的线程间通信,可以使用std::promise和std::future:
cpp复制#include <future>
#include <thread>
#include <iostream>
void worker(std::promise<int> result_promise) {
std::this_thread::sleep_for(std::chrono::seconds(1));
result_promise.set_value(42);
}
int main() {
std::promise<int> promise;
std::future<int> result = promise.get_future();
std::thread t(worker, std::move(promise));
std::cout << "Waiting for result..." << std::endl;
std::cout << "Result: " << result.get() << std::endl;
t.join();
return 0;
}
在多线程项目开发中,正确使用条件变量需要注意以下几点:
一个实用的技巧是使用RAII包装条件变量等待:
cpp复制class ConditionWaiter {
public:
ConditionWaiter(std::mutex& mtx, std::condition_variable& cv)
: lock_(mtx), cv_(cv) {}
template<typename Predicate>
void Wait(Predicate pred) {
cv_.wait(lock_, pred);
}
template<typename Predicate, typename Rep, typename Period>
bool WaitFor(Predicate pred, const std::chrono::duration<Rep, Period>& timeout) {
return cv_.wait_for(lock_, timeout, pred);
}
private:
std::unique_lock<std::mutex> lock_;
std::condition_variable& cv_;
};
这样使用时更安全、更直观:
cpp复制ConditionWaiter(mtx, cv).Wait([] { return !queue.empty(); });