posix多线程程序使用条件变量 pthread_cond_signal

因为类似的问题至少碰到3次以上,虽然很简单但是每次都花了不少时间来定位,所以决定写个demo来演示一下:)程序逻辑比较简单,主线程读入一个整数,分别有两个线程对这个整数求1~n的累加和。代码如下:

#include <iostream>
#include 
<pthread.h>
#include 
<string>
#include 
<unistd.h>

using namespace std;

int Number = 0;
pthread_mutex_t NMutex;
pthread_cond_t NCond;

void *thread1(void *arg)
{
        pthread_detach(pthread_self());
        pthread_mutex_lock(
&NMutex);
        
while (Number <= 0 )//等待主线程读入Number
                pthread_cond_wait(&NCond, &NMutex);
        
int Count = Number;
        
int Sum = 1;
        
for (int i = 1; i < Count; i++)
                Sum 
+= i;
        cout 
<< "count by thread1 is " << Sum << endl;
        pthread_mutex_unlock(
&NMutex);
        
return NULL;
}


void *thread2(void *arg)
{
        pthread_detach(pthread_self());
        pthread_mutex_lock(
&NMutex);
        
while (Number <= 0 )//等待主线程读入Number
                pthread_cond_wait(&NCond, &NMutex);
        
int Count = Number;
        
int Sum = 1;
        
for (int i = 1; i < Count; i++)
                Sum 
+= i;
        cout 
<< "count by thread2 is " << Sum << endl;
        pthread_mutex_unlock(
&NMutex);
        
return NULL;
}


int main(int argc, char* argv[])
{
        pthread_mutex_init(
&NMutex, NULL);
        pthread_cond_init(
&NCond, NULL);
        pthread_t p1, p2;
        pthread_create(
&p1, NULL, thread1, NULL);
        pthread_create(
&p2, NULL, thread2, NULL);

//begin input
        pthread_mutex_lock(&NMutex);
           cout << "input a number ";
        cin 
>> Number;
        pthread_mutex_unlock(
&NMutex);
        pthread_cond_signal(
&NCond);
//end input

        pthread_exit(NULL);
}

运行结果:
input a number
5
count by thread1 is 11
count by thread2 is 11

  结果虽然没错,但是却有一个疑惑,主线程里明明是用了pthread_cond_signal,它应该是只唤醒一个线程,但是为什么thread1和thread2都执行了呢。把pthread_cond_signal换成pthread_cond_broadcast运行结果也是一样的。如果把//begin input 到 //end input这一段代码放到另外一个线程去执行,就只有一个输出了,也就是说pthread_cond_signal就只唤醒了一个线程了。为什么会这样呢?难道在主线程调用pthread_cond_signal有什么特殊性吗?

  其实这里的区别并不在于input段代码是在主线程还是在子线程,而是因为thread1和thread2根本没进入wait状态,分析一下程序的执行过程:
  
主线程创建p1和p2后三个线程(主线程、p1、p2)的执行顺序是未知的,也就是说不知道哪个会先执行。假设主线程先执行,那么主线程获得NMutex锁,p1和p2都在 pthread_mutex_lock(&NMutex);处阻塞。当主线程读入Number后解锁,这时Number已经不为0了,无论p1还是p2获得锁都不会进入wait状态,所以两个线程都顺利执行完。这就是上面运行结果所表现的情况:调用pthread_cond_signal却唤醒两个线程的原因是跟本就没有线程在wait状态,两个线程都只是带等待解锁,即使不调用pthread_cond_signal,运行结果也是一样。

  上面的分析是基于主线程先执行的情况,如果是子线程先执行,结果就是我们期望的了。但是上面的代码并没有保证哪个线程先执行,把input段放到另一个独立的线程(p3)去执行获得了正确结果也是偶然的,如果p3比p1、p2先执行那么结果还是跟上面一样。在这个例子中,要想获得正确的结果就必须有额外的代码保证主线程和子线程的执行顺序(让子线程先执行)。

转载于:https://www.cnblogs.com/wangkangluo1/archive/2011/07/28/2119479.html

