线程同步之条件变量:pthread_cond_signal和pthread_cond_wait

在多线程编程下,常常出现A线程要等待B线程条件完成后再继续进行,这里等待方式有两种:

1.使用锁+轮询

使用这种方法可以很简单的实现,但是会有一定的性能消耗,其还有一个点要好好把握,就是一次轮询没有结果后相隔多久进行下一次的轮询,间隔时间太短,消耗的CPU资源较多,间隔时间太长,不能很及时的响应请求

所以这种方法不是推荐。

2.使用条件变量的线程同步(推荐)

采用阻塞和消息方式可以极大程度上减少资源的浪费以及增加实时性

线程条件变量pthread_cond_t

线程等待某个条件

int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); 

通知函数

通知所有的线程

int pthread_cond_broadcast(pthread_cond_t *cond); 

只通知一个线程

int pthread_cond_signal(pthread_cond_t *cond); 

---------------------------------------------------------------------------------

正确的使用方法

pthread_cond_wait用法:

pthread_mutex_lock(&mutex);

while(condition_is_false)

{

        pthread_cond_wait(&cond,&mutex);

}

condition_is_false=true;  //此操作是带锁的,也就是说只有一个线程同时进入这块

pthread_mutex_unlock(&mutex);

 ----------------------------------------------------

pthread_cond_signal用法: 

pthread_mutex_lock(&mutex);

condition_is_false=false;

pthread_cond_signal(&cond)

pthread_mutex_unlock(&mutex)

--------------------------------------------------------

 记住上面这种用法!!!可以避免误用pthread_cond_broadcast而释放了所有条件变量

下面是pthread_cond_XX的用法

1.初始化条件变量pthread_cond_init

#include

int pthread_cond_init(pthread_cond_t *cv,

const pthread_condattr_t *cattr);

返回值:函数成功返回0;任何其他返回值都表示错误

初始化一个条件变量。当参数cattr为空指针时,函数创建的是一个缺省的条件变量。否则条件变量的属性将由cattr中的属性值来决定。调用 pthread_cond_init函数时,参数cattr为空指针等价于cattr中的属性为缺省属性,只是前者不需要cattr所占用的内存开销。这个函数返回时,条件变量被存放在参数cv指向的内存中。

可以用宏PTHREAD_COND_INITIALIZER来初始化静态定义的条件变量,使其具有缺省属性。这和用pthread_cond_init函数动态分配的效果是一样的。初始化时不进行错误检查。如:

pthread_cond_t cv = PTHREAD_COND_INITIALIZER;

不能由多个线程同时初始化一个条件变量。当需要重新初始化或释放一个条件变量时,应用程序必须保证这个条件变量未被使用。

2.阻塞在条件变量上pthread_cond_wait

#include

int pthread_cond_wait(pthread_cond_t *cv,

pthread_mutex_t *mutex);

返回值:函数成功返回0;任何其他返回值都表示错误

函数将解锁mutex参数指向的互斥锁,并使当前线程阻塞在cv参数指向的条件变量上。

被阻塞的线程可以被pthread_cond_signal函数,pthread_cond_broadcast函数唤醒,也可能在被信号中断后被唤醒。

pthread_cond_wait函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。

pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。

一般一个条件表达式都是在一个互斥锁的保护下被检查。当条件表达式未被满足时,线程将仍然阻塞在这个条件变量上。当另一个线程改变了条件的值并向条件变量发出信号时,等待在这个条件变量上的一个线程或所有线程被唤醒,接着都试图再次占有相应的互斥锁。

阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait()函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。如:

pthread_mutex_lock();

while (condition_is_false)

pthread_cond_wait();

pthread_mutex_unlock();

阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。

注意:pthread_cond_wait()函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条件变量相关的互斥锁仍将处在锁定状态。

3.解除在条件变量上的阻塞pthread_cond_signal

#include

int pthread_cond_signal(pthread_cond_t *cv);

返回值:函数成功返回0;任何其他返回值都表示错误

函数被用来释放被阻塞在指定条件变量上的一个线程。

必须在互斥锁的保护下使用相应的条件变量。否则对条件变量的解锁有可能发生在锁定条件变量之前,从而造成死锁。

唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER类型的,系统将根据线程的优先级唤醒线程。

