Linux多线程同步之条件变量

Linux多线程同步之条件变量


1. 简介

条件变量是多线程的一种同步机制。多线程之间如果存在某种条件,当条件满足时其他线程才能够运行,此时可以使用条件变量。当条件不满足时,线程在该条件上阻塞,直到某个线程改变条件变量,并唤醒在该条件变量上阻塞的一个或多个线程。


2. 相关函数

2.1 初始化与释放

使用条件变量之前,必须先对其进行初始化。由pthread_cond_t数据类型表示的条件变量有两种初始化方式:1)由PTHREAD_COND_INITIALIZER赋给静态分配的条件变量;2)使用pthread_cond_init函数初始化动态分配的条件变量。在释放条件变量之前,使用pthread_cond_destroy函数销毁条件变量。

#include 

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 
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 
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 
#include 
#include 

#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

你可能感兴趣的:(Linux多线程同步之条件变量)