Linux->多线程4

目录

本文说明

一:概念

1:信号量的介绍

2:PV操作

3:二元信号量模拟实现互斥功能

二:接口

1:定义

2:初始化

3:销毁信号量

4:申请信号量

5:释放信号量

三:信号量下的生产者和消费者模型

1:环形队列的概念

2:环形队列的规则

3:环形队列的信号量和锁

①:两个信号量

②:生产者的行为

③:消费者的行为

④:两把锁

⑤:锁和信号量的顺序

4:环形队列的实现

四:测试代码

1:单生产者单消费者模型

2:多生产者多消费者模型

3:阻塞队列VS环形队列

①:阻塞队列

②:环形队列

五:总代码

1:RingQueue.hpp

2:Task.hpp

3:单生产者消费者模型

4:多生产者消费者模型

5:增加计数功能


本文说明

多线程这个章节,博客共5篇,这是第四篇,介绍信号量下的生产者和消费者模型

一:概念

1:信号量的介绍

保护临界资源的方式除了锁,还有信号量

当临界资源是一个整体的时候,我们常常用锁来进行保护,比如之前的生产者消费者模型中对阻塞队列的保护就是采用锁,但是如果临界资源被分成很多份的时候,我们就会采取信号量来保护

将这块临界资源分割为多个区域,当多个执行流访问临界资源时,我们就可以让这些执行流同时访问临界资源的不同区域,这样会更加高效

但是,信号量相对于锁来说,不仅起到了保护临界资源的作用,更有判断是否有临界资源的作用,申请到了锁,有可能没有临界资源,但申请到了信号量,则一定会有临界资源

所以,信号量是用来衡量资源数目的 只要申请成功 就一定有对应的资源给你使用,所以信号量本身就是一种判断,而锁内部还需要判断是否有临界资源

所以,能用信号量的场景,用信号量所写的代码必定会比锁写的要少得多!

而至于为什么信号量能够反映是否拥有临界资源,那是因为信号量本质是一个计数器,是描述临界资源中资源数目的计数器,所以信号量能够更细粒度的对临界资源进行管理的同时,也能反映是否还有临界资源

就像电影院的票一样,你还能购票说明电影院中还有位置,你不能买则代表无位置了

2:PV操作

所以,申请信号量和释放信号量,是需要对信号量进行增减操作的

信号量的PV操作:

  • P操作:我们将申请信号量称为P操作,申请信号量的本质就是申请获得临界资源中某块资源的使用权限,当申请成功时临界资源中资源的数目应该减一,因此P操作的本质就是让计数器减一。
  • V操作:我们将释放信号量称为V操作,释放信号量的本质就是归还临界资源中某块资源的使用权限,当释放成功时临界资源中资源的数目就应该加一,因此V操作的本质就是让计数器加一。

毫无疑问,PV操作的底层必然是原子性的!

3:二元信号量模拟实现互斥功能

信号量本质是一个计数器,如果将信号量的初始值设置为1,那么此时该信号量叫做二元信号量。

信号量初始值为1,说明信号量所描述的临界资源只有一份,此时信号量的作用基本等价于互斥锁

二:接口

1:定义

和锁和条件变量一样,只需包含对应的头文件之后,就可以直接使用类型sem_t去定义信号量,但是信号量需要该他一个值,比如临界资源被分成了5份,我们就应该将其初始化为5

静态定义:

cpp 复制代码
sem_t g_semaphore = SEM_INITIALIZER(5); 

**解释:**宏的后面的数字,是信号量的初始值。同理,这种定义方式不需要手动初始化和销毁

动态定义:

cpp 复制代码
sem_t my_sem; 

**解释:**动态定义的初始值,就需要在初始化接口中传参设置!同理,这种定义的方式需要手动调用初始化和销毁

2:初始化

初始化信号量的函数叫做sem_init,该函数的函数原型如下:

cpp 复制代码
int sem_init(sem_t *sem, int pshared, unsigned int value);

