互斥锁和条件变量

简介:

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

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

复制代码
 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <pthread.h>
 5 #include <errno.h>
 6 
 7 #define     MAXNITEMS        1000000
 8 #define     MAXNTHREADS     100
 9 
10 int nitems;
11 
12 struct
13 {
14     pthread_mutex_t     mutex;
15     int                 buff[MAXNITEMS];
16     int                 nput;
17     int                 nval;
18 } shared = {
19     PTHREAD_MUTEX_INITIALIZER
20 };
21 
22 void *produce(void*);
23 void *consume(void*);
24 
25 int main(int argc,char *argv[])
26 {
27     int     i,nthreads,count[MAXNTHREADS];
28     pthread_t tid_produce[MAXNTHREADS],tid_consume;
29     if(argc != 3)
30     {
31         printf("usage: producongs2 <#itmes> <#threads>.\n");
32         exit(0);
33     }
34     nitems = atoi(argv[1]);
35     nthreads = atoi(argv[2]);
36     pthread_setconcurrency(nthreads);  //设置线程并发级别
37     for(i=0;i<nthreads;++i)
38     {
39         count[i] = 0;
40         pthread_create(&tid_produce[i],NULL,produce,&count[i]);
41     }
42     for(i=0;i<nthreads;i++)
43     {
44         pthread_join(tid_produce[i],NULL); //等待线程退出
45         printf("count[%d] = %d\n",i,count[i]);
46     }
47     pthread_create(&tid_consume,NULL,consume,NULL);
48     pthread_join(tid_consume,NULL);  //等待线程退出
49     exit(0);
50 }
51 
52 void *produce(void *arg)
53 {
54     for(; ;)
55     {
56         pthread_mutex_lock(&shared.mutex); //加锁
57         if(shared.nput >= nitems)
58         {
59             pthread_mutex_unlock(&shared.mutex); //释放锁
60             return ;
61         }
62         shared.buff[shared.nput] = shared.nval;
63         shared.nput++;
64         shared.nval++;
65         pthread_mutex_unlock(&shared.mutex); //加锁
66         *((int*) arg) += 1;
67     }
68 }
69 void *consume(void *arg)
70 {
71     int     i;
72     for(i=0;i<nitems;i++)
73     {
74         if(shared.buff[i] != i)
75             printf("buff[%d] = %d\n",i,shared.buff[i]);
76     }
77     return;
78 }
复制代码

程序执行结果如下:

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

复制代码
 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <pthread.h>
 5 #include <errno.h>
 6 
 7 #define     MAXNITEMS        1000000
 8 #define     MAXNTHREADS     100
 9 
10 int nitems;
11 
12 struct
13 {
14     pthread_mutex_t     mutex;
15     int                 buff[MAXNITEMS];
16     int                 nput;
17     int                 nval;
18 } shared = {
19     PTHREAD_MUTEX_INITIALIZER
20 };
21 
22 void *produce(void*);
23 void *consume(void*);
24 void consume_wait(int);
25 int main(int argc,char *argv[])
26 {
27     int     i,nthreads,count[MAXNTHREADS];
28     pthread_t tid_produce[MAXNTHREADS],tid_consume;
29     if(argc != 3)
30     {
31         printf("usage: producongs2 <#itmes> <#threads>.\n");
32         exit(0);
33     }
34     nitems = atoi(argv[1]);
35     nthreads = atoi(argv[2]);
36     pthread_setconcurrency(nthreads+1);
37     //创建生产者线程
38     for(i=0;i<nthreads;++i)
39     {
40         count[i] = 0;
41         pthread_create(&tid_produce[i],NULL,produce,&count[i]);
42     }
43     //创建消费者线程
44     pthread_create(&tid_consume,NULL,consume,NULL);
45     for(i=0;i<nthreads;i++)
46     {
47         pthread_join(tid_produce[i],NULL);
48         printf("count[%d] = %d\n",i,count[i]);
49     }
50     //等待消费者线程退出
51     pthread_join(tid_consume,NULL);
52     exit(0);
53 }
54 
55 void *produce(void *arg)
56 {
57     for(; ;)
58     {
59         pthread_mutex_lock(&shared.mutex);
60         if(shared.nput >= nitems)
61         {
62             pthread_mutex_unlock(&shared.mutex);
63             return ;
64         }
65         shared.buff[shared.nput] = shared.nval;
66         shared.nput++;
67         shared.nval++;
68         pthread_mutex_unlock(&shared.mutex);
69         *((int*) arg) += 1;
70     }
71 }
72 void *consume(void *arg)
73 {
74     int     i;
75     for(i=0;i<nitems;i++)
76     {
77         consume_wait(i);
78         if(shared.buff[i] != i)
79             printf("buff[%d] = %d\n",i,shared.buff[i]);
80     }
81     return;
82 }
83 void consume_wait(int i)
84 {
85     for(; ;)  //进行轮询,判断i是否已经由生产者生产
86     {
87         pthread_mutex_lock(&shared.mutex);
88         if(i<shared.nput)   //i已经生产
89         {
90             pthread_mutex_unlock(&shared.mutex);
91             return; 
92         }
93         pthread_mutex_unlock(&shared.mutex);
94     }
95 }
复制代码

