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_waitfunction 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_broadcastwith 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_signalfunction 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 themutexwith which it calledpthread_cond_wait()orpthread_cond_timedwait().The thread(s) that are unblocked shall contend for the
mutexaccording 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 < ...
随机推荐
- ElasticSearch初步了解和安装(windows上安装)
ElasticSearch是什么 ElasticSearch(一般简称es)是一个基于Lucene的分布式搜索和数据分析引擎.它提供了REST api 的操作接口.它可以快速的存储.搜索.分析海量数据 ...
- 题解 P1541 【乌龟棋】
题目描述 乌龟棋的棋盘是一行\(N\)个格子,每个格子上一个分数(非负整数).棋盘第\(1\)格是唯一的起点,第\(N\)格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点. 乌龟棋中\(M\) ...
- Flink系列(0)——准备篇(流处理基础)
Apache Flink is a framework and distributed processing engine for stateful computations over unbound ...
- Loadrunner11简单压测接口教程
一.需求 使用Loadrunner压测目标接口,要求支持1000并发数. 目标接口:https://www.xxx.com/digitaldata/api/signer/1.0/signerRegis ...
- 【SpringCloud】05.Eureka的高可用
1.简单情况 2.为了达到Eureka的高可用,可以多个Eureka互相注册. 3.我们需要修改两处: Eureka Client Eureka Server 3.1 Eureka Client 在C ...
- leetcode4:sort-list
题目描述 在O(n log n)的时间内使用常数级空间复杂度对链表进行排序. Sort a linked list in O(n log n) time using constant space co ...
- 使用pipenv管理python虚拟环境
前言 近期的项目中,我开始尝试着从virtualenv管理python虚拟环境,切换到用pipenv来管理. 经过一段时间的使用,着实觉得pipenv使用的更加顺手,更加的便捷.这当然也延续了 Ken ...
- Linux下端口被占用,关掉端口占用的方法
Linux下端口被占用(例如端口3000),关掉端口占用的进程的方法: 1.netstat -tln | grep 3000 2.sudo lsof -i:3000 3.sudo kill -9 进程
- [MIT6.006] 7. Counting Sort, Radix Sort, Lower Bounds for Sorting 基数排序,基数排序,排序下界
在前6节课讲的排序方法(冒泡排序,归并排序,选择排序,插入排序,快速排序,堆排序,二分搜索树排序和AVL排序)都是属于对比模型(Comparison Model).对比模型的特点如下: 所有输入ite ...
- 用 Cloud Performance Test怎么录制测试脚本
Cloud Performance Test 云压力测试平台(以下简称:CPT)可以提供一站式全链路云压力测试服务,通过分布式压力负载机,快速搭建系统高并发运行场景,按需模拟千万级用户实时访问,并结合 ...