互斥锁和条件变量

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

1、互斥锁

  互斥锁是用来保护临界区资源,实际上保护的是临界区中被操纵的数据,互斥锁通常用于保护由多个线程或多进程分享的共享数据。一般是一些可供线程间使用的全局变量,来达到线程同步的目的,即保证任何时刻只有一个线程或进程在执行其中的代码。一般加锁的轮廓如下:

pthread_mutex_lock()
临界区
pthread_mutex_unlock()

互斥锁API

pthread_mutex_lock(pthread_mutex_t *mutex);

用此函数加锁时,如果mutex已经被锁住,当前尝试加锁的线程就会阻塞,直到互斥锁被其他线程释放。当此函数返回时,说明互斥锁已经被当前线程成功加锁.

pthread_mutex_trylock(pthread_mutex_t *mutex);

用此函数加锁时,如果mutex已经卑琐主,当前尝试加锁的线程不会阻塞,而是立即返回,返回的错误码为EBUSY,而不是阻塞等待。

pthread_mutex_unlock(pthread_mutex_t *mutex);

注意使用锁之前要记得初始化。互斥锁的初始化有两种初始化方式:

1.对于静态分配的互斥锁一半用宏赋值的方式初始化

eg: static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

2.对于动态分配的互斥锁(如调用malloc)或分配在共享内存中,则必须调用pthread_mutex_init(pthread_mutex *mutex, pthread_mutexattr_t *mutexattr)函数来进行初始化。

例子1:写个程序实现生产者—消费者问题,先只考虑多个生产者线程之间的同步,直到所有的生产者线程都完成工作以后,才启动消费者线程。程序如下:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.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*);
void *consume(void*); int main(int argc,char *argv[])
{
int i,nthreads,count[MAXNTHREADS];
pthread_t tid_produce[MAXNTHREADS],tid_consume;
if(argc != )
{
printf("usage: producongs2 <#itmes> <#threads>.\n");
exit();
}
nitems = atoi(argv[]);
nthreads = atoi(argv[]);
pthread_setconcurrency(nthreads); //设置线程并发级别
for(i=;i<nthreads;++i)
{
count[i] = ;
pthread_create(&tid_produce[i],NULL,produce,&count[i]);
}
for(i=;i<nthreads;i++)
{
pthread_join(tid_produce[i],NULL); //等待线程退出
printf("count[%d] = %d\n",i,count[i]);
}
pthread_create(&tid_consume,NULL,consume,NULL);
pthread_join(tid_consume,NULL); //等待线程退出
exit();
} void *produce(void *arg)
{
for(; ;)
{
pthread_mutex_lock(&shared.mutex); //加锁
if(shared.nput >= nitems)
{
pthread_mutex_unlock(&shared.mutex); //释放锁
return ;
}
shared.buff[shared.nput] = shared.nval;
shared.nput++;
shared.nval++;
pthread_mutex_unlock(&shared.mutex); //加锁
*((int*) arg) += ;
}
}
void *consume(void *arg)
{
int i;
for(i=;i<nitems;i++)
{
if(shared.buff[i] != i)
printf("buff[%d] = %d\n",i,shared.buff[i]);
}
return;
}

程序执行结果如下:

例子2:改进例子1,所有生产者线程启动后立即启动消费者线程,这样生产者线程产生数据的同时,消费者线程就能出来它,此时必须同步生产者和消费者,程序如下:

  #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.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*);
void *consume(void*);
void consume_wait(int);
int main(int argc,char *argv[])
{
int i,nthreads,count[MAXNTHREADS];
pthread_t tid_produce[MAXNTHREADS],tid_consume;
if(argc != )
{
printf("usage: producongs2 <#itmes> <#threads>.\n");
exit();
}
nitems = atoi(argv[]);
nthreads = atoi(argv[]);
pthread_setconcurrency(nthreads+);
//创建生产者线程
for(i=;i<nthreads;++i)
{
count[i] = ;
pthread_create(&tid_produce[i],NULL,produce,&count[i]);
}
//创建消费者线程
pthread_create(&tid_consume,NULL,consume,NULL);
for(i=;i<nthreads;i++)
{
pthread_join(tid_produce[i],NULL);
printf("count[%d] = %d\n",i,count[i]);
}
//等待消费者线程退出
pthread_join(tid_consume,NULL);
exit();
} void *produce(void *arg)
{
for(; ;)
{
pthread_mutex_lock(&shared.mutex);
if(shared.nput >= nitems)
{
pthread_mutex_unlock(&shared.mutex);
return ;
}
shared.buff[shared.nput] = shared.nval;
shared.nput++;
shared.nval++;
pthread_mutex_unlock(&shared.mutex);
*((int*) arg) += ;
}
}
void *consume(void *arg)
{
int i;
for(i=;i<nitems;i++)
{
consume_wait(i);
if(shared.buff[i] != i)
printf("buff[%d] = %d\n",i,shared.buff[i]);
}
return;
}
void consume_wait(int i)
{
for(; ;) //进行轮询,判断i是否已经由生产者生产
{
pthread_mutex_lock(&shared.mutex);
if(i<shared.nput) //i已经生产
{
pthread_mutex_unlock(&shared.mutex);
return;
}
pthread_mutex_unlock(&shared.mutex);
}
}