存在的问题:当消费者获取的条目尚没有准备好时,消费者线程一次次的循环去判断,每次给互斥锁解锁又上锁,这种轮询的办法浪费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);

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

复制代码
 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <pthread.h>
 5 #include <errno.h>
 6 
 7 #define     MAXNITEMS        1000000
 8 #define     MAXNTHREADS     100
 9 
10 int nitems;
11 
12 struct
13 {
14     pthread_mutex_t     mutex;
15     int                 buff[MAXNITEMS];
16     int                 nput;
17     int                 nval;
18 } shared = {
19     PTHREAD_MUTEX_INITIALIZER
20 };
21 //条件变量
22 struct {
23     pthread_mutex_t mutex;  
24     pthread_cond_t  cond;
25     int nready;
26 }nready = {
27   PTHREAD_MUTEX_INITIALIZER,PTHREAD_COND_INITIALIZER
28 };
29 
30 void *produce(void*);
31 void *consume(void*);
32 
33 int main(int argc,char *argv[])
34 {
35     int     i,nthreads,count[MAXNTHREADS];
36     pthread_t tid_produce[MAXNTHREADS],tid_consume;
37     if(argc != 3)
38     {
39         printf("usage: producongs2 <#itmes> <#threads>.\n");
40         exit(0);
41     }
42     nitems = atoi(argv[1]);
43     nthreads = atoi(argv[2]);
44     pthread_setconcurrency(nthreads+1);
45     for(i=0;i<nthreads;++i)
46     {
47         count[i] = 0;
48         pthread_create(&tid_produce[i],NULL,produce,&count[i]);
49     }
50     pthread_create(&tid_consume,NULL,consume,NULL);
51     for(i=0;i<nthreads;i++)
52     {
53         pthread_join(tid_produce[i],NULL);
54         printf("count[%d] = %d\n",i,count[i]);
55     }
56     pthread_join(tid_consume,NULL);
57     exit(0);
58 }
59 
60 void *produce(void *arg)
61 {
62     printf("producer begins work\n");
63     for(; ;)
64     {
65         pthread_mutex_lock(&shared.mutex);
66         if(shared.nput >= nitems)
67         {
68             pthread_mutex_unlock(&shared.mutex);
69             return ;
70         }
71         shared.buff[shared.nput] = shared.nval;
72         shared.nput++;
73         shared.nval++;
74         pthread_mutex_unlock(&shared.mutex);
75         pthread_mutex_lock(&nready.mutex);
76         if(nready.nready == 0)
77             pthread_cond_signal(&nready.cond); //通知消费者
78         nready.nready++;
79         pthread_mutex_unlock(&nready.mutex);
80         *((int*) arg) += 1;
81     }
82 }
83 void *consume(void *arg)
84 {
85     int     i;
86     printf("consuemer begins work.\n");
87     for(i=0;i<nitems;i++)
88     {
89         pthread_mutex_lock(&nready.mutex);
90         while(nready.nready == 0)
91             pthread_cond_wait(&nready.cond,&nready.mutex); //等待生产者
92         nready.nready--;
93         pthread_mutex_unlock(&nready.mutex);
94         if(shared.buff[i] != i)
95             printf("buff[%d] = %d\n",i,shared.buff[i]);
96     }
97     return;
98 }
复制代码

