线程同步之条件变量

1 基本概念

条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。

条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用。

       当条件满足的时候,线程通常解锁并等待该条件发生变化,一旦另一个线程修改了环境变量,就会通知相应的环境变量唤醒一个或者多个被这个条件变量阻塞的线程。这些被唤醒的线程将重新上锁,并测试条件是否满足。一般来说条件变量被用于线程间的同步;当条件不满足的时候,允许其中的一个执行流挂起和等待

参考:条件变量详细解说_清风徐来Groot的博客-CSDN博客_条件变量

2 为什么使用条件变量

线程抢占互斥锁时,线程A抢到了互斥锁,但是条件不满足,线程A就会让出互斥锁让给其他线程,然后等待其他线程唤醒他;一旦条件满足,线程就可以被唤醒,并且拿互斥锁去访问共享区。经过这中设计能让进程运行更稳定。

3 函数使用

3.1 pthread_cond_init函数

作用:初始化一个条件变量

int pthread_cond_init(pthread_cond_t *restrict cond,
                      const pthread_condattr_t *restrict attr);
// 参 2:attr 表条件变量属性,通常为默认值,传 NULL 即可

静态初始化和动态初始化

// 1 静态初始化
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;

// 2 动态初始化
int pthread_cond_init(pthread_cond_t *restrict cond,
                      const pthread_condattr_t *restrict attr);

3.2 pthread_cond_destroy函数

作用:销毁一个条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

3.3 pthread_cond_wait函数(重点)

作用:(非常重要 三点)

1 阻塞等待条件变量 cond(参 1)满足


2 释放已掌握的互斥锁(解锁互斥量)相当于 pthread_mutex_unlock(&mutex);


3 当被唤醒,pthread_cond_wait 函数返回时,解除阻塞并重新申请获取互斥锁
pthread_mutex_lock(&mutex);

int pthread_cond_wait(pthread_cond_t *restrict cond,
                      pthread_mutex_t *restrict mutex);

1.2.两步为一个原子操作。

3.4 pthread_cond_timedwait 函数

作用:限时等待一个条件变量

int pthread_cond_timedwait(pthread_cond_t *restrict cond,
                           pthread_mutex_t *restrict mutex,
                           const struct timespec *restrict abstime);

3.5 pthread_cond_signal 函数

作用:唤醒至少一个阻塞在条件变量上的线程

int pthread_cond_signal(pthread_cond_t *cond);

3.6 pthread_cond_broadcast 函数

作用:唤醒全部阻塞在条件变量上的线程

int pthread_cond_broadcast(pthread_cond_t *cond);

线程同步之条件变量_第1张图片

4 生产者消费者模型

线程同步之条件变量_第2张图片

步骤:

生产者:

(1)生产数据;

(2)加锁pthread_mutex_lock(&mutex);

(3)将数据放置到公共区域;

(4)解锁pthread_mutex_unlock(&mutex);

(5)通知阻塞在条件变量上的线程pthread_cond_signal()、pthread_cond_brocadcast();

(6)循环产生后序数据。

消费者:

(1)创建锁pthread_mutex_t mutex;

(2)初始化锁pthread_mutex_init(mutex, NULL);

(3)加锁pthread_mutex_lock(&mutex);

(4)等待条件满足

        pthread_cond_wait(&cond, &mutex);

        阻塞等待条件变量;

        解锁;

        ----10s;

        加锁;

(5)访问共享数据;

(6)解锁、释放条件变量,释放锁。

代码

/*************************************************************************
 > File Name: pthread_cond_producer_consumer.c
 > Author: Winter
 > Created Time: 2022年08月02日 星期二 14时24分26秒
*************************************************************************/

#include 
#include 
#include 
#include 
#include 
#include 

/* 借助互斥量和条件变量实现 生产者和消费者 */
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;        // 互斥锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;           // 条件变量

// 定义一个链表
struct Msg {
	int val;
	struct Msg* next;
};

// 头节点
struct Msg* head;



// 生产者线程;负责生产产品,及产生链表节点
void* do_producer(void* arg) {
	while (1) {
		// 申请空间
		struct Msg* mp = (struct Msg*)malloc(sizeof(struct Msg));
		// 模拟生产一个数据
		mp->val = rand() % 1000 + 1;
		printf("------Producer:%d---\n", mp->val);


		// 将数据放到链表上,要加锁
		pthread_mutex_lock(&mutex);
		// 采用头插法产生数据
		mp->next = head;
		head = mp;
		// 解锁
		pthread_mutex_unlock(&mutex);


		// 唤醒阻塞在条件变量上的线程
		pthread_cond_signal(&cond);
		sleep(rand() % 3);
	}
	return NULL;
}


// 消费者线程;消费一个数据,即从链表上摘除一个节点
void* do_consumer(void* arg) {
	while (1) {
		struct Msg* mp;
		// 加锁,从链表上读取数据
		pthread_mutex_lock(&mutex);

		while (head == NULL) {
			// 头节点为空,说明没有数据加进来,因为链表采用的是头插法
			// 等待头节点有数据,即等待生产者产生数据
			// 等待有数据后被唤醒,当满足唤醒条件时,这里不再阻塞,头节点也有数据了,跳出循环。读取数据,同时wait函数会重新加锁(生产者已经解锁了)
			pthread_cond_wait(&cond, &mutex);
		}
		// 读取数据存到mp中
		mp = head;
		head = head->next;

		// 解锁
		pthread_mutex_unlock(&mutex);

		printf("=======================Consumer:%d\n", mp->val);
		free(mp);
		sleep(rand() % 3);
	}
	return NULL;
}



