17-2.【Linux系统编程】线程同步详解 - 条件变量的理解及应用

目录

  • [1. 线程互斥引入的新问题及解决方式](#1. 线程互斥引入的新问题及解决方式)
  • [2. 同步概念与竞态条件](#2. 同步概念与竞态条件)
  • [3. 条件变量](#3. 条件变量)
    • [3.1 条件变量的概念](#3.1 条件变量的概念)
    • [3.2 条件变量相关函数及使用](#3.2 条件变量相关函数及使用)
    • [3.3 举例使用](#3.3 举例使用)
  • [4. 生产者消费者模型](#4. 生产者消费者模型)
    • [4.2 原理](#4.2 原理)
    • [4.2 为何要使用生产者消费者模型](#4.2 为何要使用生产者消费者模型)
    • [4.3 生产者消费者模型优点](#4.3 生产者消费者模型优点)
    • [4.4 基于BlockingQueue的生产者消费者模型](#4.4 基于BlockingQueue的生产者消费者模型)
      • [4.4.1 BlockingQueue](#4.4.1 BlockingQueue)
      • [4.4.2 代码编写生产者消费者模型(单+单)](#4.4.2 代码编写生产者消费者模型(单+单))
      • [4.4.3 代码编写生产者消费者模型,模板封装,单+多](#4.4.3 代码编写生产者消费者模型,模板封装,单+多)
      • [4.4.4 C++ queue模拟阻塞队列的生产消费模型](#4.4.4 C++ queue模拟阻塞队列的生产消费模型)

1. 线程互斥引入的新问题及解决方式

引入新的技术(锁/互斥量)会带来新问题(线程),解决问题需要再引入新技术(同步)。

  1. 问题举例

    用 "自助取款机" 的场景,把「互斥的缺陷→同步的作用」讲清楚:

    1. 用 "自助取款机" 对应技术概念
    • 自助取款机 = 临界资源(同一时间只能 1 个人用)
    • 取款机的 "操作权限" = (谁拿到权限,谁能进机器操作)
    1. 只引入互斥的问题:出现 "线程饥饿"

    一群人来取钱,用 "抢权限" 的方式(对应互斥锁的竞争):

    • 每个人想取钱,都得抢 "取款机的操作权限"(申请锁);
    • 有一个人反应特别快、手速极快:他取完钱后,不等别人反应,立刻又抢到了操作权限(释放锁后马上重新申请锁);
    • 结果:其他人一直抢不到权限,只能干等着,永远取不了钱 ------ 这就是线程饥饿

    此时,互斥虽然保证了 "同一时间只有 1 个人用取款机"(资源安全),但出现了 "不公平、部分执行流永远得不到资源" 的问题。
    3. 引入同步:强制 "排队执行"

    为了解决饥饿,给取款机加 "排队规则"(对应线程同步机制):

    • 所有人必须先在取款机外排好队;
    • 谁拿到操作权限(申请到锁)、取完钱后,必须回到队伍的队尾重新排队,不能直接抢下一次权限;
    • 下一个用取款机的人,只能是队伍的队首(按顺序申请锁)。

    这样一来,所有人都能按顺序用到取款机,既保证了资源安全(互斥的核心作用),又解决了 "饥饿" 问题 ------ 这就是线程同步的核心目标:在安全的基础上,让执行流按规则有序访问资源

  2. 总结基础互斥锁的问题

    • 基础互斥锁允许执行流 "释放锁后立即重新申请",易让 "竞争力强" 的执行流持续抢占资源,导致其他执行流长期无法获取锁(即线程饥饿);
    • 基础互斥锁可保证资源的互斥访问,但存在 "不高效、不公平" 的缺陷(常被称为 "钝互斥"),比如无规则的抢占会浪费资源、无法保障执行流的访问顺序。
  3. 线程同步的目标:在保证临界资源(如取款机)安全的前提下,让所有执行流按预设规则有序访问资源,解决基础互斥锁的 "饥饿、不公平" 等问题。

核心意思(线程同步的背景与目标):

  • 基础互斥锁(钥匙)能解决 "多个执行流同时访问临界资源" 的问题(保证互斥),但存在低效、不公平、易导致饥饿等缺陷;
  • 线程同步技术,就是在 "保证临界资源安全" 的基础上,引入有序规则,让多个执行流按合理顺序访问资源,解决基础互斥锁的缺陷。

2. 同步概念与竞态条件

  • 同步:是线程 / 进程间的协同机制,在互斥保证数据安全(排他性访问临界资源)的基础上,通过预设规则(如 FIFO 顺序、条件触发)让多个执行流按特定逻辑顺序访问临界资源。其核心目标不仅是避免饥饿问题,还能确保程序执行结果可预测、提升资源利用效率,实现线程间的有序协同。

  • 竞态条件:多线程 / 进程并发访问共享资源时,由于执行时序的不确定性 (调度器随机分配 CPU 时间片),导致程序最终结果依赖于线程执行的先后顺序,进而引发数据不一致、逻辑异常等问题,这种因时序竞争导致的异常场景称为竞态条件。例如:两个线程同时读取全局变量count=1,各自执行count++后,最终count可能为 2 而非预期的 3,就是典型的竞态条件。

补充说明(贴合核心逻辑)

  1. 同步与互斥的关系:互斥是同步的基础(先保证资源安全,再谈有序访问),同步是互斥的进阶(解决互斥带来的不公平、饥饿等缺陷);
  2. 竞态条件的本质:共享资源的访问缺乏有效同步 / 互斥机制,导致 "读 - 改 - 写" 等操作被拆分执行,最终因时序混乱引发异常。

3. 条件变量

条件变量是专门针对 "线程需要等待某个条件满足才能执行" 的同步场景设计的机制,它本身不保证互斥(必须配合互斥锁),但能高效实现 "等待 - 唤醒" 的同步逻辑:

  • 核心能力:让条件不满足的线程释放锁并挂起(不空耗 CPU),待条件满足时被其他线程唤醒,重新抢锁执行;
  • 解决的同步痛点:替代 "线程持有锁自旋等待条件" 的低效方式,同时通过 "有序唤醒(如 FIFO 队列)" 辅助实现线程执行的有序性。

条件变量是实现线程同步的关键技术手段之一,它需配合互斥锁使用,专为解决 "线程需等待特定条件满足才能执行" 的同步场景而生,通过 "等待 - 唤醒" 机制让条件不满足的线程释放锁并挂起(避免空耗 CPU),待条件满足时唤醒线程重新竞争锁执行,以此实现线程按预设条件有序、高效地访问临界资源。

3.1 条件变量的概念

  • 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
  • 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

举例:"快递柜存取件"

小区的共享快递柜是大家共用的 "临界资源",同一时间只能一个人存或取快递 ------ 这就像互斥锁的作用,确保不会因多人同时操作导致包裹混乱。但实际使用中存在两个关键限制:快递柜满时,快递员(类似 "生产者线程")没法存件,得等有人取件腾出空位;快递柜里没有业主的包裹时,业主(类似 "消费者线程")没法取件,得等快递员存进包裹。

如果只靠互斥锁,麻烦会很突出:快递员赶到后发现柜子满了,只能霸占着操作口(持有锁)反复刷新查看 "有没有空位",既耽误自己送其他快递,又不让后续业主取件,纯属空耗时间;业主过来没找到自己的包裹,也只能守在操作口反复检查,导致快递柜完全陷入 "占着不用" 的僵局,效率极低。

这时候,我们日常用的「快递柜状态 app」和「菜鸟 app」就派上了用场 ------ 它们正是条件变量的现实映射。快递员发现柜子满了,不用死守,直接在状态 app 上设置 "空位提醒",然后离开去送其他快递(相当于释放互斥锁,线程挂起,不占用资源);当有业主取件腾出空位时,快递柜会自动同步状态到 app,app 立刻推送通知给等待的快递员,告诉他 "可以来存件了"(唤醒线程)。

业主的逻辑也一样:没查到自己的包裹时,在菜鸟 app 上开启 "到件提醒",就可以先去做其他事(释放锁挂起);等快递员把包裹存进柜子,系统会通过菜鸟 app 推送取件通知,业主收到后再回来取件(唤醒后重新获取锁)。

核心逻辑不变:互斥锁就像快递柜的 "操作权限",保证同一时间只有一个人使用;而「快递柜状态 app / 菜鸟 app」扮演的 "条件通知角色",就是条件变量的核心作用 ------ 让等待某个条件(有空位 / 有包裹)的线程释放锁、暂时挂起(不空耗资源),等条件满足时通过 "通知" 唤醒,重新获取锁继续执行。两者配合,才能让多线程既安全又高效地协同工作。

3.2 条件变量相关函数及使用

条件变量的定义

cpp 复制代码
#include <pthread.h>

// 全局(静态)条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// 初始化
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
// 销毁
int pthread_cond_destroy(pthread_cond_t *cond);
    参数:
        cond:要初始化的条件变量
        attr:NULL

在指定的条件变量下等待

cpp 复制代码
#include <pthread.h>
// 阻塞线程,让其等待被唤醒
int pthread_cond_wait(pthread_cond_t *restrict cond,
   pthread_mutex_t *restrict mutex);
    参数:
        cond:要在这个条件变量上等待
        mutex:互斥量,后面详细解释
    
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
   pthread_mutex_t *restrict mutex,
   const struct timespec *restrict abstime);	// 超时自己醒来(目前不需要此函数)

唤醒等待条件变量的线程

cpp 复制代码
#include <pthread.h>

int pthread_cond_broadcast(pthread_cond_t *cond);// 唤醒所有的线程
int pthread_cond_signal(pthread_cond_t *cond);	 // 唤醒在该条件变量下等待的一个线程

3.3 举例使用

简单案例:通过条件变量让创建成功的所有线程都进入等待。再利用循环唤醒等待,使得线程按照顺序执行。

  • 我们先使用PTHREAD_MUTEX_INITIALIZER进行测试,对其他细节暂不追究
  • 然后将接口更改成为使用 pthread_cond_init/pthread_cond_destroy 的方式,方便后续进行封装
cpp 复制代码
#include <iostream>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <vector>

const int NUM = 5;
int cnt = 1000;

// 定义全局互斥锁:用来保护临界资源(如cnt),保证同一时间只有一个线程访问
pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;  
// 定义全局条件变量:用来实现线程间的"等待-唤醒"协同,解决线程需满足特定条件才能执行的问题
pthread_cond_t gcond = PTHREAD_COND_INITIALIZER;    

// 线程执行函数:演示条件变量的核心用法
// 核心逻辑说明:
// 1. 线程需要等待"特定条件满足"才能操作临界资源(本例中先让线程等待,由主线程唤醒);
// 2. 判定"条件是否满足"本身就是访问临界资源,所以必须在加锁后的临界区里判断;
// 3. 如果条件不满足需要让线程休眠,这个休眠操作也必须在临界区内部执行;
// 4. 条件变量的两大核心能力:
//    - 让满足条件的线程等待(挂起,不占用CPU);
//    - 让其他线程唤醒等待的线程,实现线程间的同步协作;
void* threadrun(void *args)
{
    std::string name = static_cast<const char*>(args);
    while(true)
    {
        // 1. 先加锁,进入临界区(保护临界资源cnt,也保证条件判断的原子性)
        pthread_mutex_lock(&glock);

        // 2. 调用条件变量等待:
        //    - 执行到这里时,线程会自动释放glock锁(避免死锁,让其他线程能操作临界资源);
        //    - 线程会被挂起,直到主线程调用pthread_cond_signal/broadcast唤醒;
        //    - 被唤醒后,线程会重新竞争获取glock锁,拿到锁后才会继续往下执行;
        pthread_cond_wait(&gcond, &glock);  // glock在pthread_cond_wait之前,会被自动释放掉

        // 3. 被唤醒后执行:操作临界资源(打印并修改cnt)
        std::cout << name << " 计算:" << cnt << std::endl;
        cnt++;

        // 4. 解锁,退出临界区(释放锁,让其他线程能竞争访问临界资源)
        pthread_mutex_unlock(&glock);
    }
}

int main()
{
    std::vector<pthread_t> threads;
    for(int i = 0; i < NUM; i++)
    {
        pthread_t tid;
        char *name = new char[64];
        snprintf(name, 64, "thread-%d", i);
        int n = pthread_create(&tid, nullptr, threadrun, name);
        if(n != 0)
            continue;
        threads.push_back(tid);
        usleep(100);
    }

    // 每隔1s唤醒一个线程
    while(true)
    {
        std::cout << "唤醒所有线程...." << std::endl;
        pthread_cond_broadcast(&gcond);

        // std::cout << "唤醒一个进程" << std::endl;
        // pthread_cond_signal(&gcond);
        sleep(1);
    }

    // 等待所有线程结束(本例中主线程是死循环,这部分代码实际不会执行)
    for(auto &id : threads)
    {
        int m = pthread_join(id, nullptr);
        (void)m;    // 屏蔽未使用变量的警告
    }

    return 0;
}

Makefile

makefile 复制代码
testCond:testCond.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f testCond

4. 生产者消费者模型

4.2 原理

生产者消费者模型:

3种关系:

  • 生产者和生产者之间:竞争关系,互斥关系
  • 消费者和消费者之间:互斥关系
  • 生产者和消费者之间:互斥关系,同步关系

2种角色:

  • 生产者和消费者角色(线程承担)

1个交易场所:

  • 以特定结构构成的一个"内存空间"

😉321原则(便于记忆)

4.2 为何要使用生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。

4.3 生产者消费者模型优点

  1. 生产过程和消费过程解耦

  2. 支持忙闲不均

  3. 提高效率(支持并发:不是体现在入交易场所和出交易场所上,而是在于未来获取任务和处理具体任务是并发的!即:体现在生产过程和消费过程 而++不是从生产到仓库或者从从仓库到消费++)

4.4 基于BlockingQueue的生产者消费者模型

4.4.1 BlockingQueue

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于:当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)

4.4.2 代码编写生产者消费者模型(单+单)

Main.cc

cpp 复制代码
#include <iostream>
#include <pthread.h>
#include "BlockQueue.hpp"
#include <unistd.h>

void* consumer(void* args)
{
    BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);
    while(true)
    {
        sleep(1);
        int data = bq->Pop();
        std::cout << "消费了一个数据:" << data << std::endl;
    }
}

void* productor(void* args)
{
    int data = 1;
    BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);
    while(true)
    {
        // sleep(1);
        std::cout << "生产了一个数据:"<< data << std::endl;

        bq->Equeue(data);
        data++;
    }
}

int main()
{
    // 申请阻塞队列
    BlockQueue<int> *bq = new BlockQueue<int>();

    //构建生产和消费
    pthread_t c, p;

    pthread_create(&c, nullptr, consumer, bq);
    pthread_create(&p, nullptr, productor, bq);

    pthread_join(c, nullptr);
    pthread_join(p, nullptr);

    return 0;
}

BlockQueue.hpp

cpp 复制代码
// 阻塞队列的实现
#pragma once

#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>

const int defaultcap = 5; // for test

template <typename T>
class BlockQueue
{
private:
    bool IsFull(){ return _q.size() >= _cap; }
    bool IsEmpty() { return _q.empty(); }

public:
    BlockQueue(int cap = defaultcap)
        : _cap(cap)
        ,_csleep_num(0)
        ,_psleep_num(0)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_full_cond, nullptr);
        pthread_cond_init(&_empty_cond, nullptr);
    }

    void Equeue(const T &in)
    {
        pthread_mutex_lock(&_mutex);
        // 生产者调用
        while(IsFull())
        {
            // 应该让生产者线程等待
            // 重点1:pthread_cond_wait调用成功,挂起当前线程之前,要先自动释放锁!!
            // 重点2:当线程被唤醒的时候,默认就在临界区内唤醒!要从pthread_cond_wait成功返回,需要当前线程,重新申请锁!!!
            // 重点3:如果我被唤醒,但是申请锁失败了??我就会在锁上继续等待!!!
            _psleep_num++;

            // 问题1:pthread_cond_wait是函数,调用可能失败。则pthread_cond_wait立即返回。会导致下面的push失败
            // 问题2:pthread_cond_wait可能会因为,条件不满足,pthread_cond_wait伪唤醒。即:需要用户唤醒+本身判断同时生效,才会继续push
            pthread_cond_wait(&_full_cond, &_mutex);
            _psleep_num--;
        }
        // 暂定:队列有空间
        _q.push(in);

        // 到此一定有数据了
        // 临时方案
        if(_csleep_num > 0)
        {
            pthread_cond_signal(&_empty_cond);  // 没有锁休眠时唤醒也不影响
            std::cout << "唤醒消费者" << std::endl;
        }

        pthread_mutex_unlock(&_mutex);  // // 唤醒在解锁之前还是之后都可以
    }

    T Pop()
    {
        // 消费者调用
        pthread_mutex_lock(&_mutex);
        if(IsEmpty())
        {
            _csleep_num++;
            pthread_cond_wait(&_empty_cond, &_mutex);
            _csleep_num--;
        }
        T data = _q.front();
        _q.pop();

        // 到此一定有空间
        if(_psleep_num > 0)
        {
            pthread_cond_signal(&_full_cond);
            std::cout << "唤醒生产者" << std::endl;
        }
        pthread_mutex_unlock(&_mutex);   // 唤醒在解锁之前还是之后都可以
        return data;
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_full_cond);
        pthread_cond_destroy(&_empty_cond);
    }

private:
    std::queue<T> _q;   // 临界资源!!
    int _cap; // 容量大小

    pthread_mutex_t _mutex;
    pthread_cond_t _full_cond;
    pthread_cond_t _empty_cond;

    int _csleep_num; //消费者休眠的个数
    int _psleep_num; // 生产者休眠的个数
};

Makefile

makefile 复制代码
cp:Main.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f cp

4.4.3 代码编写生产者消费者模型,模板封装,单+多

Main.cc

cpp 复制代码
#include "BlockQueue.hpp"
#include "Task.hpp"
#include <iostream>
#include <pthread.h>
#include <unistd.h>

void *consumer(void *args)
{
    BlockQueue<task_t> *bq = static_cast<BlockQueue<task_t> *>(args);

    while (true)
    {
        sleep(3);
        // 1. 消费任务
        task_t t = bq->Pop();

        // 2. 处理任务 -- 处理任务的时候,这个任务,已经被拿到线程的上下文中了,不属于队列了
        t();
    }
}

void *productor(void *args)
{
    BlockQueue<task_t> *bq = static_cast<BlockQueue<task_t> *>(args);
    while (true)
    {
        // 1. 获得任务
        //std::cout << "生产了一个任务: " << x << "+" << y << "=?" << std::endl;
        std::cout << "生产了一个任务: " << std::endl;

        // 2. 生产任务
        bq->Equeue(Download);
    }
}

int main()
{
    // 扩展认识: 阻塞队列: 可以放任务吗?
    // 申请阻塞队列
    BlockQueue<task_t> *bq = new BlockQueue<task_t>();

    // 构建单个生产和消费者
    pthread_t c,p;
    pthread_create(&c, nullptr, consumer, bq);
    pthread_create(&p, nullptr, productor, bq);
    pthread_join(c, nullptr);
    pthread_join(p, nullptr);


    // 构建多个生产和消费者
    // pthread_t c[2], p[3];

    // pthread_create(c, nullptr, consumer, bq);
    // pthread_create(c+1, nullptr, consumer, bq);
    // pthread_create(p, nullptr, productor, bq);
    // pthread_create(p+1, nullptr, productor, bq);
    // pthread_create(p+2, nullptr, productor, bq);

    // pthread_join(c[0], nullptr);
    // pthread_join(c[1], nullptr);
    // pthread_join(p[0], nullptr);
    // pthread_join(p[1], nullptr);
    // pthread_join(p[2], nullptr);

    return 0;
}


/*
#include <iostream>
#include <pthread.h>
#include "BlockQueue.hpp"
#include "Task.hpp"
#include <unistd.h>

void* consumer(void* args)
{
    BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);
    while(true)
    {
        sleep(1);
        Task t = bq->Pop();
        t.Execute();
        std::cout << "消费了一个任务:" << t.X() << "+" << t.Y() << "=" << t.Result() << std::endl;
    }
}

void* productor(void* args)
{
    int x = 1;
    int y = 1;
    BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);
    while(true)
    {
        // sleep(1);
        std::cout << "生产了一个任务:"<< x << "+" << y << "=?" << std::endl;
        Task t(x,y);
        bq->Equeue(t);
        x++;y++;
    }
}

int main()
{
    // 申请阻塞队列
    BlockQueue<Task> *bq = new BlockQueue<Task>();

    //构建生产和消费
    pthread_t c, p;

    pthread_create(&c, nullptr, consumer, bq);
    pthread_create(&p, nullptr, productor, bq);

    pthread_join(c, nullptr);
    pthread_join(p, nullptr);

    return 0;
}
*/

BlockQueue.hpp

cpp 复制代码
// 阻塞队列的实现
#pragma once

#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>

const int defaultcap = 5; // for test

template <typename T>
class BlockQueue
{
private:
    bool IsFull(){ return _q.size() >= _cap; }
    bool IsEmpty() { return _q.empty(); }

public:
    BlockQueue(int cap = defaultcap)
        : _cap(cap)
        ,_csleep_num(0)
        ,_psleep_num(0)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_full_cond, nullptr);
        pthread_cond_init(&_empty_cond, nullptr);
    }

    void Equeue(const T &in)
    {
        pthread_mutex_lock(&_mutex);
        // 生产者调用
        while(IsFull())
        {
            // 应该让生产者线程等待
            // 重点1:pthread_cond_wait调用成功,挂起当前线程之前,要先自动释放锁!!
            // 重点2:当线程被唤醒的时候,默认就在临界区内唤醒!要从pthread_cond_wait成功返回,需要当前线程,重新申请锁!!!
            // 重点3:如果我被唤醒,但是申请锁失败了??我就会在锁上继续等待!!!
            _psleep_num++;
            
            std::cout << "生产者,进入休眠了:_psleep_num" << _psleep_num << std::endl;

            // 问题1:pthread_cond_wait是函数,调用可能失败。则pthread_cond_wait立即返回。会导致下面的push失败
            // 问题2:pthread_cond_wait可能会因为,条件不满足,pthread_cond_wait伪唤醒。即:需要用户唤醒+本身判断同时生效,才会继续push
            pthread_cond_wait(&_full_cond, &_mutex);
            _psleep_num--;
        }
        // 暂定:队列有空间
        _q.push(in);

        // 到此一定有数据了
        // 临时方案
        if(_csleep_num > 0)
        {
            pthread_cond_signal(&_empty_cond);  // 没有锁休眠时唤醒也不影响
            std::cout << "唤醒消费者" << std::endl;
        }

        pthread_mutex_unlock(&_mutex);  // // 唤醒在解锁之前还是之后都可以
    }

    T Pop()
    {
        // 消费者调用
        pthread_mutex_lock(&_mutex);
        if(IsEmpty())
        {
            _csleep_num++;
            pthread_cond_wait(&_empty_cond, &_mutex);
            _csleep_num--;
        }
        T data = _q.front();
        _q.pop();

        // 到此一定有空间
        if(_psleep_num > 0)
        {
            pthread_cond_signal(&_full_cond);
            std::cout << "唤醒生产者" << std::endl;
        }
        pthread_mutex_unlock(&_mutex);   // 唤醒在解锁之前还是之后都可以
        return data;
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_full_cond);
        pthread_cond_destroy(&_empty_cond);
    }

private:
    std::queue<T> _q;   // 临界资源!!
    int _cap; // 容量大小

    pthread_mutex_t _mutex;
    pthread_cond_t _full_cond;
    pthread_cond_t _empty_cond;

    int _csleep_num; //消费者休眠的个数
    int _psleep_num; // 生产者休眠的个数
};

Task.hpp

cpp 复制代码
#pragma once
#include <functional>

// 任务形式2
// 定义一个任务类型,返回值void,参数为空
using task_t = std::function<void()>;
void Download()
{
    std::cout << "我是一个下载任务..." << std::endl;
}


// 任务形式1
class Task
{
public:
    Task(){}
    Task(int x, int y)
    :_x(x)
    ,_y(y)
    {

    }

    void Execute()
    {
        _result = _x + _y;
    }

    int X() {return _x;}
    int Y() {return _y;}

    int Result()
    {
        return _result;
    }

private:
    int _x;
    int _y;
    int _result;
};

Makefile

makefile 复制代码
cp:Main.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f cp

4.4.4 C++ queue模拟阻塞队列的生产消费模型

代码:AI自行实现

相关推荐
kalvin_y_liu2 小时前
【2026年经济周期关键节点案例分析】
人工智能
量子炒饭大师2 小时前
Cyber骇客的逻辑节点美学 ——【初阶数据结构与算法】二叉树
c语言·数据结构·c++·链表·排序算法
Wokoo72 小时前
开发者AI大模型学习与接入指南
java·人工智能·学习·架构
骚戴2 小时前
2025 n1n.ai 全栈国产大模型接入列表与实测报告
人工智能·大模型·llm·api·ai gateway
南山乐只2 小时前
【Spring AI 开发指南】ChatClient 基础、原理与实战案例
人工智能·后端·spring ai
oMcLin2 小时前
CentOS 7 频繁出现 “Connection Refused” 错误的原因分析与解决
linux·运维·centos
fpcc3 小时前
C++编程实践—false_type和true_type的实践应用
c++
极客小云3 小时前
【突发公共事件智能分析新范式:基于PERSIA框架与大模型的知识图谱构建实践】
大数据·人工智能·知识图谱
量子炒饭大师3 小时前
Cyber骇客神经塔尖协议 ——【初阶数据结构与算法】堆
c语言·数据结构·c++·二叉树·github·