如果没有线程被阻塞在条件变量上,那么调用pthread_cond_signal()将没有作用。

4.阻塞直到指定时间pthread_cond_timedwait

#include

#include

int pthread_cond_timedwait(pthread_cond_t *cv,

pthread_mutex_t *mp, const structtimespec * abstime);

返回值:函数成功返回0;任何其他返回值都表示错误

函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数abstime指定。函数返回时,相应的互斥锁往往是锁定的,即使是函数出错返回。

注意:pthread_cond_timedwait函数也是退出点。

超时时间参数是指一天中的某个时刻。使用举例:

pthread_timestruc_t to;

to.tv_sec = time(NULL) + TIMEOUT;

to.tv_nsec = 0;

超时返回的错误码是ETIMEDOUT。

5.释放阻塞的所有线程pthread_cond_broadcast

#include

int pthread_cond_broadcast(pthread_cond_t *cv);

返回值:函数成功返回0;任何其他返回值都表示错误

函数唤醒所有被pthread_cond_wait函数阻塞在某个条件变量上的线程,参数cv被用来指定这个条件变量。当没有线程阻塞在这个条件变量上时,pthread_cond_broadcast函数无效。

由于pthread_cond_broadcast函数唤醒所有阻塞在某个条件变量上的线程,这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用pthread_cond_broadcast函数。

6.释放条件变量pthread_cond_destroy

#include

int pthread_cond_destroy(pthread_cond_t *cv);

返回值:函数成功返回0;任何其他返回值都表示错误

释放条件变量。

注意:条件变量占用的空间并未被释放。

7.唤醒丢失问题

在线程未获得相应的互斥锁时调用pthread_cond_signal或pthread_cond_broadcast函数可能会引起唤醒丢失问题。

唤醒丢失往往会在下面的情况下发生:

一个线程调用pthread_cond_signal或pthread_cond_broadcast函数;

另一个线程正处在测试条件变量和调用pthread_cond_wait函数之间;

没有线程正在处在阻塞等待的状态下

最后是两个可以直接运行的程序,可以更好的理解条件变量的运行过程。

(注意:为了验证整个程序的运行过程,添加了多条printf语句)

[cpp]  view plain copy

/*条件变量
互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,
它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的
某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否
满足。一般说来,条件变量被用来进行线程间的同步。
cond1.c
[root@mashang smb]# gcc cond1.c -o cond1 -lpthread
[root@mashang smb]# ./cond1
*/ 
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
 
pthread_mutex_t count_lock;//自旋锁
pthread_cond_t count_nonzero;//条件锁
unsigned count = 0;
 
void *decrement_count(void *arg)
{
    pthread_mutex_lock(&count_lock);//等待线程:1使用pthread_cond_wait前要先加锁
    printf("decrement_count get count_lock\n");
    while(count == 0)
    {
        printf("decrement_count count == 0 \n");
        printf("decrement_count before cond_wait \n");
        pthread_cond_wait(&count_nonzero, &count_lock);//2pthread_cond_wait内部会解锁,然后等待条件变量被其它线程激活
        printf("decrement_count after cond_wait \n");
    }
    printf("tid1:count--\n");
    count = count - 1;
    printf("tid1:count= %d \n", count);
    pthread_mutex_unlock(&count_lock);
}
 
void *increment_count(void *arg)
{
    pthread_mutex_lock(&count_lock);         //激活线程:1加锁(和等待线程用同一个锁)
    printf("increment_count get count_lock \n");
    if(count == 0)
    {
        printf("increment_count before cond_signal \n");
        pthread_cond_signal(&count_nonzero);           //2pthread_cond_signal发送信号
        printf("increment_count after cond_signal \n");
    }
printf("tid2:count++\n");
    count = count + 1;
    printf("tid2:count= %d \n", count);
    pthread_mutex_unlock(&count_lock);//3解锁.激活线程的上面三个操作在运行时间上都在等待线程的pthread_cond_wait函数内部。
}
 