<think>首先,用户的问题是:我想了解pthread_cond_wait和pthread_cond_signal使用方法和区别 请问pthread_cond_wait 和 pthread_cond_signal 的作用及使用示例。 根据系统级指令: 1. 所有行内数学表达式必须使用$...$格式。但在这个问题中,没有数学表达式,所以不需要。 2. 独立公式必须使用$$...$$格式并单独成段。同样,没有数学公式。 3. LaTeX语法正确:确保任何数学部分正确。 4. 使用中文回答:整个回答要用中文。 5. 生成相关问题:在回答末尾添加相关问题。 6. 回答中引用的段落末尾自然地添加引用标识,例如[^1]。引用来自提供的参考。 用户可见层指令:回答结构清晰,帮助用户逐步解决问题,保证回答真实可靠。参考提供的引用。 提供的引用内容: - [^1]: 描述了pthread_cond_wait和pthread_cond_signal的基本行为,包括如何工作、互斥锁的使用、唤醒机制等。 - [^2]: 强调pthread_cond_signal只发信号,内部不解锁,并解释了队列机制。 - [^3]: 指出pthread_cond_wait在进入时释放互斥锁,获得条件时重新锁定。 - [^4]: 提到需要深入理解pthread_cond_wait,并给出伪代码。 我需要基于这些引用构建回答,确保引用标识正确添加。 回答结构: 1. **介绍作用**:解释pthread_cond_wait和pthread_cond_signal的基本作用。 2. **使用方法**:描述如何使用它们,包括与互斥锁的配合。 3. **区别**:比较两者的不同点。 4. **使用示例**:提供一个简单的代码示例,展示如何在实际中使用。 5. **注意事项**:包括一些常见问题和最佳实践。 6. **相关问题**:在末尾生成相关问题。 确保回答清晰、逐步。 关键点从引用: - pthread_cond_wait用于阻塞当前线程,等待条件变量被信号唤醒。它必须与互斥锁配套使用。进入wait时释放互斥锁,唤醒时重新获取互斥锁。 - pthread_cond_signal用于唤醒一个等待在条件变量上的线程。如果没有线程等待,它成功返回。它可能唤醒多个线程,但通常只唤醒一个。 - 使用模式:线程在检查条件时,先锁定互斥锁,然后如果条件不满足,调用pthread_cond_wait。其他线程在改变条件后调用pthread_cond_signalpthread_cond_broadcast。 示例代码:提供一个简单的C代码示例,展示生产者-消费者问题或类似场景。 用中文写回答。 生成相关问题:基于主题,如优化、常见错误、应用场景等。</think>### pthread_cond_wait 和 pthread_cond_signal 的作用、使用方法及区别 在多线程编程中,`pthread_cond_wait` 和 `pthread_cond_signal` 是 POSIX 线程库(pthread)中用于线程同步的关键函数,它们基于条件变量condition variable)实现线程间的等待和通知机制。下面我将逐步解释它们的作用、使用方法、区别,并提供一个使用示例。回答基于 POSIX 标准,并参考了相关技术文档[^1][^2][^3]。 #### 1. **作用** - **`pthread_cond_wait` 的作用**: 该函数用于使当前线程进入阻塞状态,等待一个条件变量被信号唤醒。它必须与互斥锁(mutex)配合使用。调用时,线程会自动释放关联的互斥锁,并进入等待队列;当被唤醒后,线程会自动重新获取互斥锁,然后继续执行。这确保了线程在等待条件变化时不会持有锁,从而避免死锁[^1][^3]。 - 例如:在生产者-消费者模型中,消费者线程等待缓冲区非空时调用 `pthread_cond_wait`。 - **`pthread_cond_signal` 的作用**: 该函数用于发送一个信号,唤醒一个正在等待该条件变量的线程(如果有)。如果没有线程在等待,它会成功返回但无效果。它通常只唤醒一个线程(取决于线程优先级和等待时间),减少了“惊群效应”(thundering herd)的风险[^1][^2]。 - 例如:生产者线程向缓冲区添加数据后调用 `pthread_cond_signal` 来通知等待的消费者线程。 #### 2. **使用方法** - **基本使用模式**: `pthread_cond_wait` 和 `pthread_cond_signal` 必须与互斥锁一起使用,以确保对共享数据的原子访问。典型模式如下: - **等待线程(如消费者)**: 1. 锁定互斥锁(`pthread_mutex_lock`)。 2. 检查条件是否满足(如缓冲区是否为空)。 3. 如果条件不满足,调用 `pthread_cond_wait` 释放锁并等待。 4. 被唤醒后,自动重新获取锁,并重新检查条件(防止虚假唤醒)。 5. 处理共享数据(如消费数据)。 6. 解锁互斥锁(`pthread_mutex_unlock`)[^1][^3][^4]。 - **通知线程(如生产者)**: 1. 锁定互斥锁。 2. 修改共享数据(如向缓冲区添加数据)。 3. 调用 `pthread_cond_signal` 唤醒一个等待线程。 4. 解锁互斥锁[^1][^2]。 - **关键注意事项**: - `pthread_cond_wait` 在进入等待时会自动释放互斥锁,唤醒后会自动重新获取锁,这避免了死锁[^3]。 - `pthread_cond_signal` 只发送信号,不涉及解锁操作;解锁由互斥锁管理[^2]。 - 条件检查必须在循环中进行,以防止虚假唤醒(spurious wakeup),即线程可能无缘无故被唤醒,但条件未真正满足[^4]。 - 对于需要唤醒所有等待线程的场景,使用 `pthread_cond_broadcast` 代替 `pthread_cond_signal`。 #### 3. **区别** | 特性 | `pthread_cond_wait` | `pthread_cond_signal` | |------|----------------------|-----------------------| | **功能** | 使线程阻塞等待条件变量 | 唤醒一个等待条件变量的线程 | | **锁操作** | 调用时自动释放互斥锁,唤醒后自动重新获取锁 | 不操作锁,仅发送信号 | | **唤醒机制** | 被动:等待被唤醒 | 主动:触发唤醒操作 | | **线程影响** | 可能阻塞当前线程 | 无阻塞,立即返回 | | **使用场景** | 用于条件不满足时的等待 | 用于条件变化后的通知 | | **唤醒数量** | 不适用 | 通常只唤醒一个线程(优先级或等待时间决定)[^1][^2] | - 核心区别:`pthread_cond_wait` 是“等待者”,依赖于互斥锁管理线程挂起;`pthread_cond_signal` 是“通知者”,仅负责信号传递,不涉及锁状态变更[^1][^2][^3]。 #### 4. **使用示例** 以下是一个简单的生产者-消费者模型示例,展示 `pthread_cond_wait` 和 `pthread_cond_signal` 的实际使用。代码使用 C 语言编写,基于 POSIX 线程库。 ```c #include <pthread.h> #include <stdio.h> // 共享数据:缓冲区大小 #define BUFFER_SIZE 10 int buffer[BUFFER_SIZE]; int count = 0; // 当前缓冲区数据数量 // 互斥锁和条件变量 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 消费者线程函数 void* consumer(void* arg) { while (1) { pthread_mutex_lock(&mutex); // 锁定互斥锁 // 循环检查条件:如果缓冲区为空,则等待 while (count == 0) { pthread_cond_wait(&cond, &mutex); // 等待信号,自动释放锁 } // 消费数据 int item = buffer[--count]; printf("Consumed item: %d\n", item); pthread_mutex_unlock(&mutex); // 解锁 } return NULL; } // 生产者线程函数 void* producer(void* arg) { int item = 0; while (1) { pthread_mutex_lock(&mutex); // 锁定互斥锁 // 生产数据(如果缓冲区满则等待,但本示例省略满条件检查) if (count < BUFFER_SIZE) { buffer[count++] = item++; printf("Produced item: %d\n", item - 1); // 发送信号唤醒一个消费者线程 pthread_cond_signal(&cond); } pthread_mutex_unlock(&mutex); // 解锁 } return NULL; } int main() { pthread_t prod_thread, cons_thread; // 创建生产者和消费者线程 pthread_create(&prod_thread, NULL, producer, NULL); pthread_create(&cons_thread, NULL, consumer, NULL); // 等待线程结束(实际中应处理线程退出) pthread_join(prod_thread, NULL); pthread_join(cons_thread, NULL); return 0; } ``` **示例解释**: - **消费者线程**:使用 `pthread_cond_wait` 在缓冲区空时等待。它先锁定互斥锁,然后在循环中检查 `count == 0`,如果为真则调用 `pthread_cond_wait` 释放锁并挂起。当生产者发送信号后,消费者被唤醒,自动重新获取锁并消费数据[^1][^3]。 - **生产者线程**:修改共享数据(`count` 和 `buffer`)后,调用 `pthread_cond_signal` 唤醒一个等待的消费者线程[^1][^2]。 - **为什么使用循环检查条件**:防止虚假唤醒(例如,系统事件可能唤醒线程,但条件未变),确保条件真正满足[^4]。 #### 5. **注意事项** - **死锁风险**:如果 `pthread_cond_wait` 调用前未正确锁定互斥锁,或信号发送后未及时解锁,可能导致死锁。始终遵循“锁定-检查-等待”模式[^3][^4]。 - **性能考虑**:`pthread_cond_signal` 在 Linux 中通过队列机制高效唤醒线程,不会直接返回到用户空间,减少开销[^2]。但在多核 CPU 上,可能意外唤醒多个线程,需在代码中处理[^1]。 - **错误处理**:实际代码应检查函数返回值(如 `pthread_cond_wait` 返回 0 表示成功),并处理错误码。 - **替代函数**:需唤醒所有线程时用 `pthread_cond_broadcast`;避免在信号处理函数中调用这些函数,因为它们不是异步信号安全的。 ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值