参数说明:

  • sem:需要初始化的信号量。
  • pshared:传入0值表示线程间共享,传入非零值表示进程间共享。
  • value:信号量的初始值(计数器的初始值)。

返回值说明:

  • 初始化信号量成功返回0,失败返回-1。

3:销毁信号量

销毁信号量的函数叫做sem_destroy,该函数的函数原型如下:

cpp 复制代码
int sem_destroy(sem_t *sem);

参数说明:

  • sem:需要销毁的信号量。

返回值说明:

  • 销毁信号量成功返回0,失败返回-1。

4:申请信号量

申请信号量又叫作等待信号量,函数叫做sem_wait,该函数的函数原型如下:

cpp 复制代码
int sem_wait(sem_t *sem);

参数说明:

  • sem:需要等待的信号量。

返回值说明:

  • 等待信号量成功返回0,信号量的值减一。
  • 等待信号量失败返回-1,信号量的值保持不变。

5:释放信号量

释放信号量又叫作发布信号量,函数叫做sem_post,该函数的函数原型如下:

cpp 复制代码
int sem_post(sem_t *sem);

参数说明:

  • sem:需要发布的信号量。

返回值说明:

  • 发布信号量成功返回0,信号量的值加一。
  • 发布信号量失败返回-1,信号量的值保持不变。

三:信号量下的生产者和消费者模型

1:环形队列的概念

信号量去优化我们的生产者消费者,本质是舍弃之前的阻塞队列,使用环形队列

环形队列如下:

**解释:**生产者和消费者顺时针并发进行工作,一个生成数据,一个消费数据,并不一定是前后并行,因为取决于生产者的生产速度和消费者的消费速度!其次每次对得到的下标进行取模容量,即可形成循环

2:环形队列的规则

要想生产者和消费者正确地并发进行,一个生成数据,一个消费数据,那么我们必须遵循两条规则

**规则一:**生产者和消费者不能对同一个位置进行访问。

**解释:**这是必然的,如果生产者和消费者访问的是环形队列当中的同一个位置,那么此时生产者和消费者就相当于同时对这一块临界资源进行了访问,结果是未知的,所以这是不允许的。

**规则二:**无论是生产者还是消费者,都不应该将对方套一个圈以上。

解释: 也就是消费者和生产者都不可以追上并超过了对方。

换句话说:

  • 生产者 不能跑得太快,把消费者"套圈"了(即生产者在消费者还没消费完一圈时,就又追上来覆盖了消费者还没消费的数据)

  • 消费者 也不能跑得太快,把生产者"套圈"了(即消费者消费得比生产者还快,去读取了生产者还没生产出来的无效数据)

3:环形队列的信号量和锁

①:两个信号量

环形队列有两个信号量,因为信号量不仅保护临界资源,其还具有判断是否有临界资源的作用!

所以:

对于生产者来说,资源就是环形队列中是否有空的位置

对于消费者来说,资源就是环形队列中是否有数据

所以:

信号量blank_sem初始值我们设置为环形队列的容量,因为刚开始时环形队列当中全是空间。

信号量data_sem的初始值我们应该设置为0,因为刚开始时环形队列当中没有数据。

这样设置,在程序一运行,就已经让二者处于不同位置,因为一开始消费者和生产者都去申请信号量,只会有生产者申请成功,消费者的data_sem为0,申请失败

②:生产者的行为

对于生产者来说,生产者每次生产数据前都需要先申请blank_sem:

  • 如果blank_sem的值不为0,则信号量申请成功,此时生产者可以进行生产操作。
  • 如果blank_sem的值为0,则信号量申请失败,此时生产者需要在blank_sem的等待队列下进行阻塞等待,直到环形队列当中有新的空间后再被唤醒。

