生产者消费者问题(条件变量)

生产者消费者问题

使用条件变量解决

条件变量概念

条件变量视是线程可用的另一种同步机制。条件变量给多个线程提供了一个汇合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生

条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量。其他线程在获得互斥量之前不会察觉到这种改变,因为互斥量必须在锁住以后才能计算条件。

相关函数

#include <pthread.h>
pthread_cond_t cond;
// 初始化
int pthread_cond_init(pthread_cond_t *restrict cond,
      const pthread_condattr_t *restrict attr);
 //cond: 条件变量的地址 attr: 条件变量属性,一般使用默认属性,指定为 NULL
// 销毁释放资源        
int pthread_cond_destroy(pthread_cond_t *cond);

// 线程阻塞函数, 哪个线程调用这个函数, 哪个线程就会被阻塞
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
//通过函数原型可以看出,该函数在阻塞线程的时候,需要一个互斥锁参数,这个互斥锁主要功能是进行线程同步,让线程顺序进入临界区,避免出现数共享资源的数据混乱。
//该函数会对这个互斥锁做以下几件事情:
//1.在阻塞线程时候,如果线程已经对互斥锁 mutex 上锁,那么会将这把锁打开,这样做是为了避免死锁
//2.当线程解除阻塞的时候,函数内部会帮助这个线程再次将这个 mutex 互斥锁锁上,继续向下访问临界区
// 表示的时间是从1971.1.1到某个时间点的时间, 总长度使用秒/纳秒表示
struct timespec {
	time_t tv_sec;      /* Seconds */
	long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
};
// 将线程阻塞一定的时间长度, 时间到达之后, 线程就解除阻塞了
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

// 唤醒阻塞在条件变量上的线程, ××至少有一个××被解除阻塞
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒阻塞在条件变量上的线程, 被阻塞的线程××全部××解除阻塞
int pthread_cond_broadcast(pthread_cond_t *cond);

组成

生产者线程 -> 若干个
生产商品或者任务放入到任务队列中
任务队列满了就阻塞,不满的时候就工作
通过一个生产者的条件变量控制生产者线程阻塞和非阻塞

消费者线程 -> 若干个
读任务队列,将任务或者数据取出
任务队列中有数据就消费,没有数据就阻塞
通过一个消费者的条件变量控制消费者线程阻塞和非阻塞

队列 -> 存储任务 / 数据,对应一块内存,为了读写访问可以通过一个数据结构维护这块内存
可以是数组、链表,也可以使用 stl 容器:queue /stack/list /vector

生产者消费者模型

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

// 链表的节点
struct Node
{
    int number;
    struct Node* next;
};

// 定义条件变量, 控制消费者线程
pthread_cond_t cond;
// 互斥锁变量
pthread_mutex_t mutex;
// 指向头结点的指针
struct Node * head = NULL;

// 生产者的回调函数
void* producer(void* arg)
{
    // 一直生产
    while(1)
    {
        pthread_mutex_lock(&mutex);
        // 创建一个链表的新节点
        struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));
        // 节点初始化
        pnew->number = rand() % 1000;
        // 节点的连接, 添加到链表的头部, 新节点就新的头结点
        pnew->next = head;
        // head指针前移
        head = pnew;
        printf("+++producer, number = %d, tid = %ld\n", pnew->number, pthread_self());
        pthread_mutex_unlock(&mutex);

        // 生产了任务, 通知消费者消费
        pthread_cond_broadcast(&cond);

        // 生产慢一点
        sleep(rand() % 3);
    }
    return NULL;
}

// 消费者的回调函数
void* consumer(void* arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        // 一直消费, 删除链表中的一个节点
//        if(head == NULL)   // 这样写有bug
        while(head == NULL)
        {
            // 任务队列, 也就是链表中已经没有节点可以消费了
            // 消费者线程需要阻塞
            // 线程加互斥锁成功, 但是线程阻塞在这行代码上, 锁还没解开
            // 其他线程在访问这把锁的时候也会阻塞, 生产者也会阻塞 ==> 死锁
            // 这函数会自动将线程拥有的锁解开
            pthread_cond_wait(&cond, &mutex);
            // 当消费者线程解除阻塞之后, 会自动将这把锁锁上
            // 这时候当前这个线程又重新拥有了这把互斥锁
        }
        // 取出链表的头结点, 将其删除
        struct Node* pnode = head;
        printf("--consumer: number: %d, tid = %ld\n", pnode->number, pthread_self());
        head  = pnode->next;
        free(pnode);
        pthread_mutex_unlock(&mutex);        

        sleep(rand() % 3);
    }
    return NULL;
}

