linux 互斥锁和条件变量
为什么有条件变量?
请参看一个线程等待某种事件发生
注意:本文是linux c版本的条件变量和互斥锁(mutex),不是C++的。
mutex : mutual exclusion(相互排斥)
1,互斥锁的初始化,有以下2种方式。
- 调用方法的初始化:互斥锁是用malloc动态分配,或者分配在内存共享区的时候使用。
- 不调用方法的初始化:静态分配的时候使用。
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
- 返回值:成功0;失败errno
2,互斥锁的销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 返回值:成功0;失败errno
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
3,加锁和解锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- pthread_mutex_lock:加锁。如果是没有加锁的状态,则加锁并返回不阻塞。如果是已经被加锁的状态,这阻塞在这里,并一直等待,直到解锁。
- pthread_mutex_trylock:尝试去加锁。如果是没有加锁的状态,则加锁并返回不阻塞。果是已经被加锁的状态,则不阻塞,立即返回,返回值为EBUSY。
4,条件变量的2个函数
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_wait:
调用此函数时点的处理:
1,给互斥锁解锁。
2,把调用此函数的线程投入睡眠,直到另外某个线程就本条件变量调用pthread_cond_signal。
被唤醒后的处理:返回前重新给互斥锁加锁。
pthread_cond_signal:唤醒调用pthread_cond_wait函数的线程
条件变量通常用于生产者和消费者模式。
什么是生成者和消费者模式?