当生产者生产完数据后,应该释放data_sem:

  • 虽然生产者在进行生产前是对blank_sem进行的P操作,但是当生产者生产完数据,应该对data_sem进行V操作而不是blank_sem。
  • 生产者在生产数据前申请到的是blank位置,当生产者生产完数据后,该位置当中存储的是生产者生产的数据,在该数据被消费者消费之前,该位置不再是blank位置,而应该是data位置
  • 当生产者生产完数据后,意味着环形队列当中多了一个data位置,因此我们应该对data_sem进行V操作。

③:消费者的行为

对于消费者来说,消费者每次消费数据前都需要先申请data_sem:

  • 如果data_sem的值不为0,则信号量申请成功,此时消费者可以进行消费操作。
  • 如果data_sem的值为0,则信号量申请失败,此时消费者需要在data_sem的等待队列下进行阻塞等待,直到环形队列当中有新的数据后再被唤醒。

当消费者消费完数据后,应该释放blank_sem:

  • 虽然消费者在进行消费前是对data_sem进行的P操作,但是当消费者消费完数据,应该对blank_sem进行V操作而不是data_sem。
  • 消费者在消费数据前申请到的是data位置,当消费者消费完数据后,该位置当中的数据已经被消费过了,再次被消费就没有意义了,为了让生产者后续可以在该位置生产新的数据,我们应该将该位置算作blank位置,而不是data位置
  • 当消费者消费完数据后,意味着环形队列当中多了一个blank位置,因此我们应该对blank_sem进行V操作。

④:两把锁

上面的逻辑对于单生产者和单消费者模型的确是对的,但是对于多生产者对消费者,则仍需完善!

多生产者出错场景:

起初多个生产者都会去申请信号量,并且都会申请成功,这意味着,多个生产者可能会在同一个下标进行重复生产,这必然会出错

多个消费者申请信号量,此时不止一个数据,所以不止一个消费者可以申请信号量成功,这意味这多个消费者可能在同一下标下进行获取数据

所以,我们仍需要锁,因为环形队列下,生产者和消费者是并发进行的,所以我们如果让生产者和消费者共用一把锁,则会让其丧失并发的功能,所以我们需要两把锁,一把属于生产者,一把属于消费者!

这样,多个生产者之间只会有一个去访问环形队列,消费者也是同理,就解决了问题

⑤:锁和信号量的顺序

先申请锁,还是申请信号量,都是可以的,但是先申请信号量再申请锁,更加优秀!

因为:

先申请锁再申请信号量,有可能你仍然无法访问临界资源

但是,先申请信号量再申请锁,则代表一但申请到锁,则立刻可以访问临界资源

类似电影院买票:

低效率: 到电影院去买票,存在去了电影院,却还没票的风险
**高效率:**线上买了票再去电影院 ,去了一定能看电影

4:环形队列的实现

RingQueue.hpp代码及其注释如下:

cpp 复制代码
#pragma once

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <vector>

#define NUM 8

template <class T>
class RingQueue
{
private:
    // P操作
    void P(sem_t &s)
    {
        sem_wait(&s);
    }
    // V操作
    void V(sem_t &s)
    {
        sem_post(&s);
    }

public:
    // 构造函数
    RingQueue(int cap = NUM)
        : _cap(cap), _p_pos(0), _c_pos(0)
    {
        _q.resize(_cap);                // 根据用户输入的容量 开辟环形队列的大小
        sem_init(&_blank_sem, 0, _cap); // 初始化信号量blank_sem   初始值设置为环形队列的容量
        sem_init(&_data_sem, 0, 0);     // 初始化信号量data_sem 初始值设置为0

        pthread_mutex_init(&_productor_mutex, nullptr); // 初始化生产者锁
        pthread_mutex_init(&_consumer_mutex, nullptr);  // 初始化消费者锁
    }

    // 析构函数
    ~RingQueue()
    {
        sem_destroy(&_blank_sem); // 销毁信号量blank_sem
        sem_destroy(&_data_sem);  // 销毁信号量data_sem

        pthread_mutex_destroy(&_productor_mutex); // 销毁生产者锁
        pthread_mutex_destroy(&_consumer_mutex);  // 销毁消费者锁
    }