int main()
{
    // 初始化条件变量
    pthread_cond_init(&cond, NULL);
    pthread_mutex_init(&mutex, NULL);

    // 创建5个生产者, 5个消费者
    pthread_t ptid[5];
    pthread_t ctid[5];
    for(int i=0; i<5; ++i)
    {
        pthread_create(&ptid[i], NULL, producer, NULL);
    }

    for(int i=0; i<5; ++i)
    {
        pthread_create(&ctid[i], NULL, consumer, NULL);
    }

    // 释放资源
    for(int i=0; i<5; ++i)
    {
        // 阻塞等待子线程退出
        pthread_join(ptid[i], NULL);
    }

    for(int i=0; i<5; ++i)
    {
        pthread_join(ctid[i], NULL);
    }

    // 销毁条件变量
    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);

    return 0;
}

参考

作者: 苏丙榅
链接: 爱编程的大丙

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个简单的生产者消费者模型的示例代码,使用了线程和条件变量: ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define BUFFER_SIZE 10 int buffer[BUFFER_SIZE]; int count = 0; int in = 0; int out = 0; pthread_mutex_t mutex; pthread_cond_t not_full; pthread_cond_t not_empty; void *producer(void *arg) { int item = 0; while (1) { item = rand() % 100 + 1; // 产生一个随机数 pthread_mutex_lock(&mutex); while (count == BUFFER_SIZE) { pthread_cond_wait(&not_full, &mutex); // 缓冲区已满,等待消费者消费 } buffer[in] = item; printf("Producer produced item %d, buffer[%d]\n", item, in); in = (in + 1) % BUFFER_SIZE; count++; pthread_mutex_unlock(&mutex); pthread_cond_signal(&not_empty); // 唤醒等待消费的线程 sleep(1); } return NULL; } void *consumer(void *arg) { int item = 0; while (1) { pthread_mutex_lock(&mutex); while (count == 0) { pthread_cond_wait(&not_empty, &mutex); // 缓冲区为空,等待生产者生产 } item = buffer[out]; printf("Consumer consumed item %d, buffer[%d]\n", item, out); out = (out + 1) % BUFFER_SIZE; count--; pthread_mutex_unlock(&mutex); pthread_cond_signal(&not_full); // 唤醒等待生产的线程 sleep(1); } return NULL; } int main() { pthread_t tid_producer, tid_consumer; pthread_mutex_init(&mutex, NULL); pthread_cond_init(&not_full, NULL); pthread_cond_init(&not_empty, NULL); pthread_create(&tid_producer, NULL, producer, NULL); pthread_create(&tid_consumer, NULL, consumer, NULL); pthread_join(tid_producer, NULL); pthread_join(tid_consumer, NULL); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&not_full); pthread_cond_destroy(&not_empty); return 0; } ``` 在这个示例中,缓冲区是一个循环队列,使用变量 `count` 记录缓冲区中的元素个数,使用变量 `in` 和 `out` 分别记录下一个生产者放入元素的位置和下一个消费者取出元素的位置。 生产者线程不断产生随机数,并将其放入缓冲区中。如果缓冲区已满,则线程进入等待状态,等待消费者线程取出元素后唤醒。当生产者放入元素后,唤醒等待消费的线程。 消费者线程不断从缓冲区中取出元素,并打印出来。如果缓冲区为空,则线程进入等待状态,等待生产者线程放入元素后唤醒。当消费者取出元素后,唤醒等待生产的线程。 需要注意的是,在使用条件变量时,线程必须先获得互斥锁才能等待条件变量,否则会出现竞态条件。当线程等待条件变量时,会自动释放互斥锁,等待条件变量时也是需要判断条件的,如果条件不符合,则线程会继续等待,直到条件符合才会被唤醒。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值