存在的问题:当消费者获取的条目尚没有准备好时,消费者线程一次次的循环去判断,每次给互斥锁解锁又上锁,这种轮询的办法浪费CPU时间。

2、条件变量

  互斥锁用于上锁,条件变量用于等待,条件变量的使用是与互斥锁共通使用的。

2.1等待与信号发送

  条件变量类型是pthread_cond_t,调用函数如下:

pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *pmutex);

pthread_cond_signal(pthread_cond_t *pcond);

每个条件变量总是有一个互斥锁与之关联。现在采用条件变量实现生产者与消费者问题,程序如下:

  #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.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
};
//条件变量
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,nthreads,count[MAXNTHREADS];
pthread_t tid_produce[MAXNTHREADS],tid_consume;
if(argc != )
{
printf("usage: producongs2 <#itmes> <#threads>.\n");
exit();
}
nitems = atoi(argv[]);
nthreads = atoi(argv[]);
pthread_setconcurrency(nthreads+);
for(i=;i<nthreads;++i)
{
count[i] = ;
pthread_create(&tid_produce[i],NULL,produce,&count[i]);
}
pthread_create(&tid_consume,NULL,consume,NULL);
for(i=;i<nthreads;i++)
{
pthread_join(tid_produce[i],NULL);
printf("count[%d] = %d\n",i,count[i]);
}
pthread_join(tid_consume,NULL);
exit();
} void *produce(void *arg)
{
printf("producer begins work\n");
for(; ;)
{
pthread_mutex_lock(&shared.mutex);
if(shared.nput >= nitems)
{
pthread_mutex_unlock(&shared.mutex);
return ;
}
shared.buff[shared.nput] = shared.nval;
shared.nput++;
shared.nval++;
pthread_mutex_unlock(&shared.mutex);
pthread_mutex_lock(&nready.mutex);
if(nready.nready == )
pthread_cond_signal(&nready.cond); //通知消费者
nready.nready++;
pthread_mutex_unlock(&nready.mutex);
*((int*) arg) += ;
}
}
void *consume(void *arg)
{
int i;
printf("consuemer begins work.\n");
for(i=;i<nitems;i++)
{
pthread_mutex_lock(&nready.mutex);
while(nready.nready == )
pthread_cond_wait(&nready.cond,&nready.mutex); //等待生产者
nready.nready--;
pthread_mutex_unlock(&nready.mutex);
if(shared.buff[i] != i)
printf("buff[%d] = %d\n",i,shared.buff[i]);
}
return;
}

程序执行结果如下:

总的来说,给条件变量发送信号的过程代码如下:

struct
{
pthread_mutex_t mutex;
pthread_cond_t cond;
//维护本条件的各个变量
}var = {PTHREAD_MUTEX_INITIALIZER,PTHREAD_COND_INITIALIZER,...} pthread_mutex_lock(&var.mutex);
设置条件为真
pthread_cond_signal(&var.cond);
pthread_mutex_unlock(&var.mutex);

测试条件并进入睡眠以等待条件变为真的代码大体如下:

pthread_mutex_lock(&var.mutex);
while(条件为假)
pthread_cond_wait(&var.cond,&var.mutex);
修改条件
pthread_mutex_unlock(&var.mutex);

2.2定时等待和广播

  通常pthread_cond_signal只是唤醒等待在相应条件变量上的一个线程,在某些情况下需要唤醒多个线程(例如读写者问题),可以调用pthread_cond_broadcast唤醒阻塞在相应条件变量上的所有线程。pthread_cond_timewait允许线程就阻塞时间设置一个限制值。API如下:

pthread_cond_broadcast(pthread_cond_t *cond);

pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex, const struct timespec *abstime);

