文档章节

Linux——互斥锁与条件变量(一)

KiteRunner
 KiteRunner
发布于 2014/06/01 22:03
字数 2122
阅读 530
收藏 5

为允许在线程或进程间共享数据,同步通常是必需的。互斥锁和条件变量是同步的基本组成部分。

使用互斥锁和条件变量的经典例子:生产者-消费者问题。在本例中,使用多个线程而不是多个进程,因为让多个线程共享本问题采用的公共数据缓冲区非常简单,而在多个进程间共享一个公共数据缓冲区却需要某种形式的共享内存区。

一、互斥锁:上锁与解锁

互斥锁指代相互排斥mutual exclusion),它是最基本的同步形式。互斥锁用于保护临界区(critical region),以保证任何时刻只有一个线程在执行其中的代码(假设互斥锁由多个线程共享),或者任何时刻只有一个进程在执行其中的代码(假设互斥锁由多个进程共享)。保护一个临界区的代码轮廓大体如下:

lock_the_mutex(...);
临界区
unlock_the_mutex(...);

既然任何时刻只有一个线程能够锁住一个给定的互斥锁,于是这样的代码保证在任何时刻只有一个线程在执行其临界区中的指令。

Posix互斥锁被声明为具有pthread_mutex_t数据类型的变量。如果互斥锁变量是静态分配的,那么我们可以把它初始化成常值PTHREAD_MUTEX_INITIALIZER,例如:

static pthread_mutex_t lock =  PTHREAD_MUTEX_INITIALIZER;

如果互斥锁是动态分配的,或者分配在共享内存区中,那么我们必须在运行之时,通过调用pthread_mutex_init函数来初始化它。后面有所介绍。

在Posix中,定义了三个函数来给一个互斥锁上锁和解锁:

#include<pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mptr);
int pthread_mutex_trylock(pthread_mutex_t *mptr);
int pthread_mutex_unlock(pthread_mutex_t *mptr);
均返回:若成功则为0,若出错则为正的Exxx值

如果尝试给一个已有某个线程锁住的互斥锁上锁,那么pthread_mutex_lock将阻塞到该互斥锁解锁为止。pthread_mutex_trylock是对应的非阻塞函数,如果该互斥锁已锁住,它就返回一个EBUSY错误。

这里有一个问题:

如果有多个线程阻塞在等待同一个互斥锁上,那么当该互斥锁解锁时,哪一个线程会开始运行呢?

===>>>提供一个优先级调度选项,不同线程可被赋予不同的优先级,同步函数(互斥锁、读写锁、信号量)将唤醒优先级最高(队列实现?)的被阻塞线程。

临界区实际上保护的是在临界区中被操纵的数据,也就是说,互斥锁通常用于保护由多个线程或多个进程分享的共享数据(shared data)

互斥锁是协作性锁。这就是说,如果共享数据是一个链表,那么操纵该链表的所有线程都必须在实际操纵前获取该互斥锁。不过也没有办法防止某个线程不首先获取该互斥锁就操纵该链表。

二、生产者-消费者问题

同步中有一个称为“生产者-消费者(producer-consumer)”问题的经典问题,也称为有界缓冲区(bounded buffer)问题。一个或多个生产者(线程或进程)创建者一个个数据条目,然后这些条目由一个或多个消费者(线程或进程)处理。数据条目在生产者和消费者之间是使用某种类型的IPC(interprocess communication)传递的。

当共享存储区用作生产者和消费者之间的IPC形式时,生产者和消费者必须执行某种类型的显式(explicit)同步。我们使用互斥锁展示显式同步。

在这里,我们选择使用单个消费者线程、多个生产者线程的方法。首先,介绍一些相关信息:在单个进程中有多个生产者线程和单个消费者线程。整数数组buff含有被生产和消费的条目(也就是共享数据)。为简单起见,生产者只是把buff[0]设置为0,把buff[1]设置为1,如此等等。消费者只是沿着该数组行进,并验证每个数组元素都是正确的。