    // 向环形队列插入数据(生产者调用)
    void Push(const T &data)
    {
        P(_blank_sem);                         // 生产者申请信号量blank_sem
        pthread_mutex_lock(&_productor_mutex); // 生产者申请生产者锁

        _q[_p_pos] = data; // 进行插入操作

        // 更新下一次生产的位置
        _p_pos++;
        _p_pos %= _cap; // 取模

        pthread_mutex_unlock(&_productor_mutex); // 生产者销毁生产者锁
        V(_data_sem);                            // 生产者销毁信号量blank_sem
    }

    // 从环形队列获取数据(消费者调用)
    void Pop(T &data)
    {
        P(_data_sem);                         // 消费者申请信号量data_sem
        pthread_mutex_lock(&_consumer_mutex); // 消费者申请消费者锁

        data = _q[_c_pos]; // 进行取出操作

        // 更新下一次消费的位置
        _c_pos++;
        _c_pos %= _cap; // 取模

        pthread_mutex_unlock(&_consumer_mutex); // 消费者销毁消费者锁
        V(_blank_sem);                          // 消费者销毁信号量data_sem
    }

private:
    std::vector<T> _q; // 环形队列
    int _cap;          // 环形队列的容量上限
    int _p_pos;        // 生产位置
    int _c_pos;        // 消费位置
    sem_t _blank_sem;  // 描述空间资源
    sem_t _data_sem;   // 描述数据资源

    // 定义两把锁
    pthread_mutex_t _productor_mutex; // 维护多生产之间的互斥关系
    pthread_mutex_t _consumer_mutex;  // 维护多消费之间的互斥关系
};

四:测试代码

1:单生产者单消费者模型

cpp 复制代码
#include "RingQueue.hpp"
#include "Task.hpp"
pthread_mutex_t cout_mutex = PTHREAD_MUTEX_INITIALIZER;
 
void *Producer(void *arg)
{
    RingQueue<Task> *rq = (RingQueue<Task> *)arg;
    const char *arr = "+-*/%";
    // 生产者不断进行生产
    while (true)
    {
        sleep(1);
        int x = rand() % 100;
        int y = rand() % 100;
        char op = arr[rand() % 5];
        Task t(x, y, op);
        rq->Push(t); // 生产数据
        pthread_mutex_lock(&cout_mutex);
        std::cout << "生产任务:" << x << " " << op << " " << y << " = ?" << std::endl;
        pthread_mutex_unlock(&cout_mutex);
    }
}
void *Consumer(void *arg)
{
    RingQueue<Task> *rq = (RingQueue<Task> *)arg;
    // 消费者不断进行消费
    while (true)
    {
        sleep(1);
        Task t;
        rq->Pop(t); // 消费数据
        pthread_mutex_lock(&cout_mutex);
        int result = t.Run(); // 处理数据
        std::cout << "消费任务: " << t.ToString() << " " << "=" << " " << result << std::endl;
        pthread_mutex_unlock(&cout_mutex);
    }
}
 
int main()
{
    srand((unsigned int)time(nullptr));          // 种一颗随机数种子
    pthread_t producer, consumer;                // 分布存储消费者和生产者的线程ID
    RingQueue<Task> *rq = new RingQueue<Task>; // new一个阻塞队列 类型为Task
    // 创建生产者线程和消费者线程
    pthread_create(&producer, nullptr, Producer, rq); // 创建一个生产者
    pthread_create(&consumer, nullptr, Consumer, rq); // 创建一个消费者
 
    // join生产者线程和消费者线程
    pthread_join(producer, nullptr);
    pthread_join(consumer, nullptr);
 
    delete rq;
    return 0;
}

运行结果:

2:多生产者多消费者模型

cpp 复制代码
#include "RingQueue.hpp"
#include "Task.hpp"
#define CONSUMER_COUNT 3
#define PRODUCER_COUNT 3
pthread_mutex_t cout_mutex = PTHREAD_MUTEX_INITIALIZER;
 
