线程间的同步还有这样一种情况:线程A需要等某个条件成立才能继续往下执行,现在这个条件不成立,线程A就阻塞等待,而线程B在执行过程中使这个条件成立了,就唤醒线程A继续执行。在pthread库中通过条件变量(Condition Variable)来阻塞等待一个条件,或者唤醒等待这个条件的线程。Condition Variable用pthread_cond_t类型的变量表示,可以这样初始化和销毁:

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t*cond);

int pthread_cond_init(pthread_cond_t*restrict cond,

const pthread_condattr_t *restrict attr);

pthread_cond_t cond =PTHREAD_COND_INITIALIZER;

返回值:成功返回0,失败返回错误号。

和Mutex的初始化和销毁类似,pthread_cond_init函数初始化一个Condition Variable,attr参数为NULL则表示缺省属性,pthread_cond_destroy函数销毁一个Condition Variable。如果Condition Variable是静态分配的,也可以用宏定义PTHEAD_COND_INITIALIZER初始化,相当于用pthread_cond_init函数初始化并且attr参数为NULL。ConditionVariable的操作可以用下列函数:

#include <pthread.h>

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);

返回值:成功返回0,失败返回错误号。

可见,一个Condition Variable总是和一个Mutex搭配使用的。一个线程可以调用pthread_cond_wait在一个Condition Variable上阻塞等待,这个函数做以下三步操作:

1、释放Mutex

2、阻塞等待

3、当被唤醒时,重新获得Mutex并返回

pthread_cond_timedwait函数还有一个额外的参数可以设定等待超时,如果到达了abstime所指定的时刻仍然没有别的线程来唤醒当前线程,就返回ETIMEDOUT。一个线程可以调用pthread_cond_signal唤醒在某个Condition Variable上等待的另一个线程,也可以调用pthread_cond_broadcast唤醒在这个Condition Variable上等待的所有线程。

下面的程序演示了一个生产者-消费者的例子,生产者生产一个结构体串在链表的表头上,消费者从表头取走结构体。

#include <stdlib.h>

#include <pthread.h>

#include <stdio.h>

struct msg {

structmsg *next;

intnum;

};

struct msg *head;

pthread_cond_t has_product =PTHREAD_COND_INITIALIZER;

pthread_mutex_t lock =PTHREAD_MUTEX_INITIALIZER;

void *consumer(void *p)

{

structmsg *mp;

for(;;) {

pthread_mutex_lock(&lock);

while(head == NULL)

pthread_cond_wait(&has_product,&lock);

mp= head;

head= mp->next;

pthread_mutex_unlock(&lock);

printf("Consume%d\n", mp->num);

free(mp);

sleep(rand()% 5);

}

}

void *producer(void *p)

{

structmsg *mp;

for(;;) {

mp= malloc(sizeof(struct msg));

mp->num= rand() % 1000 + 1;

printf("Produce%d\n", mp->num);

pthread_mutex_lock(&lock);

mp->next= head;

head= mp;

pthread_mutex_unlock(&lock);

pthread_cond_signal(&has_product);

sleep(rand()% 5);

}

}

int main(int argc, char *argv[])

{

pthread_tpid, cid;

srand(time(NULL));

pthread_create(&pid,NULL, producer, NULL);

pthread_create(&cid,NULL, consumer, NULL);

pthread_join(pid,NULL);

pthread_join(cid,NULL);

return0;

}执行结果如下:

Produce 744

Consume 744

Produce 567

Produce 881

Consume 881

Produce 911

Consume 911

Consume 567

Produce 698

Consume 698

在这个例子中,生产者和消费者访问链表的顺序是LIFO的。

Mutex变量是非0即1的,可看作一种资源的可用数量,初始化时Mutex是1,表示有一个可用资源,加锁时获得该资源,将Mutex减到0,表示不再有可用资源,解锁时释放该资源,将Mutex重新加到1,表示又有了一个可用资源。