然后,写出main函数如下:

#include “unpipc.h”
 
#define MAXNITEMS 1000000
#define MAXNTHREADS 100
 
int nitems;
 
struct{
    pthread_mutex_t mutex;
    int buff[MAXNITEMS];
    int nput;
    int nval;  
}shared= {PTHREAD_MUTEX_INITIALIZER};
 
void *produce(void *),*consumer(void *);//生产者、消费者函数
 
int main(int argc , char **argv){
    int i, nthreads, count[MAXNTHREADS];
    pthread_t tid_produce[MAXNTHREADS],tid_consume;
    if(argc!=3){
        err_quit("usage:prodcons <#items> <#threads>"); 
    }
    nitems = min(atoi(argv[1]), MAXNITEMS);
    nthreads = min(atoi(argv[2]), MAXNTHREADS);
  
    Set_concurrency(nthreads);
  
    /*start all the producer threads*/
    for(i=0;i<nthreads;i++){
    count[i] = 0;
    pthread_create(&tid_produce[i],NULL,produce,&count[i]); 
    }

    /*wait for all producer threads  */
    for(i=0;i<nthreads;i++){
    pthread_join(tid_produce[i],NULL);
    printf("count[%d]=%d",i,count[i]); 
    }
  
    /*start, then wait for the consumer thread*/
    pthread_create(&tid_consume,NULL,consume,NULL);
    pthread_join(tid_consume,NULL);
  
    exit(0);
}
 
void *produce(void *arg){
    for(;;){
        pthread_mutex_lock(&shared.mutex);
        if(shared.nput >= nitems){
        pthread_mutex_unlock(&shared.mutex);
        return NULL; 
        /* array is full,we are done*/
        }
        shared.buff[shared.nput] = shared.nval;
        shared.nput++;
        shared.nval++;
        pthread_mutex_unlock(&shared.mutex);
        *((int *)arg)+=1;
    } 
}
 
void *consume(void *arg){
    int i;
    for(i=0;i<nitems;i++){
        if(shared.buff[i] != i)
        printf("buff[%d] = %d\n",i,shared.buff[i]);
    }
    return NULL;
}

在上面的main函数,我们需要注意这样几个方面:

1、struct shared的作用:

首先我们分析这些成员变量,mutex是互斥锁,buff数组是数据空间,nputbuff数组中下一次存放数据的元素下标,nval是下一次存放的值。我们将这些变量整合放在一个结构体中的目的是为了强调这些变量只应该在拥有互斥锁时访问。我们分配这个结构,并初始化其中用于生产者线程间同步的互斥锁。

对于这里,我们要考虑这样一个问题:将所有受互斥锁控制的临界区变量都整合放在一个结构体中是否十分完善?这是不完善的,把共享数据和它们的同步变量收集到一个结构体中,这是一个很好的编程技巧,然而在很多情况下共享数据是动态分配的,譬如说一个链表,我们可以把一个链表的头及该链表的同步变量存放到一个结构体中,但是其他共享数据(该链表的其他部分)却不在结构体中,因此这种方法是不完善的。

2、命令行输入参数分析:形如“prodcons items threads”,其中,items指定生产者存放的条目数,threads指定待创建生产者线程的数目。

3、Set_concurrency(nthreads)函数的作用:

告诉线程系统我们希望并发运行多少线程。

4、创建生产者线程:

在这里,我们用线程创建函数pthread_create(...)来创建生产者进程,每个线程执行produce。在tid_produce数组中保存每个生成线程的pid。传递给每个生产者线程的参数是指向count数组中某个元素的指针。那么count数组的作用是什么?代码中,我们首先把count计数器初始化为0,然后每个线程在每次往缓冲区中存放一个条目时给这个计数器加1(*(int*)arg)+=1),。当一切生产完毕时,我们输出这个计数器数组中各元素的值,以查看每个生产者线程分别存放了多少条目。