void *Producer(void *arg)
{
    RingQueue<Task> *rq = (RingQueue<Task> *)arg;
    const char *arr = "+-*/%";
    // 生产者不断进行生产
    while (true)
    {
        sleep(1);
        int x = rand() % 100;
        int y = rand() % 100;
        char op = arr[rand() % 5];
        Task t(x, y, op);
        rq->Push(t); // 生产数据
        pthread_mutex_lock(&cout_mutex);
        std::cout << "生产任务:" << x << " " << op << " " << y << " = ?" << std::endl;
        pthread_mutex_unlock(&cout_mutex);
    }
}
void *Consumer(void *arg)
{
    RingQueue<Task> *rq = (RingQueue<Task> *)arg;
    // 消费者不断进行消费
    while (true)
    {
        sleep(1);
        Task t;
        rq->Pop(t); // 消费数据
        pthread_mutex_lock(&cout_mutex);
        int result = t.Run(); // 处理数据
        std::cout << "消费任务: " << t.ToString() << " " << "=" << " " << result << std::endl;
        pthread_mutex_unlock(&cout_mutex);
    }
}
 
int main()
{
    srand((unsigned int)time(nullptr)); // 种一颗随机数种子
 
    // 存储消费者和生产者的线程ID
    std::vector<pthread_t> producers(PRODUCER_COUNT);
    std::vector<pthread_t> consumers(CONSUMER_COUNT); 
    RingQueue<Task> *rq = new RingQueue<Task>;      // new一个阻塞队列 类型为Task
    // 创建生产者线程和消费者线程
 
    // 创建多个生产者线程
    for (int i = 0; i < PRODUCER_COUNT; ++i)
    {
        pthread_create(&producers[i], nullptr, Producer, rq);
    }
 
    // 创建多个消费者线程
    for (int i = 0; i < CONSUMER_COUNT; ++i)
    {
        pthread_create(&consumers[i], nullptr, Consumer, rq);
    }
 
    // join所有生产者线程
    for (int i = 0; i < PRODUCER_COUNT; ++i)
    {
        pthread_join(producers[i], nullptr);
    }
 
    // join所有消费者线程
    for (int i = 0; i < CONSUMER_COUNT; ++i)
    {
        pthread_join(consumers[i], nullptr);
    }
 
    delete rq;
    return 0;
}

运行结果:

3:阻塞队列VS环形队列

但是无法体现出信号量的优秀之处,所以我们修改代码,让上篇博客的生产者消费者模型和本文的效率进行对比,所以我们用一个变量来表示消费者解决掉任务的次数!阻塞队列和环形队列均是在各自队列容量为5,三个生产者,三个消费者的条件下去进行对比!

①:阻塞队列

②:环形队列

**注:**在测试代码中,最大的性能瓶颈是 std::cout 输出操作。控制台输出是非常慢的操作,尤其是在多线程环境下使用互斥锁保护时。无论队列实现多么高效,输出操作都会成为主要性能限制因素

五:总代码

1:RingQueue.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <vector>

#define NUM 5

template <class T>
class RingQueue
{
private:
    // P操作
    void P(sem_t &s)
    {
        sem_wait(&s);
    }
    // V操作
    void V(sem_t &s)
    {
        sem_post(&s);
    }

public:
    // 构造函数
    RingQueue(int cap = NUM)
        : _cap(cap), _p_pos(0), _c_pos(0)
    {
        _q.resize(_cap);                // 根据用户输入的容量 开辟环形队列的大小
        sem_init(&_blank_sem, 0, _cap); // 初始化信号量blank_sem   初始值设置为环形队列的容量
        sem_init(&_data_sem, 0, 0);     // 初始化信号量data_sem 初始值设置为0

        pthread_mutex_init(&_productor_mutex, nullptr); // 初始化生产者锁
        pthread_mutex_init(&_consumer_mutex, nullptr);  // 初始化消费者锁
    }