信号量(Semaphore)和Mutex类似,表示可用资源的数量,和Mutex不同的是这个数量可以大于1。

这里介绍的是POSIX semaphore库函数,详见sem_overview(7),这种信号量不仅可用于同一进程的线程间同步,也可用于不同进程间的同步。

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared,unsigned int value);

int sem_wait(sem_t *sem);

int sem_trywait(sem_t *sem);

int sem_post(sem_t * sem);

int sem_destroy(sem_t * sem);

semaphore变量的类型为sem_t,sem_init()初始化一个semaphore变量,value参数表示可用资源的数量,pshared参数为0表示信号量用于同一进程的线程间同步,本节只介绍这种情况。在用完semaphore变量之后应该调用sem_destroy()释放与semaphore相关的资源。

调用sem_wait()可以获得资源,使semaphore的值减1,如果调用sem_wait()时semaphore的值已经是0,则挂起等待。如果不希望挂起等待,可以调用sem_trywait()。调用sem_post()可以释放资源,使semaphore的值加1,同时唤醒挂起等待的线程。

上面生产者-消费者的例子是基于链表的,其空间可以动态分配,现在基于固定大小的环形队列重写这个程序:

#include <stdlib.h>

#include <pthread.h>

#include <stdio.h>

#include <semaphore.h>

#define NUM 5

int queue[NUM];

sem_t blank_number, product_number;

void *producer(void *arg)

{

intp = 0;

while(1) {

sem_wait(&blank_number);

queue[p]= rand() % 1000 + 1;

printf("Produce%d\n", queue[p]);

sem_post(&product_number);

p= (p+1)%NUM;

sleep(rand()%5);

}

}

void *consumer(void *arg)

{

intc = 0;

while(1) {

sem_wait(&product_number);

printf("Consume%d\n", queue[c]);

queue[c]= 0;

sem_post(&blank_number);

c= (c+1)%NUM;

sleep(rand()%5);

}

}

int main(int argc, char *argv[])

{

pthread_tpid, cid;

sem_init(&blank_number,0, NUM);

sem_init(&product_number,0, 0);

pthread_create(&pid,NULL, producer, NULL);

pthread_create(&cid,NULL, consumer, NULL);

pthread_join(pid,NULL);

pthread_join(cid,NULL);

sem_destroy(&blank_number);

sem_destroy(&product_number);

return0;

}

Linux系统编程(29)——线程间同步(续篇)的更多相关文章

  1. linux应用编程之进程间同步

    一.描述 在操作系统中,异步并发执行环境下的一组进程,因为相互制约关系,进而互相发送消息.互相合作.互相等待,使得各进程按一定的顺序和速度执行,称为进程间的同步.具有同步关系的一组并发进程,称为合作进 ...

  2. linux系统编程:线程原语

    线程原语 线程概念 线程(thread),有时被称为轻量级进程(Lightweight Process,LWP).是程序运行流的最小单元.一个标准的线程由线程ID.当前指令指针(PC),寄存器集合和堆 ...

  3. Linux系统编程:线程控制

    一.提出问题 问1.线程存在的意义是什么?什么时候适合使用多线程? 答1.在单进程环境中实现多任务,线程可访问其所在进程的资源,例如内存.描述符等.对于单进程,如果要完成多项任务,这些任务只能依次执行 ...

  4. linux系统编程:线程同步-相互排斥量(mutex)

    线程同步-相互排斥量(mutex) 线程同步 多个线程同一时候訪问共享数据时可能会冲突,于是须要实现线程同步. 一个线程冲突的演示样例 #include <stdio.h> #includ ...

  5. linux系统编程:线程同步-信号量(semaphore)

    线程同步-信号量(semaphore) 生产者与消费者问题再思考 在实际生活中,仅仅要有商品.消费者就能够消费,这没问题. 但生产者的生产并非无限的.比如,仓库是有限的,原材料是有限的,生产指标受消费 ...

  6. Linux系统编程 —线程同步概念

    同步概念 同步,指对在一个系统中所发生的事件之间进行协调,在时间上出现一致性与统一化的现象. 但是,对于不同行业,对于同步的理解略有不同.比如:设备同步,是指在两个设备之间规定一个共同的时间参考:数据 ...

  7. Linux 系统编程 学习:11-线程:线程同步

    Linux 系统编程 学习:11-线程:线程同步 背景 上一讲 我们介绍了线程的属性 有关设置.这一讲我们来看线程之间是如何同步的. 额外安装有关的man手册: sudo apt-get instal ...

  8. linux系统编程--线程同步

    同步概念 所谓同步,即同时起步,协调一致.不同的对象,对“同步”的理解方式略有不同. 如,设备同步,是指在两个设备之间规定一个共同的时间参考: 数据库同步,是指让两个或多个数据库内容保持一致,或者按需 ...

  9. Linux系统编程—进程间同步

    我们知道,线程间同步有多种方式,比如:信号量.互斥量.读写锁,等等.那进程间如何实现同步呢?本文介绍两种方式:互斥量和文件锁. 互斥量mutex 我们已经知道了互斥量可以用于在线程间同步,但实际上,互 ...