int main(void)
{
    pthread_t tid1, tid2;
 
    pthread_mutex_init(&count_lock, NULL);
    pthread_cond_init(&count_nonzero, NULL);
 
    pthread_create(&tid1, NULL, decrement_count, NULL);
printf("tid1 decrement is created,begin sleep 2s \n");
    sleep(2);
printf("after sleep 2s, start creat tid2 increment \n");
    pthread_create(&tid2, NULL, increment_count, NULL);
printf("after tid2 increment is created,begin sleep 10s \n");
    sleep(5);
printf("after sleep 5s,begin exit!\n");
    pthread_exit(0);
 
    return 0;
}
————————————————
版权声明:本文为CSDN博主「mashang123456789」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/mashang123456789/article/details/9792677


第二个例程:

[cpp]  view plain copy

/*
cond2.c
[root@mashang smb]# gcc cond2.c -o cond2 -lpthread
[root@mashang smb]# ./cond2                       程序运行过程:
counter: 0                                         (1)开始时 counter 为0 (main)
main1: before creating thd1 decrement              (2)创建thd1,进入线程内部:
thd1 decrement get the lock                             先锁定互斥锁,若counter为0,此线程被阻塞在条件变量(count_nonzero)上.
thd1 decrement before cond_wait                         同时释放互斥锁count_lock (wait内部会先释放锁,等待signal激活后自动再加上锁)
main2:thd1 is created, begin create thd2 increment (3)与此同时主程序还在运行,创建另一个线程thrd2,进入程序内部:
thd2 increment get the lock                             先锁定互斥锁,若counter为0, 唤醒在条件变量(count_nonzero)上的线程即thrd1
thd2 increment before cond_signal                       但是由于有互斥锁count_lock,thrd1还是在等待.【signal激活后,wait内部又自动加上锁了】
thd2 increment after  cond_signal                       所以thd2继续运行,count++,释放互斥锁
thd2 increment:counter = 1 
thd1 decrement after cond_wait                      thrd1由于互斥锁释放,重新判断counter是不是为0,如果为0再把线程阻塞在条件变量count_nonzero上这时counter已经为1了.
thd1 decrement:counter = 0                          所以线程继续运行.counter--释放互斥锁
main3:after thd1 and thd2, begin sleep and exit!   (4)退出后,运行主线程main
*/ 
 
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
 
pthread_mutex_t counter_lock;
pthread_cond_t counter_nonzero;
int counter = 0;
int estatus = -1;
 
void *decrement_counter(void *argv);
void *increment_counter(void *argv);
 
int main(int argc, char **argv)
{
    printf("counter: %d\n", counter);
    pthread_t thd1, thd2;
    int ret;
printf("main1: before creating thd1 decrement \n");
    ret = pthread_create(&thd1, NULL, decrement_counter, NULL);//先创建的是等待线程,用pthread_cond_wait
    if(ret){
        perror("del:/n");
        return 1;
    }
printf("main2:thd1 is created, begin create thd2 increment \n");
    ret = pthread_create(&thd2, NULL, increment_counter, NULL);//后创建的是激活线程,用pthread_cond_signal
    if(ret){
        perror("inc: /n");
        return 1;
    }
printf("main3:after thd1 and thd2, begin sleep and exit!\n");
    sleep(3);
    return 0;
}
 
void *decrement_counter(void *argv)
{
    pthread_mutex_lock(&counter_lock);
printf("thd1 decrement get the lock \n");
    while(counter == 0)
    {
     printf("thd1 decrement before cond_wait\n");
        pthread_cond_wait(&counter_nonzero, &counter_lock);
     printf("thd1 decrement after cond_wait \n");
    }
    counter--;
    printf("thd1 decrement:counter = %d \n", counter);
    pthread_mutex_unlock(&counter_lock);
 
    return &estatus;
}
 
void *increment_counter(void *argv)
{
    pthread_mutex_lock(&counter_lock);
printf("thd2 increment get the lock\n");
    if(counter == 0)
    {
     printf("thd2 increment before cond_signal\n");
     pthread_cond_signal(&counter_nonzero);
     printf("thd2 increment after  cond_signal\n");
    }
    counter++;
    printf("thd2 increment:counter = %d \n", counter);
    pthread_mutex_unlock(&counter_lock);
 
    return &estatus;
}
————————————————
版权声明:本文为CSDN博主「mashang123456789」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/mashang123456789/article/details/9792677

  • 1
    点赞
  • 5
    收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值