程序执行结果如下:

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

复制代码
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);

 

相关文章
|
数据挖掘 算法 编解码
带你读《数据挖掘导论(原书第2版)》之二:数据
本书所涵盖的主题包括:数据预处理、预测建模、关联分析、聚类分析、异常检测和避免错误发现。通过介绍每个主题的基本概念和算法,为读者提供将数据挖掘应用于实际问题所需的必要背景。
|
4月前
|
机器学习/深度学习 人工智能 Kubernetes
开源AI驱动的商业综合体保洁管理——智能视频分析系统的技术解析
智能保洁管理系统通过计算机视觉与深度学习技术,解决传统保洁模式中监管难、效率低的问题。系统涵盖垃圾滞留监测、地面清洁度评估、设施表面检测等功能,实现高精度(96%以上)、实时响应(&lt;200毫秒)。基于开源TensorFlow与Kubernetes架构,支持灵活部署与定制开发,适用于商场、机场等场景,提升管理效率40%以上。未来可扩展至气味监测等领域,推动保洁管理智能化升级。
191 26
|
Windows
windows 电脑 连接蓝牙耳机没有麦克风
【8月更文挑战第31天】当Windows电脑连接蓝牙耳机后无法使用麦克风时,可尝试以下步骤解决:检查蓝牙设置,确保耳机正确连接并开启麦克风选项;检查音频设备设置,确认蓝牙耳机为默认播放和录制设备;更新蓝牙和音频驱动;确认耳机与系统的兼容性及正确设置。如问题未解,可重新配对耳机或联系客服。
8547 7
|
编译器 Shell C++
在编译的两个.o文件中有对同一个头文件的引用,因此在链接时出现结构体重复定义的问题怎么解决
在编译的两个.o文件中有对同一个头文件的引用,因此在链接时出现结构体重复定义的问题怎么解决
374 2
【UI】elementui el-pagination分页位置靠右
【UI】elementui el-pagination分页位置靠右
551 0
|
Web App开发 存储 网络安全
Charles抓包神器的使用,完美解决抓取HTTPS请求unknown问题
本文介绍了在 Mac 上使用的 HTTP 和 HTTPS 抓包工具 Charles 的配置方法。首先,强调了安装证书对于抓取 HTTPS 请求的重要性,涉及 PC 和手机端。在 PC 端,需通过 Charles 软件安装证书,然后在钥匙串访问中设置为始终信任。对于 iOS 设备,需设置 HTTP 代理,通过电脑上的 IP 和端口访问特定网址下载并安装证书,同时在设置中信任该证书。配置 Charles 包括设置代理端口和启用 SSL 代理。完成这些步骤后,即可开始抓包。文章还提及 Android 7.0 以上版本可能存在不信任用户添加 CA 证书的问题,但未提供解决办法。
4242 0
Charles抓包神器的使用,完美解决抓取HTTPS请求unknown问题
|
机器学习/深度学习 人工智能 数据可视化
太强!AI没有落下的腾讯出YOLO-World爆款 | 开集目标检测速度提升20倍,效果不减
太强!AI没有落下的腾讯出YOLO-World爆款 | 开集目标检测速度提升20倍,效果不减
1047 0
|
机器学习/深度学习 自然语言处理 物联网
大模型学习路线与建议
大模型学习路线与建议
1383 0
|
机器学习/深度学习 编解码 自然语言处理
SRCNN、DRCN、FSRCNN、ESPCN、SRGAN、RED…你都掌握了吗?一文总结超分辨率分析必备经典模型(一)(1)
SRCNN、DRCN、FSRCNN、ESPCN、SRGAN、RED…你都掌握了吗?一文总结超分辨率分析必备经典模型(一)
599 0
|
Java 程序员
List 去重的 6 种方法,这种方法最完美!
List 去重的 6 种方法,这种方法最完美!
447 0
List 去重的 6 种方法,这种方法最完美!