条件变量:等待与信号发送

使用互斥锁虽然可以解决一些资源竞争的问题,但互斥锁只有两种状态(加锁和解锁),这限制了互斥锁的用途。

条件变量(条件锁)也可以解决线程同步和共享资源访问的问题,条件变量是对互斥锁的补充,它允许一个线程阻塞并等待另一个线程发送的信号,当收到信号时,阻塞的线程被唤醒并试图锁定与之相关的互斥锁。

条件变量初始化

条件变量和互斥锁一样,都有静态动态两种创建方式,静态方式使用PTHREAD_COND_INITIALIZER常量,如下:

pthread_cond_t
cond = PTHREAD_COND_INITIALIZER

动态方式调用函数int
pthread_cond_init,API定义如下:

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

条件变量的属性由参数attr指定,如果参数attr为NULL,那么就使用默认的属性设置。尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。多线程不能同时初始化一个条件变量,因为这是原子操作。如果函数调用成功,则返回0,并将新创建的条件变量的ID放在参数cond中。

解除条件变量

int pthread_cond_destroy(pthread_cond_t *cond);
调用destroy函数解除条件变量并不会释放存储条件变量的内存空间。

条件变量阻塞(等待)

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

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

等待有两种方式:条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEDOUT,结束等待,其中abstime以与系统调用time相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。

