文档章节

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

KiteRunner
 KiteRunner
发布于 2014/06/01 22:03
字数 2122
阅读 513
收藏 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
Linux多线程编程

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

wannneg
2016/04/15
29
0
0-linux 编程学习笔记导航

学习交流群: Linux 环境编程 610441700 说明:本系列文章并不能取代 《APUE》这本旷世之作,文章中难免有错误与不足之处,希望读者们遇到有疑问的地方可以加群互相交流,共同进步。写这一系...

q1007729991
2016/10/09
0
0
漏洞预警 Linux内核出现多个漏洞

  漏洞概述   Linux是一种开源电脑操作系统内核。它是一个用C语言写成,符合POSIX标准的类Unix操作系统。漏洞发生在 Linux 内核中,可造成越界访问或释放后再使用的安全问题。   漏洞影...

FreeBuf
05/18
0
0
【C++11 并发编程教程 - Part 3 : 锁的进阶与条件变量(bill译)】

C++11 并发编程教程 - Part 3 : 锁的进阶与条件变量 注:文中凡遇通用的术语及行话,均不予以翻译。译文有不当之处还望悉心指正。 原文:C++11 Concurrency Tutorial – Part 3: Advanced l...

技术小胖子
2017/11/02
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Kali Linux Docker 練習

docker pull kalilinux/kali-linux-docker docker run -t -i kalilinux/kali-linux-docker /bin/bash apt-get update apt-get install htop apt-get install nmap apt-get install wpscan ap......

BaiyuanLab
15分钟前
0
0
通俗大白话来理解TCP协议的三次握手和四次分手

最近在恶补计算机网络方面的知识,之前对于TCP的三次握手和四次分手也是模模糊糊,对于其中的细节更是浑然不知,最近看了很多这方面的知识,也在系统的学习计算机网络,加深自己的CS功底,就...

onedotdot
20分钟前
1
0
TiDB 在爱奇艺的应用及实践

爱奇艺,中国高品质视频娱乐服务提供者,2010 年 4 月 22 日正式上线,推崇品质、青春、时尚的品牌内涵如今已深入人心,网罗了全球广大的年轻用户群体,积极推动产品、技术、内容、营销等全方...

TiDB
25分钟前
0
0
Web系统大规模并发:电商秒杀与抢购

一、大规模并发带来的挑战 在过去的工作中,我曾经面对过5w每秒的高并发秒杀功能,在这个过程中,整个Web系统遇到了很多的问题和挑战。如果Web系统不做针对性的优化,会轻而易举地陷入到异常...

xtof
今天
3
0
代码质量管理平台-sonarqube

在工作中,往往开发的时候会不怎么注重代码质量的人很多,存在着很多的漏洞和隐患等问题,sonarqube可以进行代码质量的审核,而且十分的残酷。。。。。接下来我们说下怎么安装 进入官网下载:...

落叶清风
今天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部