随机推荐

  1. nginx中时间的管理

    nginx出于性能考虑採用类似lib_event的方式,自己对时间进行了cache,用来降低对gettimeofday()的调用,由于一般来说server对时间的精度要求不是特别的高,只是假设须要比較 ...

  2. Linux下查看所有用户(shell脚本获取)

      在Linux系统中,使用者账号管理最重要的两个文件是/etc/password和/etc/shadow.在/etc/password文件中,每一行都代表一个账号,但是有很多账号是系统账号.比如:b ...

  3. Sqlserver查询表结构信息-字段说明、类型、长度等信息

    Sqlserver 中查询表结构信息-字段说明.类型.长度等信息综合语法. SELECT 表名 = d.name,--case when a.colorder=1 then d.name else ' ...

  4. Eclipse、MyEclipse使用git插件(egit)

    在开发Java.JavaEE等相关程序时,我们会用到Eclipse或者MyEclipse,同时使用到git作为版本控制软件,所以我们需要在这些IDE上集成git插件,而egit正是Eclipse基金会 ...

  5. Freemarker常用技巧(三)

    freemarker模板解析过程 例如:一个freemarker表达式<body> ${hello} </body>,会被解析成三个部分,分别是<body>${he ...

  6. Freemarker常用技巧(二)

    1 list.break指令<#list sequence as item>  ...</#list>tem_index:当前变量的索引值.item_has_next:是否存在 ...

  7. 计算方法(三)C#矩阵类库

    测量平差以及工科中常常用到矩阵的相关运算,因此自己写了一个,同时考虑到了类库的可用性,这次又重载了比较匀运算符,修正了一些问题 using System; using System.Collectio ...

  8. Windows下将硬盘由MBR转为GPT

    打开命令提示符,输入 diskpart 进入diskpart提示符.Win7/Vista用户可以直接在开始菜单的搜索框中输入diskpart回车即可打开diskpart提示符. 在diskpart提示 ...

  9. Php RSS

    RSS 聚合最近非常流行,因此至少对 RSS 及其工作方式有所了解是一名 PHP 开发人员的迫切需要.本文介绍了 RSS 基础知识.RSS 众多用途中的一些用途.如何使用 PHP 从数据库创建 RSS ...

  10. C++拾遗(九)类与动态内存分配(1)

    创建类时编译器自动生成以下成员函数(如果用户没有定义): 1.默认构造函数 2.拷贝构造函数 3.赋值操作符 4.默认析构函数 5.地址操作符 以下一一说明: 构造函数 构造函数在创建对象时被调用. ...