linux 线程的同步 二 (互斥锁和条件变量)的更多相关文章

  1. 【Linux C 多线程编程】互斥锁与条件变量

    一.互斥锁 互斥量从本质上说就是一把锁, 提供对共享资源的保护访问. 1) 初始化: 在Linux下, 线程的互斥量数据类型是pthread_mutex_t. 在使用前, 要对它进行初始化: 对于静态 ...

  2. Linux互斥锁、条件变量和信号量

    Linux互斥锁.条件变量和信号量  来自http://kongweile.iteye.com/blog/1155490 http://www.cnblogs.com/qingxia/archive/ ...

  3. linux c 线程间同步(通信)的几种方法--互斥锁,条件变量,信号量,读写锁

    Linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量.信号量和读写锁. 下面是思维导图:  一.互斥锁(mutex)  锁机制是同一时刻只允许一个线程执行一个关键部分的代码. 1 . ...

  4. 非常精简的Linux线程池实现(一)——使用互斥锁和条件变量

    线程池的含义跟它的名字一样,就是一个由许多线程组成的池子. 有了线程池,在程序中使用多线程变得简单.我们不用再自己去操心线程的创建.撤销.管理问题,有什么要消耗大量CPU时间的任务通通直接扔到线程池里 ...

  5. 线程私有数据TSD——一键多值技术,线程同步中的互斥锁和条件变量

    一:线程私有数据: 线程是轻量级进程,进程在fork()之后,子进程不继承父进程的锁和警告,别的基本上都会继承,而vfork()与fork()不同的地方在于vfork()之后的进程会共享父进程的地址空 ...

  6. 进程间通信机制(管道、信号、共享内存/信号量/消息队列)、线程间通信机制(互斥锁、条件变量、posix匿名信号量)

    注:本分类下文章大多整理自<深入分析linux内核源代码>一书,另有参考其他一些资料如<linux内核完全剖析>.<linux c 编程一站式学习>等,只是为了更好 ...

  7. node源码详解(七) —— 文件异步io、线程池【互斥锁、条件变量、管道、事件对象】

    本作品采用知识共享署名 4.0 国际许可协议进行许可.转载保留声明头部与原文链接https://luzeshu.com/blog/nodesource7 本博客同步在https://cnodejs.o ...

  8. 【转载】同步和互斥的POSIX支持(互斥锁,条件变量,自旋锁)

    上篇文章也蛮好,线程同步之条件变量与互斥锁的结合: http://www.cnblogs.com/charlesblc/p/6143397.html   现在有这篇文章: http://blog.cs ...

  9. linux 互斥锁和条件变量

    为什么有条件变量? 请参看一个线程等待某种事件发生 注意:本文是linux c版本的条件变量和互斥锁(mutex),不是C++的. mutex : mutual exclusion(相互排斥) 1,互 ...

随机推荐

  1. 跟我一起学习ASP.NET 4.5 MVC4.0(三)

    今天我们继续ASP.NET 4.5 MVC 4.0,前两天熟悉了MVC4.0在VS11和win8下的更新,以及MVC中的基础语法和几个关键字的使用.了解了这些就可以对MVC进一步认识,相信很多人都对M ...

  2. spoj8406

    题解: 二分+树状数组 记录以下i在当前拍第几 代码: #include<bits/stdc++.h> using namespace std; ; int a[N],f1[N],f2[N ...

  3. 猎豹浏览器(chrome内核)屏蔽视频广告

    1.基于猎豹浏览器(原则上chrome内核浏览器都可以) 2.下载插件Adblock Plus,下载地址:http://chromecj.com/productivity/2014-07/24/dow ...

  4. Triangles 正多边形分割锐角三角形

    题目描述 已知一个圆的圆周被N个点分成了N段等长圆弧,求任意取三个点,组成锐角三角形的个数. 输入 多组数据,每组数据一个N(N <= 1000000) 输出 对于每组数据,输出不同锐角三角形的 ...

  5. ASP.NET下跨应用共享Session和使用Redis进行Session托管

    在之前的博客中,我说到了Session的共享问题,其中说到了Web Farm和Web Garden两种情况下Session的处理.在ASP.NET提供的Session处理方法中,有以下四种模式: 1. ...

  6. SaltStack一键部署负载均衡和高可用

    一.负载均衡的部署 server1    haproxy (调度器) server2    apache server3    nginx 1)在server1上首先安装salt-minion服务.并 ...

  7. Tomcat学习之二:tomcat安装、配置及目录文件说明

    我们看到tomcat目录/bin文件夹里有个tomcat6w.exe,顾名思义就是tomcat以window方式显示控制台.第1次点击打开它时候,可能会提示:tomcat指定的服务未安装,此时我们可以 ...

  8. HDU 1590 Searching(求复数向量和的极限)

    Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission( ...

  9. HDU 1589 Stars Couple(计算几何求二维平面的最近点对和最远点对)

    Time Limit: 1000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission( ...

  10. CentOS7安装OpenStack(Rocky版)-05.安装一个nova计算节点实例

    上一篇文章分享了控制节点的nova计算服务的安装方法,在实际生产环境中,计算节点通常会安装一些单独的节点提供服务,本文分享单独的nova计算节点的安装方法 ----------------  完美的分 ...