简 述: 继续上一篇中,讲解了原子⚛操作(粗略看作 cpu 会执行完该几行代码,才会切换到其他的线程)和重点讲解读写锁 的使用。本篇讲解 条件变量 的使用步骤:

  • pthread_cond_t g_cond() //条件变量–阻塞线程,等待条件满足*
  • pthread_cond_init() //初始化
  • pthread_cond_wait() / pthread_cond_timedwait() //阻塞线程(若是条件不满足)
  • …其他代码
  • pthread_cond_signal() / pthread_cond_timedwait() //通知阻塞中的线程解除阻塞
  • pthread_cond_destroy() //销毁

[TOC]


本文初发于 “偕臧的小站“,同步转载于此。


编程环境:

  💻: MacOS 10.14 📎 gcc/g++ 9.2 📎 gdb8.3

  💻: uos20 📎 gcc/g++ 8.3 📎 gdb8.0


条件变量是锁🔐吗?

  • 件变量不是锁, 但是条件变量能够阻塞线程
  • 使用条件变量 + 互斥量
    • 互斥量:保护一块共享数据
    • 条件变量:引起阻塞
      • 生产者和消费者模型

条件变量的两个动作?

  • 当条件不满足,阻塞线程
  • 当条件满足;通知阻塞的线程开始工作

使用条件变量流程:

条件变量通常是结合和互斥量一起使用。 其使用方式,和互斥量使用很是相似,包括下一篇准备写的信号量(信号灯) ,的使用方式也是和互斥量很是相似。

  1. 初始化条件变量

    int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
  2. 阻塞条件变量

    //阻塞一个条件变量
    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);  //注意:另一个参数是互斥量参数
    
    //限时阻塞一个条件变量
    int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

    pthread_cond_wait() 函数,还有干了其他事情:

    • 阻塞线程
    • 将已经上锁的互斥锁解锁
    • 接解除阻塞之后,对互斥锁进行加锁操作
  3. 唤醒阻塞在条件变量上的线程

    //唤醒至少一个阻塞在条件变量上的线程
    int pthread_cond_signal(pthread_cond_t *cond);
    
    //唤醒全部阻塞在条件变量上的线程
    int pthread_cond_broadcast(pthread_cond_t *cond);
  4. 销毁一个条件变量

    int pthread_cond_destroy(pthread_cond_t *cond);

生产者-消费者模型:

对于 “条件变量 + 互斥量” 的使用组合,有一个很是景点的例子 “生产者-消费者模型”。


理论模型:


代码实现:

将上面的理论模型的伪代码,用实际的完整代码实现如下:

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

typedef struct node  //节点结构
{
    int data;
    struct node* next;
} Node;

Node* g_head =  nullptr;  //永远指向链表头部的指针

pthread_mutex_t g_mutex;  //互斥锁--线程同步
pthread_cond_t g_cond;    //条件变量--阻塞线程,等待条件满足

void* funProducer(void* arg);  //生产者--添加一个头结点
void* funCustomer(void* arg);  //消费者--删除一个头结点

int main(int argc, char *argv[])
{
    pthread_t p1;
    pthread_t p2;

    pthread_mutex_init(&g_mutex, nullptr);  //初始化互斥锁
    pthread_cond_init(&g_cond, nullptr);    //初始化条件变量

    pthread_create(&p1, nullptr, funProducer, nullptr);  //创建生产者线程
    pthread_create(&p2, nullptr, funCustomer, nullptr);  //创建消费者线程

    pthread_join(p1, nullptr);  //阻塞回收子线程
    pthread_join(p2, nullptr);

    pthread_mutex_destroy(&g_mutex);  //配套销毁互斥锁
    pthread_cond_destroy(&g_cond);    //配套销毁条件变量
    
    return 0;
}

void* funProducer(void* arg)
{
    while (true) {
        Node* pNew = new Node();
        // Node* pNew = (Node *)malloc(sizeof(Node));
        pNew->data = rand() % 1000; 

        pthread_mutex_lock(&g_mutex);  //加锁
        pNew->next = g_head;
        g_head = pNew;
        printf("-----funProducer(生产者): %lu, %d\n", pthread_self(), pNew->data);
        pthread_mutex_unlock(&g_mutex);  //解锁
        
        pthread_cond_signal(&g_cond);  //通知阻塞的消费者线程,解除阻塞

        sleep(rand() % 3);  //随机休息 0~2 s
    }
    
    return nullptr;
}

void* funCustomer(void* arg)
{
    while (true) {
        pthread_mutex_lock(&g_mutex);  //加锁

        if (g_head == nullptr) {  //若是没有,则等待生产者生产出来,在此阻塞,等待消费
            pthread_cond_wait(&g_cond, &g_mutex); //阻塞线程,且该函数会对互斥锁解锁;且接解除阻塞之后,对互斥锁进行加锁操作
        }

        Node* pDel = g_head;
        g_head = g_head->next;
        printf("-----funCustomer(消费者): %lu, %d\n", pthread_self(), pDel->data);
        delete pDel;
        // free(pDel);
        pthread_mutex_unlock(&g_mutex);  //解锁
    }

    return nullptr;
}

代码分析:

多看一下理论模型的那个伪代码的流程图,生产者生产完一个之后,要通知(此时处于)阻塞的线程,给它一个信号,让他解除阻塞。


运行结果:


下载地址:

20_conditton

欢迎 star 和 fork 这个系列的 linux 学习,附学习由浅入深的目录。