简 述: 继续上一篇中,讲解了原子⚛操作(粗略看作 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 
条件变量是锁🔐吗?
- 条件变量不是锁, 但是条件变量能够阻塞线程
- 使用条件变量 + 互斥量- 互斥量:保护一块共享数据
- 条件变量:引起阻塞- 生产者和消费者模型
 
 
条件变量的两个动作?
- 当条件不满足,阻塞线程
- 当条件满足;通知阻塞的线程开始工作
使用条件变量流程:
条件变量通常是结合和互斥量一起使用。 其使用方式,和互斥量使用很是相似,包括下一篇准备写的信号量(信号灯) ,的使用方式也是和互斥量很是相似。
- 初始化条件变量 - int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
- 阻塞条件变量 - //阻塞一个条件变量 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() 函数,还有干了其他事情: - 阻塞线程
- 将已经上锁的互斥锁解锁
- 接解除阻塞之后,对互斥锁进行加锁操作
 
- 唤醒阻塞在条件变量上的线程 - //唤醒至少一个阻塞在条件变量上的线程 int pthread_cond_signal(pthread_cond_t *cond); //唤醒全部阻塞在条件变量上的线程 int pthread_cond_broadcast(pthread_cond_t *cond);
- 销毁一个条件变量 - 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;
}代码分析:
多看一下理论模型的那个伪代码的流程图,生产者生产完一个之后,要通知(此时处于)阻塞的线程,给它一个信号,让他解除阻塞。
运行结果:
 
下载地址:
欢迎 star 和 fork 这个系列的 linux 学习,附学习由浅入深的目录。