无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()或pthread_cond_timedwait()(下同)的竞争条件(Race
Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者自适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。阻塞时处于解锁状态。

激活

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行,如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。
共享变量的状态改变必须遵守lock/unlock的规则:需要在同一互斥锁的保护下使用pthread_cond_signal(即pthread_cond_wait必须放在pthread_mutex_lockpthread_mutex_unlock之间)否则条件变量可以在对关联条件变量的测试和pthread_cond_wait带来的阻塞之间获得信号,这将导致无限期的等待(死锁)。因为他要根据共享变量的状态来决定是否要等待,所以为了避免死锁,必须要在lock/unlock队中。
共享变量的状态改变必须遵守lock/unlock的规则:pthread_cond_signal即可以放在pthread_mutex_lockpthread_mutex_unlock之间,也可以放在pthread_mutex_lockpthread_mutex_unlock之后,但是各有优缺点。
若为前者,在某些线程的实现中,会造成等待线程从内核中唤醒(由于cond_signal)然后又回到内核空间(因为cond_wait返回后会有原子加锁的行为),所以一来一回会有性能的问题(上下文切换)。详细来说就是,当一个等待线程被唤醒的时候,它必须首先加锁互斥量(参见pthread_cond_wait()执行步骤)。如果线程被唤醒而此时通知线程任然锁住互斥量,则被唤醒线程会立刻阻塞在互斥量上,等待通知线程解锁该互斥量,引起线程的上下文切换。当通知线程解锁后,被唤醒线程继续获得锁,再一次的引起上下文切换。这样导致被唤醒线程不能顺利加锁,延长了加锁时间,加重了系统不必要的负担。但是在LinuxThreads或者NPTL里面,就不会有这个问题,因为在Linux 线程中,有两个队列,分别是cond_wait队列和mutex_lock队列, cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,不会有性能的损耗,因此Linux推荐这种形式。
而后者不会出现之前说的那个潜在的性能损耗,因为在signal之前就已经释放锁了。但如果unlocksignal之前,有个低优先级的线程正在mutex上等待的话,那么这个低优先级的线程就会抢占高优先级的线程(cond_wait的线程)。而且,假设而这在上面的放中间的模式下是不会出现的。
而对于pthread_cond_broadcast函数,它使所有由参数cond指向的条件变量阻塞的线程退出阻塞状态,如果没有阻塞线程,则函数无效。
实例代码如下
void *produce_cond(void *arg)
{
for(;;)
{
pthread_mutex_lock(&put.mutex);
if(put.nput >= nitems)
{
pthread_mutex_unlock(&put.mutex);
return NULL;
}
buff[put.nput]=put.nval;
put.nput++;
put.nval++;
pthread_mutex_unlock(&put.mutex);
pthread_mutex_lock(&nready.mutex);
if(nready.nready == 0)
printf("produce_cond nready==0\n");
pthread_cond_signal(&nready.cond);
nready.nready++;
pthread_mutex_unlock(&nready.mutex);
*((int *)arg)+=1;
}
} void *consume_cond(void *arg)
{
int i;
for(i=0;i<3;i++)
{
pthread_mutex_lock(&nready.mutex);
printf("consume_cond nready =%d\n",&nready);
while(nready.nready == 0)
pthread_cond_wait(&nready.cond,&nready.mutex);
nready.nready--;
pthread_mutex_unlock(&nready.mutex);
printf("consume_cond\n");
if(buff[i]!=i)
{
printf("buff[%d]=%d\n",i,buff[i]);
}
}
}
produce_cond中给用来统计准备好由消费者处理的条目数的计数器nready.nready加一。在加1之前,如果该计数器的值为0,就调用pthread_cond_signal唤醒可能正在等待其值变为非零的任意消费者线程。该计数器是在生产者和消费者之间共享的。因此只有锁住与之关联的互斥所nready.mutex时才能访问它。
consume_cond中消费者只是等待计数器nready.nready变为非零,既然该计数器是在所有生产者和消费者之间共享的。那么只有锁住与之关联的互斥锁时才能测试它的值。如果在锁住该互斥锁期间该计数器的值为0,我们就调用pthread_cond_wait进入睡眠,该函数原子地执行以下两个动作:
1 给互斥锁nread.mutex解锁
2 把调用线程投入睡眠,直到另外某个线程就本条件变量调用pthread_cond_signal.
pthread_cond_wait在返回前重新给互斥锁nready.mutex上锁。因此当它返回并且我们发现计数器nready.nready不为0时,我们就该把计数器减一,然后给该互斥锁解锁

linux c编程:互斥锁条件变量的更多相关文章

  1. Linux多线程编程详细解析----条件变量 pthread_cond_t

    Linux操作系统下的多线程编程详细解析----条件变量 1.初始化条件变量pthread_cond_init #include <pthread.h> int pthread_cond_ ...

  2. Linux系统编程 —互斥量mutex

    互斥量mutex 前文提到,系统中如果存在资源共享,线程间存在竞争,并且没有合理的同步机制的话,会出现数据混乱的现象.为了实现同步机制,Linux中提供了多种方式,其中一种方式为互斥锁mutex(也称 ...

  3. Linux Shell编程(8)——变量详解

    不同与许多其他的编程语言,Bash不以"类型"来区分变量.本质上来说,Bash变量是字符串,但是根据环境的不同,Bash允许变量有整数计算和比较.其中的决定因素是变量的值是不是只含 ...

  4. Linux Shell编程(6)——变量替换

    变量的名字是它的值保存的地方.引用它的值称为变量替换.$让我们仔细地区别变量和变量的值.如果variable1是一个变量的名字,那么$variable1就是引用这个变量的值――即这个变量它包含的数据. ...

  5. Linux Shell编程(7)——变量赋值

    =赋值操作符(它的左右两边不能有空白符) 不要搞混了=和-eq,-eq是比赋值操作更高级的测试.注意:等于号(=)根据环境的不同它可能是赋值操作符也可能是一个测试操作符.例子:简单的变量赋值 #!/b ...

  6. Linux系统编程—条件变量

    条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用.条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等 ...

  7. MFC编程入门之八(对话框:创建对话框类和添加控件变量)

    创建好对话框资源后要做的就是生成对话框类了.生成对话框类主要包括新建对话框类.添加控件变量和控件的消息处理函数. 例程Addition是基于对话框的程序,所以程序自动创建了对话框模板IDD_ADDIT ...

  8. Linux Shell编程变量赋值和引用

    我们可以使用任意一种文字编辑器,比如gedit.kedit.emacs.vi等来编写shell脚本,它必须以如下行开始(必须放在文件的第一行):   #!/bin/sh  ...  注意:最好使用“! ...

  9. 【转】VS2010/MFC编程入门之八(对话框:创建对话框类和添加控件变量)

    原文网址:http://www.jizhuomi.com/software/153.html 前两讲中鸡啄米为大家讲解了如何创建对话框资源.创建好对话框资源后要做的就是生成对话框类了.鸡啄米再声明下, ...

随机推荐

  1. 快速解读GC日志

    本文是 Plumbr 发行的 Java垃圾收集指南 的部分内容.文中将介绍GC日志的输出格式, 以及如何解读GC日志, 从中提取有用的信息.我们通过 -XX:+UseSerialGC 选项,指定JVM ...

  2. What is Web Application Architecture? How It Works, Trends, Best Practices and More

    At Stackify, we understand the amount of effort that goes into creating great applications. That’s w ...

  3. TPM--Trusted Platform Module

    trouSerS是IBM的一帮牛人搞的TSS软件栈,提供了与TPM交互的API,从而可以让我们方便地编写应用程序. 地址:https://sourceforge.net/projects/trouse ...

  4. 3G模块(U6300)linux下拨号上网

    U6300支持linux.Android系列嵌入式系统.作为linux内核系统,系统均会自带驱动usbserial,就没有提供专门的U6300V的USB驱动,都是加载系统的usbserial以实现对U ...

  5. 转 java调用php的webService

    1.首先先下载php的webservice包:NuSOAP,自己到官网去下载,链接就不给出来了,自己去google吧    基于NoSOAP我们写了一个php的webservice的服务端,例子如下: ...

  6. HTTP与HTTPS异同/HTTP1.0与HTTP1.1差别

    HTTP状态码 分类 解释 描述 1XX 信息 服务器收到请求,需要请求者继续执行操作 2XX 成功 操作被成功接收并处理 3XX 重定向 需要进一步的操作以完成请求 4XX 客户端错误 请求包含语法 ...

  7. vim 将tab转为空格

    在vimrc中添加以下选项 set expandtab 会将tab转换为空格,如果要输入一个tab则需要Ctrl-V<Tab>来实现 set tabstop= 会将tab转换为4个空格 使 ...

  8. PAT005 Path in a Heap

    题目: Insert a sequence of given numbers into an initially empty min-heap H. Then for any given index ...

  9. php 知乎爬虫

    http://blog.jobbole.com/88788/ https://github.com/owner888/phpspider 费了半天劲安装了redis,导出cookie,发现仍是缺失很多 ...

  10. html 调用ActiveX

    html网页调用ActiveX控件时,要获取到ActiveX的ClassID,这个ClassID是注册到系统里的,而不是工程中的uuid,(下图为uuid). 正确的是在注册表的HKEY_CLASSE ...