版本1:所有生产者线程是并行执行的,消费者线程是等待所有的生产者线性执行结束后,消费者线程才开始执行。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define MAXITEM 100000000
#define MAXTHREAD 100
#define min(x,y) ( x>y?y:x )
int nitem;
struct {
pthread_mutex_t mutex;
int buf[MAXITEM];
int idx;
int val;
}shared = {
PTHREAD_MUTEX_INITIALIZER
};
void* produce(void*);
void* consume(void*);
int main(int argc, char** argv){
int i;
int nthreads;
int count[MAXTHREAD];
pthread_t tid_produce[MAXTHREAD], tid_consume;
if(argc != 3){
printf("arg error\n");
return 1;
}
nitem = min(MAXITEM,atoi(argv[1]));
nthreads = min(MAXTHREAD, atoi(argv[2]));
for(i = 0; i < nthreads; ++i){
count[i] = 0;
pthread_create(&tid_produce[i], NULL, produce, &count[i]);
}
for(i = 0; i < nthreads; ++i){
pthread_join(tid_produce[i], NULL);
printf("cout[%d] = %d\n", i, count[i]);
}
pthread_create(&tid_consume, NULL, consume, NULL);
pthread_join(tid_consume, NULL);
return 0;
}
void* produce(void* arg){
while(1){
pthread_mutex_lock(&shared.mutex);
if(shared.idx >= nitem){
pthread_mutex_unlock(&shared.mutex);
return NULL;
}
shared.buf[shared.idx] = shared.val;
shared.idx++;
shared.val++;
pthread_mutex_unlock(&shared.mutex);
*((int*)arg) +=1;
}
}
void* consume(void* arg){
int i;
for(i = 0; i < nitem; ++i){
if(shared.buf[i] != i){
printf("buf[%d] = %d\n", i, shared.buf[i]);
}
}
}
版本2:所有生产者线程和消费者线程都是并行执行的。这时会有个问题,就是消费者线程被先执行的情况下,生产者线程还没有生产数据,这时消费者线程就只能循环给互斥锁解锁又上锁。这成为轮转(spinning)或者轮询(polling),是一种多CPU时间的浪费。我们也可以睡眠很短的一段时间,但是不知道睡多久。这时,条件变量就登场了。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define MAXITEM 100000000
#define MAXTHREAD 100
#define min(x,y) ( x>y?y:x )
int nitem;
struct {
pthread_mutex_t mutex;
int buf[MAXITEM];
int idx;
int val;
}shared = {
PTHREAD_MUTEX_INITIALIZER
};
void* produce(void*);
void* consume(void*);
int main(int argc, char** argv){
int i;
int nthreads;
int count[MAXTHREAD];
pthread_t tid_produce[MAXTHREAD], tid_consume;
if(argc != 3){
printf("arg error\n");
return 1;
}
nitem = min(MAXITEM,atoi(argv[1]));
nthreads = min(MAXTHREAD, atoi(argv[2]));
for(i = 0; i < nthreads; ++i){
count[i] = 0;
pthread_create(&tid_produce[i], NULL, produce, &count[i]);
}
pthread_create(&tid_consume, NULL, consume, NULL);
for(i = 0; i < nthreads; ++i){
pthread_join(tid_produce[i], NULL);
printf("cout[%d] = %d\n", i, count[i]);
}
pthread_join(tid_consume, NULL);
return 0;
}
void* produce(void* arg){
while(1){
pthread_mutex_lock(&shared.mutex);
if(shared.idx >= nitem){
pthread_mutex_unlock(&shared.mutex);
return NULL;
}
shared.buf[shared.idx] = shared.val;
shared.idx++;
shared.val++;
pthread_mutex_unlock(&shared.mutex);
*((int*)arg) +=1;
}
}
void consume_wait(int i){
while(1){
pthread_mutex_lock(&shared.mutex);
if(i < shared.idx){
pthread_mutex_unlock(&shared.mutex);
return;
}
pthread_mutex_unlock(&shared.mutex);
}
}
void* consume(void* arg){
int i;
for(i = 0; i < nitem; ++i){
consume_wait(i);
if(shared.buf[i] != i){
printf("buf[%d] = %d\n", i, shared.buf[i]);
}
}
return NULL;
}
版本3:所有生产者线程和消费者线程都是并行执行的。解决版本2的轮询问题。使用条件变量。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define MAXITEM 100000000
#define MAXTHREAD 100
#define min(x,y) ( x>y?y:x )
int nitem;
int buf[MAXITEM];
struct {
pthread_mutex_t mutex;
int idx;
int val;
} shared = {
PTHREAD_MUTEX_INITIALIZER
};
struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
int nready;
} nready = {
PTHREAD_MUTEX_INITIALIZER,
PTHREAD_COND_INITIALIZER
};
void* produce(void*);
void* consume(void*);
int main(int argc, char** argv){
int i;
int nthreads;
int count[MAXTHREAD];
pthread_t tid_produce[MAXTHREAD], tid_consume;
if(argc != 3){
printf("arg error\n");
return 1;
}
nitem = min(MAXITEM,atoi(argv[1]));
nthreads = min(MAXTHREAD, atoi(argv[2]));
for(i = 0; i < nthreads; ++i){
count[i] = 0;
pthread_create(&tid_produce[i], NULL, produce, &count[i]);
}
pthread_create(&tid_consume, NULL, consume, NULL);
for(i = 0; i < nthreads; ++i){
pthread_join(tid_produce[i], NULL);
printf("cout[%d] = %d\n", i, count[i]);
}
pthread_join(tid_consume, NULL);
return 0;
}
void* produce(void* arg){
while(1){
pthread_mutex_lock(&shared.mutex);
if(shared.idx >= nitem){
pthread_mutex_unlock(&shared.mutex);
return NULL;
}
buf[shared.idx] = shared.val;
shared.idx++;
shared.val++;
pthread_mutex_unlock(&shared.mutex);
pthread_mutex_lock(&nready.mutex);
if(nready.nready == 0){
pthread_cond_signal(&nready.cond);//--------------②
}
nready.nready++;
pthread_mutex_unlock(&nready.mutex);//--------------③
*((int*) arg) += 1;
}
}
void* consume(void* arg){
int i;
for(i = 0; i < nitem; ++i){
pthread_mutex_lock(&nready.mutex);
while(nready.nready == 0){//--------------①
pthread_cond_wait(&nready.cond, &nready.mutex);
}
nready.nready--;
pthread_mutex_unlock(&nready.mutex);
if(buf[i] != i){
printf("buf[%d] = %d\n", i, buf[i]);
}
}
printf("buf[%d] = %d\n", nitem-1, buf[nitem-1]);
}
关于互斥锁和条件变量的最佳实践:
1,把要多个线程共享的数据定义和互斥锁定义在一个结构体里。
2,把条件变量,互斥锁,和临界条件定义在一个结构体里。
3,在①的地方,最后不要用if,理由是,pthread_cond_wait返回后,有可能另一个消费者线程把它消费掉了,所以要再次测试相应的条件成立与否,防止发生虚假的(spurious)唤醒。各种线程都应该试图最大限度减少这些虚假唤醒,但是仍有可能发生。
4,注意②处的代码pthread_cond_signal,设想一下最坏的情况,调用该函数后,另外一个等待的线程立即被唤醒,所以被唤醒的pthread_cond_wait函数要立即加锁,但是调用pthread_cond_signal函数的线程还没有执行到③处的pthread_mutex_unlock,所以被唤醒的线程又立即终止了。所以为了避免这种情况发生,把②处的代码pthread_cond_signal放在③处的下一行。
参考下面的伪代码:
int flag;
pthread_mutex_lock(&nready.mutex);
int = nready.nready == 0);
nready.nready++;
pthread_mutex_unlock(&nready.mutex);
if(flag){
pthread_cond_signal(&nready.cond);
}
c/c++ 学习互助QQ群:877684253

