pthread 条件变量
在上一篇博客互斥量中,解决了线程如何互斥访问临界资源的问题。
在开始本文之前,我们先保留一个问题:为什么需要条件变量,如果只有互斥量不能解决什么问题?
API
init/destroy
条件变量的数据类型是 pthread_cond_t
.
初始化,销毁 API 为:
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
int pthread_cond_destroy(pthread_cond_t *cond);
pthread_cond_wait
函数原型:
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
作用:
The
pthread_cond_wait
function atomically blocks the current thread waiting on the condition variable specified bycond
, and releases the mutex specified bymutex
. The waiting thread unblocks only after another thread callspthread_cond_signal
, orpthread_cond_broadcast
with the same condition variable, and the current thread re-acquires the lock on mutex.—— Manual on MacOS.
在条件变量 cond
上阻塞线程,加入 cond
的等待队列,并释放互斥量 mutex
. 如果其他线程使用同一个条件变量 cond
调用了 pthread_cond_signal/broadcast
,唤醒的线程会重新获得互斥锁 mutex
.
pthread_cond_timedwait
函数原型:
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
作用:与 pthread_cond_wait
类似,但该线程被唤醒的条件是其他线程调用了 signal/broad
,或者系统时间到达了 abstime
。
pthread_cond_signal
函数原型:
int pthread_cond_signal(pthread_cond_t *cond)
作用:
The
pthread_cond_signal
function shall unblock at least one of the threads that are blocked no the specified condition variablecond
(if any threads are blocked oncond
).If more than one thread is blocked on a condition variable, the scheduling policy shall determine the order which threads are unblocked.
When each thread unblocked as a result of a
pthread_cond_broadcast()
orpthread_cond_signal()
returns from its call topthread_cond_wait()
orpthread_cond_timedwait()
, the thread shall own themutex
with which it calledpthread_cond_wait()
orpthread_cond_timedwait()
.The thread(s) that are unblocked shall contend for the
mutex
according to the scheduling policy (if applicable), and as if each had calledpthread_mutex_lock()
.The
pthread_cond_broadcast()
andpthread_cond_signal()
functions shall have no effect if there are no threads currently blocked oncond
.——Manual on Ubuntu.
唤醒一个在 cond
上等待的至少一个线程,如果 cond
上阻塞了多个线程,那么将根据调度策略选取一个。
当被唤醒的线程从 wait/timedwait
函数返回,将重新获得 mutex
(但可能需要竞争,因为可能唤醒多个线程)。
pthread_cond_broadcast
函数原型:
int pthread_cond_broadcast(pthread_cond_t *cond);
作用:唤醒所有在 cond
上等待的线程。
生产者消费者问题
又称 PC (Producer - Consumer) 问题。
详细问题定义可以看:
- [1] 百度百科:生产者消费者问题 .
- [2] 维基百科:Producer-Consumer Problem .
具体要求:
- 系统中有3个线程:生产者、计算者、消费者
- 系统中有2个容量为4的缓冲区:buffer1、buffer2
- 生产者生产'a'、'b'、'c'、‘d'、'e'、'f'、'g'、'h'八个字符,放入到buffer1; 计算者从buffer1取出字符,将小写字符转换为大写字符,放入到buffer2
- 消费者从buffer2取出字符,将其打印到屏幕上
buffer 的定义
buffer
实质上是一个队列。
const int CAPACITY = 4;
typedef struct
{
char items[CAPACITY];
int in, out;
} buffer_t;
void buffer_init(buffer_t *b) { b->in = b->out = 0; }
int buffer_is_full(buffer_t *b) { return ((b->in + 1) % CAPACITY) == (b->out); }
int buffer_is_empty(buffer_t *b) { return b->in == b->out; }
void buffer_put_item(buffer_t *buf, char item)
{
buf->items[buf->in] = item;
buf->in = (buf->in + 1) % CAPACITY;
}
char buffer_get_item(buffer_t *buf)
{
char item = buf->items[buf->out];
buf->out = (buf->out + 1) % CAPACITY;
return item;
}
一些全局变量
const int CAPACITY = 4; // buffer 的容量
const int N = 8; // 依据题意,需要转换 8 个字符
buffer_t buf1, buf2;
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; // 保证只有一个线程访问 buf1
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; // 保证只有一个线程访问 buf2
pthread_cond_t empty1 = PTHREAD_COND_INITIALIZER;
pthread_cond_t empty2 = PTHREAD_COND_INITIALIZER;
pthread_cond_t full1 = PTHREAD_COND_INITIALIZER;
pthread_cond_t full2 = PTHREAD_COND_INITIALIZER;
几个条件变量的作用如下:
empty1
表示当buf1
为空的时候,从buf1
取数据的线程要在此条件变量上等待。full1
表示当buf1
为满的时候,向buf1
写数据的线程要在此条件变量上等待。
其他同理。
producer
代码思路解析:
- 因为要对
buf1
操作,首先写一对pthread_mutex_lock/unlock
,保证临界代码区内只有producer
操作buf1
; - 如果
buf1
是满的,那么就将producer
线程阻塞在条件变量full1
上,释放互斥量mutex1
(虽然不能写入,但要让别的线程能够读取buf1
的数据); - 进入临界区,把数据写入
buf1
; - 离开临界区,因为写入了一次数据,
buf1
必定不为空,因此唤醒一个在empty1
上等待的线程,最后释放mutex1
.
void *producer(void *arg)
{
int i = 0;
// can be while(true)
for (; i < N; i++)
{
pthread_mutex_lock(&mutex1);
while (buffer_is_full(&buf1))
pthread_cond_wait(&full1, &mutex1);
buffer_put_item(&buf1, (char)('a' + i));
printf("Producer put [%c] in buffer1. \n", (char)('a' + i));
pthread_cond_signal(&empty1);
pthread_mutex_unlock(&mutex1);
}
return NULL;
}
consumer
思路与 producer
类似。
void *consumer(void *arg)
{
int i = 0;
for (; i < N; i++)
{
pthread_mutex_lock(&mutex2);
while (buffer_is_empty(&buf2))
pthread_cond_wait(&empty2, &mutex2);
char item = buffer_get_item(&buf2);
printf("\tConsumer get [%c] from buffer2. \n", item);
pthread_cond_signal(&full2);
pthread_mutex_unlock(&mutex2);
}
return NULL;
}
calculator
这是 produer
和 consumer
的结合体。
void *calculator(void *arg)
{
int i = 0;
char item;
for (; i < N; i++)
{
pthread_mutex_lock(&mutex1);
while (buffer_is_empty(&buf1))
pthread_cond_wait(&empty1, &mutex1);
item = buffer_get_item(&buf1);
pthread_cond_signal(&full1);
pthread_mutex_unlock(&mutex1);
pthread_mutex_lock(&mutex2);
while (buffer_is_full(&buf2))
pthread_cond_wait(&full2, &mutex2);
buffer_put_item(&buf2, item - 'a' + 'A');
pthread_cond_signal(&empty2);
pthread_mutex_unlock(&mutex2);
}
return NULL;
}
main 函数
int main()
{
pthread_t calc, prod, cons;
// init buffer
buffer_init(&buf1), buffer_init(&buf2);
// create threads
pthread_create(&calc, NULL, calculator, NULL);
pthread_create(&prod, NULL, producer, NULL);
pthread_create(&cons, NULL, consumer, NULL);
pthread_join(calc, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
// destroy mutex
pthread_mutex_destroy(&mutex1), pthread_mutex_destroy(&mutex2);
// destroy cond
pthread_cond_destroy(&empty1), pthread_cond_destroy(&empty2);
pthread_cond_destroy(&full1), pthread_cond_destroy(&full2);
}
为什么需要条件变量
从上面的 Producer - Consumer 问题可以看出,mutex
仅仅能表达「线程能否获得访问临界资源的权限」这一层面的信息,而不能表达「临界资源是否足够」这个问题。
假设没有条件变量,producer
线程获得了 buf1
的访问权限( buf1
的空闲位置对于 producer
来说是一种资源),但如果 buf1
是满的,producer
就没法对 buf1
操作。
对于 producer
来说,它不能占用访问 buf1
的互斥锁,但却又什么都不做。因此,它只能释放互斥锁 mutex
,让别的线程能够访问 buf1
,并取走数据,等到 buf1
有空闲位置,producer
再对 buf1
写数据。用伪代码表述如下:
pthread_mutex_lock(&mutex1);
if (buffer_is_full(&buf1))
{
pthread_mutex_unlock(&mutex1);
wait_until_not_full(&buf1);
pthread_mutex_lock(&mutex);
}
buffer_put_item(&buf1, item);
pthread_mutex_unlock(&mutex1);
而条件变量实际上是对上述一系列操作的一种封装。
为什么是 while
在上面代码中,使用 pthread_cond_wait
的时候,我们是通过这样的方式的:
while (...)
pthread_cond_wait(&cond, &mutex);
但这里为什么不是 if
而是 while
呢?
参考文章:https://www.cnblogs.com/leijiangtao/p/4028338.html
解释 1
#include <pthread.h>
struct msg {
struct msg *m_next;
/* value...*/
};
struct msg* workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
void
process_msg() {
struct msg* mp;
for (;;) {
pthread_mutex_lock(&qlock);
while (workq == NULL) {
pthread_cond_wait(&qread, &qlock);
}
mq = workq;
workq = mp->m_next;
pthread_mutex_unlock(&qlock);
/* now process the message mp */
}
}
void
enqueue_msg(struct msg* mp) {
pthread_mutex_lock(&qlock);
mp->m_next = workq;
workq = mp;
pthread_mutex_unlock(&qlock);
/** 此时第三个线程在signal之前,执行了process_msg,刚好把mp元素拿走*/
pthread_cond_signal(&qready);
/** 此时执行signal, 在pthread_cond_wait等待的线程被唤醒,
但是mp元素已经被另外一个线程拿走,所以,workq还是NULL ,因此需要继续等待*/
}
代码解析:
这里
process_msg
相当于消费者,enqueue_msg
相当于生产者,struct msg* workq
作为缓冲队列。在
process_msg
中使用while (workq == NULL)
循环判断条件,这里主要是因为在enqueue_msg
中unlock
之后才唤醒等待的线程,会出现上述注释出现的情况,造成workq==NULL
,因此需要继续等待。但是如果将
pthread_cond_signal
移到pthread_mutex_unlock()
之前执行,则会避免这种竞争,在unlock
之后,会首先唤醒pthread_cond_wait
的线程,进而workq != NULL
总是成立。因此建议使用
while
循环进行验证,以便能够容忍这种竞争。
解释 2
pthread_cond_signal
在多核处理器上可能同时唤醒多个线程。
//thread 1
while(0<x<10)
pthread_cond_wait(...);
//thread 2
while(5<x<15)
pthread_cond_wait(...);
如果某段时间内 x == 8
,那么两个线程相继进入等待。
然后第三个线程,进行了如下操作:
x = 12
pthread_cond_signal(...)
// or call pthread_cond_broadcast()
那么可能线程 1、2 都被唤醒了(因为 signal
可能唤醒多个),但是,此时线程 1 仍然不满足 while
,需要再次判断,然后进入下一次等待。
其次,即使 signal
只唤醒一个,上面我们提到,如果有多个线程都阻塞在同一个 cond
上,signal
会根据调度策略选取一个唤醒,那如果根据调度策略,唤醒的是线程 1 ,显然它还需要再一次判断是否需要继续等待(否则就违背了 pthead_cond_wait
的本意)。
pthread 条件变量的更多相关文章
- pthread条件变量
pthread条件变量等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返 ...
- 转载~kxcfzyk:Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解
Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解 多线程c语言linuxsemaphore条件变量 (本文的读者定位是了解Pthread常用多线程API和Pthread互斥锁 ...
- pthread中互斥量,锁和条件变量
互斥量 #include <pthread.h> pthread_mutex_t mutex=PTHREAD_MUTEX_INTIIALIZER; int pthread_mutex_in ...
- 互斥锁和条件变量(pthread)相关函数
互斥锁 #include <pthread.h> // 若成功返回0,出错返回正的Exxx值 // mptr通常被初始化为PTHREAD_MUTEX_INITIALIZER int pth ...
- Linux 多线程条件变量同步
条件变量是线程同步的另一种方式,实际上,条件变量是信号量的底层实现,这也就意味着,使用条件变量可以拥有更大的自由度,同时也就需要更加小心的进行同步操作.条件变量使用的条件本身是需要使用互斥量进行保护的 ...
- linux线程同步(2)-条件变量
一.概述 上一篇,介绍了互斥量.条件变量与互斥量不同,互斥量是防止多线程同时访问共享的互斥变量来保 ...
- 条件变量pthread_cond_t怎么用
#include <pthread.h> #include <stdio.h> #include <stdlib.h> pthread_mutex_t mutex ...
- [转]一个简单的Linux多线程例子 带你洞悉互斥量 信号量 条件变量编程
一个简单的Linux多线程例子 带你洞悉互斥量 信号量 条件变量编程 希望此文能给初学多线程编程的朋友带来帮助,也希望牛人多多指出错误. 另外感谢以下链接的作者给予,给我的学习带来了很大帮助 http ...
- linux多线程-互斥&条件变量与同步
多线程代码问题描述 我们都知道,进程是操作系统对运行程序资源分配的基本单位,而线程是程序逻辑,调用的基本单位.在多线程的程序中,多个线程共享临界区资源,那么就会有问题: 比如 #include < ...
随机推荐
- 小白如何学习PyTorch】25 Keras的API详解(下)缓存激活,内存输出,并发解决
[新闻]:机器学习炼丹术的粉丝的人工智能交流群已经建立,目前有目标检测.医学图像.时间序列等多个目标为技术学习的分群和水群唠嗑答疑解惑的总群,欢迎大家加炼丹兄为好友,加入炼丹协会.微信:cyx6450 ...
- 硬核卸载Vue(删除)
第一步 查找vue位置 打开 cmd 输入 where vue 第二步 进入文件 直接cv(复制粘贴) 随便打开个文件 第三步 删除vue 删除前缀vue的所有 进入node_modules 删除@v ...
- ubuntu下安装国际版QQ
在网上看到了好多的ubuntu下安装QQ的方法 好多 下面是看别人的文章 来测试的一篇 ubuntu下 安装国际版QQ http://www.ubuntukylin.com/applications/ ...
- JavaSE基础语法学习-异常
异常 异常指程序运行中出现的不期而至的各种状况,如:文件找不到.网络连接失败.非法参数等. 异常发生在程序运行期间,它影响了正常的程序执行流程. 比如说,你的代码少了一个分号,那么运行出来结果是提示是 ...
- 白话科普系列——双十一,竟然是一场有“预谋”的DDoS攻击?
随著互联网与信息技术的发展,所有人都在享受互联网带来的舒适和便利.如今,无论是个人社交行为,还是商业活动都早已离不开互联网. 但是,网络空间在创造机遇的同时,也带来了威胁.随着企业价值.知名度的提高. ...
- kubernetes-1.18.2集群安装-02
一.基础配置 修改主机名 # 在 172.17.32.23 上:hostnamectl set-hostname k8s-master01bash# 在 172.17.32.38 上:hostnam ...
- springboot mybatis保存数据中文保存成???
springboot mybatis保存数据中文保存成??? 解决方法: 数据库存连接配置后面加characterEncoding=UTF-8 如下例: spring.datasource.url=j ...
- RPA小结
1--怎么理解RPA? 1)RPA就是机器人流程自动化,根据业务的固定规则,自动完成一些任务(如数据抓取,信息录入,数据处理,自动化运维等),替代人类的重复劳动, 但RPA的发展已经不仅仅满足于此,正 ...
- 自定义泛型方法, 三级排序, low版,待升级
package com.jd.dashboard.util; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken ...
- 流量控制--3.Linux流量控制的组件
Linux流量控制的组件 流量控制元素与Linux组件之间的相关性: traditional element Linux component 入队列 修订:从用户或网络接收报文 整流 class 提供 ...