int main(int argc, char* argv[])
{
	// 需要两个线程,一个模拟生产者,一个模拟消费者
	pthread_t producer, consumer;
	// 随机数种子
	srand(time(NULL));

	// 创建生产者线程
	int res = pthread_create(&producer, NULL, do_producer, NULL);
	if (res != 0) {
		fprintf(stderr, "pthread_create producer error:%s\n", strerror(res));
		exit(1);
	}

	// 创建消费者线程
	res = pthread_create(&consumer, NULL, do_consumer, NULL);
	if (res != 0) {
		fprintf(stderr, "pthread_create consumer error:%s\n", strerror(res));
		exit(1);
	}

	// 回收生产者线程
	res = pthread_join(producer, NULL);
	if (res != 0) {
		fprintf(stderr, "pthread_join producer error:%s\n", strerror(res));
		exit(1);
	}

	// 回收消费者线程
	res = pthread_join(consumer, NULL);
	if (res != 0) {
		fprintf(stderr, "pthread_join consumer error:%s\n", strerror(res));
		exit(1);
	}

	return 0;
}

 执行

线程同步之条件变量_第3张图片

5 多个消费者

生产者休息时间短一些,其他都一样

#include 
#include 
#include 
#include 
#include 

// 借助条件变量模拟[生产者-消费者]问题

// 链表作为共享数据,需要被互斥量保护
struct Msg
{
    int val;
    struct Msg *next;
};

// 头节点
struct Msg *head;

// 静态初始化互斥量和条件变量
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;

// 生产者线程
void *producer(void *arg)
{
    struct Msg *mp;

    while (1)
    {
        // 申请空间
        mp = malloc(sizeof(struct Msg));
        // 模拟生产一个产品
        mp->val = rand() % 1000 + 1;
        printf("---produce----------------------:%d\n", mp->val);

        // 加锁
        int res = pthread_mutex_lock(&lock);
        if (res != 0)
        {
            fprintf(stderr, "pthread_mutex_lock producer error:%s\n", strerror(res));
            exit(1);
        }

        // 头插法
        mp->next = head;
        head = mp;

        // 解锁
        res = pthread_mutex_unlock(&lock);
        if (res != 0)
        {
            fprintf(stderr, "pthread_mutex_unlock producer error:%s\n", strerror(res));
            exit(1);
        }

        // 将等待在条件变量上的一个线程唤醒
        res = pthread_cond_signal(&has_product);
        if (res != 0)
        {
            fprintf(stderr, "pthread_cond_signal producer error:%s\n", strerror(res));
            exit(1);
        }
        sleep(rand() % 3);
    }
}

// 消费者线程
void *consumer(void *arg)
{
    struct Msg *mp;

    while (1)
    {
        // 加锁
        int res = pthread_mutex_lock(&lock);
        if (res != 0)
        {
            fprintf(stderr, "pthread_mutex_lock consumer error:%s\n", strerror(res));
            exit(1);
        }

        // 头节点为空,说明没有节点
        while (head == NULL)
        {
            // 消费者在阻塞
            res = pthread_cond_wait(&has_product, &lock);
            if (res != 0)
            {
                fprintf(stderr, "pthread_cond_wait consumer error:%s\n", strerror(res));
                exit(1);
            }
        }

        // 模拟消费掉一个产品
        mp = head;
        head = head->next;

        // 解锁
        res = pthread_mutex_unlock(&lock);
        if (res != 0)
        {
            fprintf(stderr, "pthread_mutex_unlock consumer error:%s\n", strerror(res));
            exit(1);
        }

        printf("==Consumer:%lu====%d\n", pthread_self(), mp->val);
        // 释放
        free(mp);
        sleep(rand() % 5);
    }
}

int main(int argc, char **argv)
{
    // 创建生产者线程和消费者线程
    pthread_t pid, cid;
    srand(time(NULL));

    // 创建生产者线程
    int res = pthread_create(&pid, NULL, producer, NULL);
    if (res != 0)
    {
        fprintf(stderr, "pthread_create producer error:%s\n", strerror(res));
        exit(1);
    }

    /***********3个消费者****************/
    // 创建消费者线程
    res = pthread_create(&cid, NULL, consumer, NULL);
    if (res != 0)
    {
        fprintf(stderr, "pthread_create consumer error:%s\n", strerror(res));
        exit(1);
    }
    // 创建消费者线程
    res = pthread_create(&cid, NULL, consumer, NULL);
    if (res != 0)
    {
        fprintf(stderr, "pthread_create consumer error:%s\n", strerror(res));
        exit(1);
    }

    // 创建消费者线程
    res = pthread_create(&cid, NULL, consumer, NULL);
    if (res != 0)
    {
        fprintf(stderr, "pthread_create consumer error:%s\n", strerror(res));
        exit(1);
    }

    // 回收生产者线程
    res = pthread_join(pid, NULL);
    if (res != 0)
    {
        fprintf(stderr, "pthread_join producer error:%s\n", strerror(res));
        exit(1);
    }

    // 回收消费者线程
    res = pthread_join(cid, NULL);
    if (res != 0)
    {
        fprintf(stderr, "pthread_join consumer error:%s\n", strerror(res));
        exit(1);
    }

    return 0;
}

执行

线程同步之条件变量_第4张图片

6 条件变量的优点

相较于 mutex 而言,条件变量可以减少竞争。
如直接使用 mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。

你可能感兴趣的:(linux系统编程,多线程,生产者,消费者,条件变量,线程同步)