    // 析构函数
    ~RingQueue()
    {
        sem_destroy(&_blank_sem); // 销毁信号量blank_sem
        sem_destroy(&_data_sem);  // 销毁信号量data_sem

        pthread_mutex_destroy(&_productor_mutex); // 销毁生产者锁
        pthread_mutex_destroy(&_consumer_mutex);  // 销毁消费者锁
    }

    // 向环形队列插入数据(生产者调用)
    void Push(const T &data)
    {
        P(_blank_sem);                         // 生产者申请信号量blank_sem
        pthread_mutex_lock(&_productor_mutex); // 生产者申请生产者锁

        _q[_p_pos] = data; // 进行插入操作

        // 更新下一次生产的位置
        _p_pos++;
        _p_pos %= _cap; // 取模

        pthread_mutex_unlock(&_productor_mutex); // 生产者销毁生产者锁
        V(_data_sem);                            // 生产者销毁信号量blank_sem
    }

    // 从环形队列获取数据(消费者调用)
    void Pop(T &data)
    {
        P(_data_sem);                         // 消费者申请信号量data_sem
        pthread_mutex_lock(&_consumer_mutex); // 消费者申请消费者锁

        data = _q[_c_pos]; // 进行取出操作

        // 更新下一次消费的位置
        _c_pos++;
        _c_pos %= _cap; // 取模

        pthread_mutex_unlock(&_consumer_mutex); // 消费者销毁消费者锁
        V(_blank_sem);                          // 消费者销毁信号量data_sem
    }

private:
    std::vector<T> _q; // 环形队列
    int _cap;          // 环形队列的容量上限
    int _p_pos;        // 生产位置
    int _c_pos;        // 消费位置
    sem_t _blank_sem;  // 描述空间资源
    sem_t _data_sem;   // 描述数据资源

    // 定义两把锁
    pthread_mutex_t _productor_mutex; // 维护多生产之间的互斥关系
    pthread_mutex_t _consumer_mutex;  // 维护多消费之间的互斥关系
};

2:Task.hpp

cpp 复制代码
#pragma once
#pragma once
#include <iostream>
#include <string>

class Task
{
public:
    Task(int x = 0, int y = 0, char op = 0) // op应该是char类型
        : _x(x), _y(y), _op(op)
    {
    }
    ~Task()
    {
    }
    
    int Run()
    {
        switch (_op)
        {
        case '+':
            return _x + _y;
        case '-':
            return _x - _y;
        case '*':
            return _x * _y;
        case '/':
            if (_y == 0)
            {
                std::cout << "Warning: div zero!" << std::endl;
                return -10086;
            }
            return _x / _y;
        case '%':
            if (_y == 0)
            {
                std::cout << "Warning: mod zero!" << std::endl;
                return -10086;
            }
            return _x % _y;
        default:
            std::cout << "error operation!" << std::endl;
            return -10086;
        }
    }

    std::string ToString() const
    {
        return std::to_string(_x) + " " + _op + " " + std::to_string(_y);
    }

private:
    int _x;
    int _y;
    char _op;
};

3:单生产者消费者模型

cpp 复制代码
#include "RingQueue.hpp"
#include "Task.hpp"
pthread_mutex_t cout_mutex = PTHREAD_MUTEX_INITIALIZER;
 
void *Producer(void *arg)
{
    RingQueue<Task> *rq = (RingQueue<Task> *)arg;
    const char *arr = "+-*/%";
    // 生产者不断进行生产
    while (true)
    {
        sleep(1);
        int x = rand() % 100;
        int y = rand() % 100;
        char op = arr[rand() % 5];
        Task t(x, y, op);
        rq->Push(t); // 生产数据
        pthread_mutex_lock(&cout_mutex);
        std::cout << "生产任务:" << x << " " << op << " " << y << " = ?" << std::endl;
        pthread_mutex_unlock(&cout_mutex);
    }
}
void *Consumer(void *arg)
{
    RingQueue<Task> *rq = (RingQueue<Task> *)arg;
    // 消费者不断进行消费
    while (true)
    {
        sleep(1);
        Task t;
        rq->Pop(t); // 消费数据
        pthread_mutex_lock(&cout_mutex);
        int result = t.Run(); // 处理数据
        std::cout << "消费任务: " << t.ToString() << " " << "=" << " " << result << std::endl;
        pthread_mutex_unlock(&cout_mutex);
    }
}
 
