Linux多线程同步之条件变量
1. 简介
条件变量是多线程的一种同步机制。多线程之间如果存在某种条件,当条件满足时其他线程才能够运行,此时可以使用条件变量。当条件不满足时,线程在该条件上阻塞,直到某个线程改变条件变量,并唤醒在该条件变量上阻塞的一个或多个线程。
2. 相关函数
2.1 初始化与释放
使用条件变量之前,必须先对其进行初始化。由pthread_cond_t数据类型表示的条件变量有两种初始化方式:1)由PTHREAD_COND_INITIALIZER赋给静态分配的条件变量;2)使用pthread_cond_init函数初始化动态分配的条件变量。在释放条件变量之前,使用pthread_cond_destroy函数销毁条件变量。
#include <phthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_attr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *restrict cond);
除非需要创建一个具有非默认属性的条件变量,否则pthread_cond_init函数的attr参数设置为NULL。
2.2 等待
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
int pthread_cond_timewait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
const struct timespec *restict tsptr);
传递给pthread_cond_wait的互斥量对条件变量进行保护。调用者把加锁后的互斥量传递给函数,函数自动对互斥量解锁,然后线程阻塞。当条件满足时,在pthread_cond_wait返回前,线程会重新对互斥量加锁,并从pthread_cond_wait之后的语句开始运行。
注意:条件变量只是起阻塞和唤醒线程的作用,判断条件需要用户给出。当多个线程阻塞在同一条件变量上时,线程被唤醒之后往往需要重新检查判断条件是否满足,如果不满足继续阻塞。这个过程一般用while语句。
pthread_cond_timewait与pthread_cond_wait函数相似,只是多了一个超时。超时值指定了线程愿意等待的时间,用timespec结构体设定。
2.3 唤醒
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
pthread_cond_signal函数至少唤醒一个等待cond上的线程,pthread_cond_broadcast唤醒等待在cond上的所有线程。
3. 使用条件变量解决生产者与消费者问题
3.1 生产者与消费者问题
生产者消费者问题是一个著名的线程同步问题,该问题描述如下:有多个生产者在生产产品,这些产品将提供给若干个消费者去消费。为了使生产者和消费者能并发执行,在两者之间设置一个具有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费。显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经放入产品的缓冲区中再次投放产品。
3.2 示例代码
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#define BUF_SIZE 5 //缓冲区大小
#define MAX_CALL 20 //线程读或写的次数
#define THREAD_NUM 3 //读或写线程数量
//计数
static int wc = 0;
//缓冲区结构体
struct Buffer {
int buf[BUF_SIZE]; //循环队列
int front;//队头
int rear;//队尾
pthread_mutex_t mutex;//互斥量
pthread_cond_t rd_cond;//读条件变量
pthread_cond_t wr_cond;//写条件变量
} buffer;
//读线程
void* thread_rd(void* arg)
{
int i = 0;
while(i < MAX_CALL)
{
pthread_mutex_lock(&(buffer.mutex));
//当队列为空时,自身阻塞,同时唤醒写线程
while(buffer.front == buffer.rear)
{
printf("thread%d waiting\n", (int)arg);
pthread_cond_signal(&(buffer.wr_cond));
pthread_cond_wait(&(buffer.rd_cond),&(buffer.mutex));
}
int data = buffer.buf[buffer.front];
printf("thread:%d,read data:%d\n",(int)arg,data);
++i;
buffer.front = (buffer.front + 1) % BUF_SIZE;
pthread_mutex_unlock(&(buffer.mutex));
sleep(0.5);
}
//线程退出时,唤醒其他阻塞的读或写线程
pthread_cond_signal(&(buffer.wr_cond));
pthread_cond_signal(&(buffer.rd_cond));
printf("thread%d exit\n", (int)arg);
return (void*)0;
}
//写线程
void* thread_wr(void* arg)
{
int i = 0;
while( i < MAX_CALL)
{
pthread_mutex_lock(&(buffer.mutex));
//队列已满时,自身阻塞,同时唤醒读线程
while(buffer.front == ((buffer.rear + 1) % BUF_SIZE))
{
printf("thread%d waiting\n", (int)arg);
pthread_cond_signal(&(buffer.rd_cond));
pthread_cond_wait(&(buffer.wr_cond),&(buffer.mutex));
}
buffer.buf[buffer.rear] = wc;
printf("thread:%d;write data:%d\n", (int)arg, buffer.buf[buffer.rear]);
buffer.rear = (buffer.rear + 1) % BUF_SIZE;
++wc;
++i;
pthread_mutex_unlock(&(buffer.mutex));
sleep(0.5);
}
//退出时,唤醒阻塞的读或写线程
pthread_cond_signal(&(buffer.rd_cond));
pthread_cond_signal(&(buffer.wr_cond));
printf("thread%d exit\n", (int)arg);
return (void*)0;
}
int main()
{
//初始化缓冲区结构体
pthread_mutex_init(&(buffer.mutex), NULL);
pthread_cond_init(&(buffer.rd_cond), NULL);
pthread_cond_init(&(buffer.wr_cond), NULL);
buffer.front = 0;
buffer.rear = 0;
//创建多个读或写线程
pthread_t rd_tids[THREAD_NUM];
pthread_t wr_tids[THREAD_NUM];
int err = 0;
for(int i = 0; i < THREAD_NUM; i++)
{
err += pthread_create(rd_tids + i, NULL, thread_rd, (void*)(i + THREAD_NUM));
err += pthread_create(wr_tids + i, NULL, thread_wr, (void*)i);
}
if(err != 0)
{
perror("phtread_create");
return 0;
}
for(int i = 0; i < THREAD_NUM; i++)
{
pthread_join(rd_tids[i], NULL);
pthread_join(wr_tids[i], NULL);
}
//销毁互斥量和条件变量
pthread_mutex_destroy(&(buffer.mutex));
pthread_cond_destroy(&(buffer.rd_cond));
pthread_cond_destroy(&(buffer.wr_cond));
return 0;
}
注意:
1. 在多核处理机上,pthread_cond_signal至少会唤醒一个阻塞在条件变量上的线程,而多个读写线程阻塞在条件变量上时,可能有多个被同时唤醒。因此需要用while重复判断,而不能使用if。例如:如果用if判断,当队列为空时,有两个写线程A、B被唤醒,如果A先取得互斥量,将队列写满时阻塞,之后B开始运行,此时队列虽然已满,但B从if语句之后开始运行,仍能写进数据。
2. 使用pthread_cond_signal而不使用pthread_cond_broadcast是因为有多个线程阻塞在同一条件变量上,而缓冲区同一时间只能有一个线程操作。如果同时唤醒所有线程,将会争夺互斥量,部分线程阻塞在互斥量上,降低程序效率。
3.3 运行结果
thread:2;write data:0
thread:5,read data:0
thread:1;write data:1
thread:4,read data:1
thread:0;write data:2
thread:3,read data:2
thread5 waiting
thread:1;write data:3
thread:2;write data:4
thread:4,read data:3
thread:0;write data:5
thread:3,read data:4
thread:1;write data:6
thread:2;write data:7
thread:4,read data:5
thread:0;write data:8
thread:3,read data:6
thread:1;write data:9
thread:2;write data:10
thread:4,read data:7
thread:0;write data:11
thread:3,read data:8
thread:2;write data:12
thread1 waiting
thread:5,read data:9
thread:4,read data:10
thread:0;write data:13
thread:3,read data:11
thread:5,read data:12
thread:2;write data:14
thread:4,read data:13
thread:0;write data:15
thread:3,read data:14
thread:5,read data:15
thread:2;write data:16
thread:4,read data:16
thread:0;write data:17
thread:3,read data:17
thread5 waiting
thread:1;write data:18
thread:2;write data:19
thread:4,read data:18
thread:0;write data:20
thread:3,read data:19
thread:2;write data:21
thread:1;write data:22
thread:4,read data:20
thread:0;write data:23
thread:3,read data:21
thread:1;write data:24
thread:2;write data:25
thread:4,read data:22
thread:0;write data:26
thread:3,read data:23
thread:2;write data:27
thread:4,read data:24
thread:1;write data:28
thread0 waiting
thread:5,read data:25
thread:3,read data:26
thread:4,read data:27
thread:5,read data:28
thread:1;write data:29
thread:2;write data:30
thread:3,read data:29
thread:1;write data:31
thread:5,read data:30
thread:2;write data:32
thread:4,read data:31
thread:3,read data:32
thread5 waiting
thread:1;write data:33
thread:2;write data:34
thread:4,read data:33
thread:0;write data:35
thread:3,read data:34
thread:2;write data:36
thread:1;write data:37
thread:4,read data:35
thread:0;write data:38
thread:3,read data:36
thread:1;write data:39
thread:4,read data:37
thread:2;write data:40
thread:0;write data:41
thread:3,read data:38
thread:1;write data:42
thread:4,read data:39
thread:2;write data:43
thread0 waiting
thread:5,read data:40
thread:3,read data:41
thread:5,read data:42
thread:2;write data:44
thread:4,read data:43
thread:1;write data:45
thread:3,read data:44
thread:2;write data:46
thread:4,read data:45
thread:5,read data:46
thread:1;write data:47
thread:3,read data:47
thread4 waiting
thread:2;write data:48
thread:5,read data:48
thread:0;write data:49
thread:1;write data:50
thread:3,read data:49
thread2 exit
thread:4,read data:50
thread5 waiting
thread:0;write data:51
thread:1;write data:52
thread3 exit
thread:5,read data:51
thread4 exit
thread:0;write data:53
thread:1;write data:54
thread:5,read data:52
thread:0;write data:55
thread:1;write data:56
thread:5,read data:53
thread:0;write data:57
thread1 exit
thread0 waiting
thread:5,read data:54
thread:5,read data:55
thread:5,read data:56
thread:5,read data:57
thread5 waiting
thread:0;write data:58
thread:0;write data:59
thread0 exit
thread:5,read data:58
thread:5,read data:59
thread5 exit