线程 条件变量

 条件变量
  前一节中我们讲述了如何使用互斥锁来实现线程间数据的共享和通信,互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。
  条件变量的结构为pthread_cond_t,函数pthread_cond_init()被用来初始化一个条件变量。它的原型为:
  
extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));


  其中cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向结构pthread_condattr_t的指针。结构pthread_condattr_t是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。释放一个条件变量的函数为pthread_cond_ destroy(pthread_cond_t cond)。 

  函数pthread_cond_wait()使线程阻塞在一个条件变量上。它的函数原型为:


  extern int pthread_cond_wait __P ((pthread_cond_t *__cond,
  pthread_mutex_t *__mutex));


  线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数pthread_cond_signal和函数pthread_cond_broadcast唤醒,但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如一个变量是否为0等等,这一点我们从后面的例子中可以看到。线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。这个过程一般用while语句实现。

  另一个用来阻塞线程的函数是pthread_cond_timedwait(),它的原型为:


  extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond,
  pthread_mutex_t *__mutex, __const struct timespec *__abstime));


  它比函数pthread_cond_wait()多了一个时间参数,经历abstime段时间后,即使条件变量不满足,阻塞也被解除。

  函数pthread_cond_signal()的原型为:


  extern int pthread_cond_signal __P ((pthread_cond_t *__cond));


  它用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的。要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足信号又可能在测试条件和调用pthread_cond_wait函数之间被发出,从而造成无限制的等待。下面是使用函数pthread_cond_wait()和函数pthread_cond_signal()的一个简单的例子。


pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count () {
pthread_mutex_lock (&count_lock);
while(count==0) 
pthread_cond_wait( &count_nonzero, &count_lock);
count=count -1;
pthread_mutex_unlock (&count_lock);
}


increment_count(){
pthread_mutex_lock(&count_lock);
if(count==0)
pthread_cond_signal(&count_nonzero);
count=count+1;
pthread_mutex_unlock(&count_lock);
}


  count值为0时,decrement函数在pthread_cond_wait处被阻塞,并打开互斥锁count_lock。此时,当调用到函数increment_count时,pthread_cond_signal()函数改变条件变量,告知decrement_count()停止阻塞。读者可以试着让两个线程分别运行这两个函数,看看会出现什么样的结果。
  函数pthread_cond_broadcast(pthread_cond_t *cond)用来唤醒所有被阻塞在条件变量cond上的线程。这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用这个函数。
<think>我们正在处理一个关于多线程编程中条件变量的使用示例的请求。条件变量通常与互斥锁结合使用,以实现线程间的同步。下面是一个典型的生产者-消费者问题示例,其中生产者线程将数据放入共享缓冲区,而消费者线程从缓冲区取出数据。当缓冲区为空时,消费者线程等待;当缓冲区满时,生产者线程等待。条件变量用于在缓冲区状态改变时通知等待的线程。 注意:我们将使用C语言pthread库来展示,因为条件变量在多线程编程中很常见,而pthread是POSIX标准线程库。 步骤: 1. 定义共享数据:一个固定大小的缓冲区,以及用于跟踪缓冲区状态的变量(如计数、读/写位置)。 2. 定义互斥锁(mutex)两个条件变量(一个用于生产者,一个用于消费者)。 3. 生产者线程:在向缓冲区添加数据前,先获取互斥锁,然后检查缓冲区是否已满。如果满了,则等待在条件变量上(同时释放互斥锁,等待被唤醒后重新获得锁)。当条件满足(缓冲区有空位)时,将数据放入缓冲区,并通知消费者条件变量(可能有数据可消费了)。 4. 消费者线程:类似地,在取出数据前,获取互斥锁,检查缓冲区是否为空。如果为空,则等待在条件变量上。当被唤醒后,取出数据,并通知生产者条件变量(可能有空位了)。 注意:使用条件变量时,检查条件必须使用while循环而不是if,因为可能存在虚假唤醒(spurious wakeup)。 下面是一个简单的示例代码,其中缓冲区大小为5,生产者生产数字(0到99),消费者消费并打印数字。</think>### 线程条件变量使用示例(生产者-消费者模型) 以下是一个使用条件变量实现多线程同步的完整示例(C++11标准),展示生产者-消费者工作模式: ```cpp #include <iostream> #include <queue> #include <thread> #include <mutex> #include <condition_variable> #include <chrono> std::queue<int> data_queue; // 共享数据缓冲区 std::mutex mtx; // 互斥锁保护共享数据 std::condition_variable cond_var; // 条件变量 // 生产者线程函数 void producer(int id) { for (int i = 0; i < 5; ++i) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产过程 // 获取互斥锁 std::unique_lock<std::mutex> lock(mtx); // 生产数据 int data = id * 100 + i; data_queue.push(data); std::cout << "生产者" << id << "生成数据: " << data << std::endl; // 通知等待的消费者 cond_var.notify_one(); } } // 消费者线程函数 void consumer(int id) { while (true) { // 获取互斥锁 std::unique_lock<std::mutex> lock(mtx); // 使用while循环避免虚假唤醒 while (data_queue.empty()) { // 等待条件满足(自动释放锁) cond_var.wait(lock); // 唤醒后自动重新获取锁 } // 消费数据 int data = data_queue.front(); data_queue.pop(); std::cout << "消费者" << id << "处理数据: " << data << std::endl; // 解锁(unique_lock离开作用域自动解锁) } } int main() { // 创建2个生产者线程2个消费者线程 std::thread producers[] = { std::thread(producer, 1), std::thread(producer, 2) }; std::thread consumers[] = { std::thread(consumer, 1), std::thread(consumer, 2) }; // 等待生产者完成 for (auto& t : producers) t.join(); // 等待消费者处理剩余数据(实际应用中应有终止机制) std::this_thread::sleep_for(std::chrono::seconds(1)); // 强制结束消费者(仅用于演示) for (auto& t : consumers) t.detach(); return 0; } ``` #### 关键机制说明: 1. **条件变量核心操作**: - `cond_var.wait(lock)`:原子操作,释放锁 + 进入等待状态 - `cond_var.notify_one()`:唤醒一个等待线程 - `cond_var.notify_all()`:唤醒所有等待线程 2. **同步流程**: ```mermaid graph TD A[消费者获取互斥锁] --> B{缓冲区空?} B -->|是| C[调用cond_var.wait] C --> D[释放锁+等待] D -->|被唤醒| E[重新获取锁] E --> B B -->|否| F[消费数据] F --> G[释放锁] ``` 3. **为什么使用`while`检查条件**: - 防止虚假唤醒(spurious wakeups) - 确保条件真正满足后才继续执行[^3][^5] 4. **互斥锁与条件变量协作**: - 互斥锁保护共享数据访问 - 条件变量管理线程等待/通知机制 - `wait()`自动释放锁,避免死锁[^3] #### 典型输出: ``` 生产者1生成数据: 100 消费者1处理数据: 100 生产者2生成数据: 200 消费者2处理数据: 200 生产者1生成数据: 101 消费者1处理数据: 101 ... ``` #### 应用场景: 1. 线程池任务调度 2. 消息队列系统 3. 事件驱动架构 4. 流水线数据处理[^1][^5] > **注意**:实际应用中应添加终止机制,示例中的无限循环仅为演示同步原理[^3][^5]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值