5、等待生产者线程,然后启动消费者线程:

等待所有生产者线程终止,同时输出每个线程的计数器值,此后才启动单个消费者线程。此处,我们实现了生产者与消费者之间的同步问题,接着等待消费者线程完成,然后终止进程。

6、Produce函数产生数据条目:

在这里,我们使用互斥锁来实现对临界区的保护,同时保证在一切生产完毕的情况下给该互斥锁解锁。注意count元素的增加不属于该临界区,因为每个线程有各自的计数器(main函数中的count)。既然如此,我们就不把这行代码包括在由互斥锁锁住的临界区中,因为作为一个通用的编程原则,我们总是应该努力减少由一个互斥锁锁住的代码量。

7、消费者验证数组内容:

这里不需要任何同步。

未完待续!

© 著作权归作者所有

共有 人打赏支持
KiteRunner
粉丝 3
博文 20
码字总数 14816
作品 0
成都
私信 提问
Linux——互斥锁与条件变量(二)

三、对比上锁与等待 在上述的生产者-消费者问题中,我们在实现同步的时候还可以用以下的方法来实现,这里只是说明与上述实现中的不同之处。 1、首先,说明一下此版本中的相关特征: 在此版本...

KiteRunner
2014/06/02
0
0
0-linux 环境编程修炼指南——外功心法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/q1007729991/article/details/52770103 学习交流群: Linux 环境编程 610441700 说明:本系列文章并不能取代 ...

--Allen--
2016/10/09
0
0
Linux多线程编程

一 线程(也称为轻量级的进程) 1.1、线程基本特点 a)线程依赖于进程而存在,在一个进程里面开出的多个线程共享同一进程空间,这样利用多线程实现多任务时,就能避免因为大量频繁的进程间切...

wannneg
2016/04/15
29
0
Linux多线程Pthread学习小结

简介 POSIX thread 简称为pthread,Posix线程是一个POSIX标准线程.该标准定义内部API创建和操纵线程. 作用 线程库实行了POSIX线程标准通常称为pthreads.pthreads是最常用的POSIX系统如Linux...

长平狐
2013/01/06
46
0
Linux多线程Pthread学习小结

简介 POSIX thread 简称为pthread,Posix线程是一个POSIX标准线程.该标准定义内部API创建和操纵线程. 作用 线程库实行了POSIX线程标准通常称为pthreads.pthreads是最常用的POSIX系统如Linux...

晨曦之光
2012/03/02
172
0

没有更多内容

加载失败,请刷新页面

加载更多

设计模式之工厂模式

本篇博文主要翻译这篇文章: https://www.journaldev.com/1392/factory-design-pattern-in-java 由于翻译水平有限,自认为许多地方翻译不恰当,欢迎各位给出宝贵的建议,建议大家去阅读原文。...

firepation
5分钟前
1
0

中国龙-扬科
7分钟前
0
0
简单谈谈vue的过渡动画

在vue中,实现过渡动画一般是下面这样: `<``transition` `name``=``"fade"``>``<``div``></``div``>``</``transition``>` 用一个transition对元素或者组件进行封装. 在过渡的时候,会......

嫣然丫丫丫
13分钟前
1
0
文件及目录处理

file_get_contents file_put_contens fopen r/r+ 只读打开,指针开头 w/w+ 写入打开,指针开头,清空文件,不存创建 a/a+ 追加打开,指针末尾,不存创建 x/x+ 创建模式打开 b 二进制打开 t 文本打开...

关元
15分钟前
0
0
如何在Angular中使用better-scroll插件

由于需要在一个固定的的高度做无限滚动,本来css的overflow-y也可以完成的,奈何安卓不是很流畅,还很生硬,就是用了第三方库better-scroll,配合angular的ng-content。angular的ng-content和...

前端攻城老湿
21分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部