本人微信:xiaoshitou5854
linux 互斥锁和条件变量的更多相关文章
- Linux互斥锁、条件变量和信号量
Linux互斥锁.条件变量和信号量 来自http://kongweile.iteye.com/blog/1155490 http://www.cnblogs.com/qingxia/archive/ ...
- 非常精简的Linux线程池实现(一)——使用互斥锁和条件变量
线程池的含义跟它的名字一样,就是一个由许多线程组成的池子. 有了线程池,在程序中使用多线程变得简单.我们不用再自己去操心线程的创建.撤销.管理问题,有什么要消耗大量CPU时间的任务通通直接扔到线程池里 ...
- linux c 线程间同步(通信)的几种方法--互斥锁,条件变量,信号量,读写锁
Linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量.信号量和读写锁. 下面是思维导图: 一.互斥锁(mutex) 锁机制是同一时刻只允许一个线程执行一个关键部分的代码. 1 . ...
- linux 线程的同步 二 (互斥锁和条件变量)
互斥锁和条件变量 为了允许在线程或进程之间共享数据,同步时必须的,互斥锁和条件变量是同步的基本组成部分. 1.互斥锁 互斥锁是用来保护临界区资源,实际上保护的是临界区中被操纵的数据,互斥锁通常用于保护 ...
- node源码详解(七) —— 文件异步io、线程池【互斥锁、条件变量、管道、事件对象】
本作品采用知识共享署名 4.0 国际许可协议进行许可.转载保留声明头部与原文链接https://luzeshu.com/blog/nodesource7 本博客同步在https://cnodejs.o ...
- 进程间通信机制(管道、信号、共享内存/信号量/消息队列)、线程间通信机制(互斥锁、条件变量、posix匿名信号量)
注:本分类下文章大多整理自<深入分析linux内核源代码>一书,另有参考其他一些资料如<linux内核完全剖析>.<linux c 编程一站式学习>等,只是为了更好 ...
- 互斥锁和条件变量(pthread)相关函数
互斥锁 #include <pthread.h> // 若成功返回0,出错返回正的Exxx值 // mptr通常被初始化为PTHREAD_MUTEX_INITIALIZER int pth ...
- 线程私有数据TSD——一键多值技术,线程同步中的互斥锁和条件变量
一:线程私有数据: 线程是轻量级进程,进程在fork()之后,子进程不继承父进程的锁和警告,别的基本上都会继承,而vfork()与fork()不同的地方在于vfork()之后的进程会共享父进程的地址空 ...
- 【转载】同步和互斥的POSIX支持(互斥锁,条件变量,自旋锁)
上篇文章也蛮好,线程同步之条件变量与互斥锁的结合: http://www.cnblogs.com/charlesblc/p/6143397.html 现在有这篇文章: http://blog.cs ...
随机推荐
- Lnmp架构部署动态网站环境.2019-7-3-1.4
安装wiki开源产品 一.创建数据库 [root@Lnmp bbs]# mysql -uroot -p123456 mysql> create database wiki; #创建wiki数据库 ...
- java链接集合
Intellij IDEA 导入eclipse web 项目详细操作 https://blog.csdn.net/deng11408205/article/details/79723213?utm_s ...
- vue高频面试题(面试路上踩过的坑)
### Vue 双向绑定原理 mvvm 双向绑定,采用**数据劫持结合发布者-订阅者模式**的方式,通过 `Object.defineProperty()` 来劫持各个属性的 setter.gette ...
- TeamyinyinFish->鱼嘤嘤小分队软件工程beta迭代作业
Github项目的链接 github工作组链接 github后台部分项目代码,issue提交在这个项目 github小程序前端部分项目代码链接 scrum会议时间 链接 第十一周 十一周博客 第十二周 ...
- C++ STL multiset
multiset的例子,允许集合内的元素是重复的 #include <iostream> #include <set> using namespace std; int mai ...
- web的前台、后台、前端、后端
前台:呈现给用户的视觉和基本的操作.后台:用户浏览网页时,我们看不见的后台数据跑动.后台包括前端,后端.前端:对应我们写的html .javascript 等网页语言作用在前端网页.后端:对应jsp. ...
- [C2W1] Improving Deep Neural Networks : Practical aspects of Deep Learning
第一周:深度学习的实用层面(Practical aspects of Deep Learning) 训练,验证,测试集(Train / Dev / Test sets) 本周,我们将继续学习如何有效运 ...
- BZOJ2073 「POI2004」PRZ 状压DP
问题描述 BZOJ2073 题解 发现 \(n \le 16\) ,显然想到状压 设 \(opt[S]\) 代表过河集合为 \(S\) 时,最小时间. 枚举 \(S\) 的子集,进行转移 枚举子集的方 ...
- CF786B Legacy 线段树优化建图
问题描述 CF786B LG-CF786B 题解 线段树优化建图 线段树的一个区间结点代表 \([l,r]\) 区间点. 然后建立区间点的时候就在线段树上建边,有效减少点的个数,从而提高时空效率. 优 ...
- luoguP3346 [ZJOI2015]诸神眷顾的幻想乡
题意 学习了广义后缀自动机. 广义后缀自动机与普通后缀自动机的区别在于它是对多个串建的,于是可以处理多个串. 广义后缀自动机和普通后缀自动机的区别在于两个特判,可以见这篇题解 对于这题,因为叶子数量小 ...