int main()
{
    srand((unsigned int)time(nullptr));          // 种一颗随机数种子
    pthread_t producer, consumer;                // 分布存储消费者和生产者的线程ID
    RingQueue<Task> *rq = new RingQueue<Task>; // new一个阻塞队列 类型为Task
    // 创建生产者线程和消费者线程
    pthread_create(&producer, nullptr, Producer, rq); // 创建一个生产者
    pthread_create(&consumer, nullptr, Consumer, rq); // 创建一个消费者
 
    // join生产者线程和消费者线程
    pthread_join(producer, nullptr);
    pthread_join(consumer, nullptr);
 
    delete rq;
    return 0;
}

4:多生产者消费者模型

cpp 复制代码
#include "RingQueue.hpp"
#include "Task.hpp"
#define CONSUMER_COUNT 3
#define PRODUCER_COUNT 3
pthread_mutex_t cout_mutex = PTHREAD_MUTEX_INITIALIZER;
 
void *Producer(void *arg)
{
    RingQueue<Task> *rq = (RingQueue<Task> *)arg;
    const char *arr = "+-*/%";
    // 生产者不断进行生产
    while (true)
    {
        sleep(1);
        int x = rand() % 100;
        int y = rand() % 100;
        char op = arr[rand() % 5];
        Task t(x, y, op);
        rq->Push(t); // 生产数据
        pthread_mutex_lock(&cout_mutex);
        std::cout << "生产任务:" << x << " " << op << " " << y << " = ?" << std::endl;
        pthread_mutex_unlock(&cout_mutex);
    }
}
void *Consumer(void *arg)
{
    RingQueue<Task> *rq = (RingQueue<Task> *)arg;
    // 消费者不断进行消费
    while (true)
    {
        sleep(1);
        Task t;
        rq->Pop(t); // 消费数据
        pthread_mutex_lock(&cout_mutex);
        int result = t.Run(); // 处理数据
        std::cout << "消费任务: " << t.ToString() << " " << "=" << " " << result << std::endl;
        pthread_mutex_unlock(&cout_mutex);
    }
}
 
int main()
{
    srand((unsigned int)time(nullptr)); // 种一颗随机数种子
 
    // 存储消费者和生产者的线程ID
    std::vector<pthread_t> producers(PRODUCER_COUNT);
    std::vector<pthread_t> consumers(CONSUMER_COUNT); 
    RingQueue<Task> *rq = new RingQueue<Task>;      // new一个阻塞队列 类型为Task
    // 创建生产者线程和消费者线程
 
    // 创建多个生产者线程
    for (int i = 0; i < PRODUCER_COUNT; ++i)
    {
        pthread_create(&producers[i], nullptr, Producer, rq);
    }
 
    // 创建多个消费者线程
    for (int i = 0; i < CONSUMER_COUNT; ++i)
    {
        pthread_create(&consumers[i], nullptr, Consumer, rq);
    }
 
    // join所有生产者线程
    for (int i = 0; i < PRODUCER_COUNT; ++i)
    {
        pthread_join(producers[i], nullptr);
    }
 
    // join所有消费者线程
    for (int i = 0; i < CONSUMER_COUNT; ++i)
    {
        pthread_join(consumers[i], nullptr);
    }
 
    delete rq;
    return 0;
}

5:增加计数功能

