互斥锁和条件变量

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

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

最新文章

  1. iOS-证书相关
  2. javascript 组合
  3. Xcopy命令参数
  4. 用Javascript获取页面元素的位置
  5. 数据结构Java实现05----栈:顺序栈和链式堆栈
  6. ulimit调优
  7. iOS7 各种问题解决
  8. c++ 字符串工具类
  9. 7 个面向Web开发者的实用CSS3教程推荐
  10. asp.net中WebForm.aspx与类文件分离使用
  11. SVM学习资料
  12. IOS开发中摇一摇是怎么实现的
  13. 私有成员 ECMAScript6 weakmap
  14. Linux系统格式化新磁盘并挂载分区
  15. Assets.xcassets误删后的恢复
  16. cmd登录远程Oracle数据库
  17. reduce函数
  18. 站在JAVA数据结构的视角看待简单表结构
  19. ubuntu 16.04 安装caffe2的方法及问题解决
  20. eclipse maven 常见问题解决方案

热门文章

  1. HDU 1934 特殊数字
  2. golang模拟动态高优先权优先调度算法
  3. bzoj1677
  4. noip2007-4
  5. poj 3984 -- 迷宫问题 深搜
  6. linux 简单常用命令
  7. MovingBoxes左右滑动放大图片插件
  8. PowerShell添加和部署WSP
  9. linux fdisk tf卡分区操作解析说明
  10. LOJ2537. 「PKUWC2018」Minimax【概率DP+线段树合并】