cpp 复制代码
#include "RingQueue.hpp"
#include "Task.hpp"
#define CONSUMER_COUNT 3
#define PRODUCER_COUNT 3
pthread_mutex_t cout_mutex = PTHREAD_MUTEX_INITIALIZER;
int count = 0;

void *Producer(void *arg)
{
    RingQueue<Task> *rq = (RingQueue<Task> *)arg;
    const char *arr = "+-*/%";
    // 生产者不断进行生产
    while (true)
    {
        int x = rand() % 100;
        int y = rand() % 100;
        char op = arr[rand() % 5];
        Task t(x, y, op);
        rq->Push(t); // 生产数据
        pthread_mutex_lock(&cout_mutex);
        std::cout << "生产任务:" << x << " " << op << " " << y << " = ?" << std::endl;
        pthread_mutex_unlock(&cout_mutex);
    }
}
void *Consumer(void *arg)
{
    RingQueue<Task> *rq = (RingQueue<Task> *)arg;
    // 消费者不断进行消费
    while (true)
    {
        Task t;
        rq->Pop(t); // 消费数据
        pthread_mutex_lock(&cout_mutex);
        count++;
        int result = t.Run(); // 处理数据
        std::cout << "消费任务: " << t.ToString() << " " << "=" << " " << result << " " << "已处理任务" << count << std::endl;
        pthread_mutex_unlock(&cout_mutex);
    }
}

int main()
{
    srand((unsigned int)time(nullptr)); // 种一颗随机数种子

    // 存储消费者和生产者的线程ID
    std::vector<pthread_t> producers(PRODUCER_COUNT);
    std::vector<pthread_t> consumers(CONSUMER_COUNT);
    RingQueue<Task> *rq = new RingQueue<Task>; // new一个阻塞队列 类型为Task
    // 创建生产者线程和消费者线程

    // 创建多个生产者线程
    for (int i = 0; i < PRODUCER_COUNT; ++i)
    {
        pthread_create(&producers[i], nullptr, Producer, rq);
    }

    // 创建多个消费者线程
    for (int i = 0; i < CONSUMER_COUNT; ++i)
    {
        pthread_create(&consumers[i], nullptr, Consumer, rq);
    }

    // join所有生产者线程
    for (int i = 0; i < PRODUCER_COUNT; ++i)
    {
        pthread_join(producers[i], nullptr);
    }

    // join所有消费者线程
    for (int i = 0; i < CONSUMER_COUNT; ++i)
    {
        pthread_join(consumers[i], nullptr);
    }

    delete rq;
    return 0;
}
相关推荐
练习时长两年半的Java练习生(升级中)几秒前
从0开始学习Java+AI知识点总结-27.web实战(Maven高级)
java·学习·maven
拾忆,想起16 分钟前
Redis发布订阅:实时消息系统的极简解决方案
java·开发语言·数据库·redis·后端·缓存·性能优化
钮钴禄·爱因斯晨23 分钟前
Linux(一) | 初识Linux与目录管理基础命令掌握
linux·运维·服务器
AllyLi022426 分钟前
CondaError: Run ‘conda init‘ before ‘conda activate‘
linux·开发语言·笔记·python
艾莉丝努力练剑28 分钟前
【C语言16天强化训练】从基础入门到进阶:Day 14
java·c语言·学习·算法
羑悻的小杀马特29 分钟前
【C++高并发内存池篇】ThreadCache 极速引擎:C++ 高并发内存池的纳秒级无锁革命!
开发语言·c++·多线程·高性能内存池
BioRunYiXue37 分钟前
FRET、PLA、Co-IP和GST pull-down有何区别? 应该如何选择?
java·服务器·网络·人工智能·网络协议·tcp/ip·eclipse
SimonKing1 小时前
想搭建知识库?Dify、MaxKB、Pandawiki 到底哪家强?
java·后端·程序员
Coision.1 小时前
linux 网络:并发服务器及IO多路复用
linux·服务器·网络
程序员清风1 小时前
为什么Tomcat可以把线程数设置为200,而不是2N?
java·后端·面试