Linux线程的同步与互斥(二) 条件变量+信号量

news2024/11/25 16:47:37

文章目录

  • 二、线程同步
      • 条件变量
        • 1、条件变量的概念
        • 2、同步概念与竞态条件
        • 3、条件变量函数初始化
        • 4、条件变量函数销毁
        • 5、条件变量函数等待
        • 6、条件变量函数唤醒等待
        • 生产者消费者模型
          • 1、理论部分
          • 2、“3 2 1”原则
          • 3、基于阻塞队列的生产者消费者模型
      • POSIX信号量
        • 1、信号量的概念
        • 2、信号量操作函数
          • ①初始化信号量
          • ②销毁信号量
          • ③等待信号量 P( )
          • ④发布信号量 V( )
        • 3、基于环形队列的生产者消费者模型
          • ①认识环形队列
          • ②多线程下的环形队列
          • ③具体代码实现
          • ④改进:改成多生产者、多消费者模型

二、线程同步

例如:当一个线程访问队列时,发现队列为空时只能等待,直到其他线程将一个节点添加到队列中,显然,只有互斥锁的情况下,我们比较困难的知道临界资源的状态,此时我们需要一种机制或者策略来知道临界资源的状态——条件变量!

条件变量

1、条件变量的概念

条件变量类似于if语句,他有“”、“”两个状态,在条件变量使用的过程中,一个线程等待条件为“真”,另一个线程在使用完临界资源后将条件设置为“真”,唤醒阻塞在等待条件变量为“真”的线程,执行其任务。在这个过程中,必须保证在并行或者并发的条件下使得条件变量在“真”“假”两个状态之间正确转换,所以条件变量一般需要和互斥锁配合使用,由此实现对于临界资源的互斥访问!

2、同步概念与竞态条件

  • 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。
  • 竞态条件:因为时序问题而导致程序异常,我们常称为竞态条件。在线程场景下,这种问题也不难理解。

3、条件变量函数初始化

如果你想要的创建条件变量,只需要在类中的或者全局创建一个pthread_cond_t类型的变量,这样原生线程库就会自动帮你创建好条件变量,然后你就可以开始一系列的操作。

函数名称pthread_cond_init
函数功能初始化条件变量
头文件#include<pthread.h>
函数原型int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
参数cond:指向要初始化的条件变量的指针
attr:指向条件变量属性的指针,来指定条件变量的一些属性,但是我们这里给NULL,表明使用默认属性来初始化该条件变量
返回值0:成功
!0:失败

4、条件变量函数销毁

函数名称pthread_cond_destroy
函数功能销毁条件变量
头文件#include<pthread.h>
函数原型int pthread_cond_destroy(pthread_cond_t *cond);
参数cond:需要销毁的条件变量
返回值0:成功
!0:失败

5、条件变量函数等待

函数名称pthread_cond_wait
函数功能等待条件变量被设置
头文件#include<pthread.h>
函数原型int pthread_cond_wait( pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
参数cond:需要等待的条件变量
mutex:与条件变量相关的互斥锁
返回值0:成功
!0:失败

6、条件变量函数唤醒等待

函数名称pthread_cond_signal
函数功能唤醒一个等待的线程
头文件#include<pthread.h>
函数原型int pthread_cond_signal(pthread_cond_t *cond);
参数cond:需要通知的条件变量的指针
返回值0:成功
!0:失败

问题:pthread_cond_signal等待的是哪一个线程?

在条件变量下的等待队列里等待的第一个线程!

函数名称pthread_cond_broadcast
函数功能唤醒所有等待的线程
头文件#include<pthread.h>
函数原型int pthread_cond_broadcast(pthread_cond_t *cond);
参数cond:需要广播通知的条件变量的指针
返回值0:成功
!0:失败

修改部分代码:
在这里插入图片描述
结果展示:
在这里插入图片描述

我们现在想要实现一个简单的逻辑,boss发出指令去控制工作线程工作:

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mtx;
pthread_cond_t cond;
// ctrl线程控制 work线程,定期完成任务
void *ctrl(void *args)
{
    std::string name=(char*)(args);
    while(true)
    {
        std::cout<<"master say:begin work!"<<std::endl;
        // 唤醒在条件变量下等待的一个线程
        pthread_cond_signal(&cond);
        sleep(5);
    }
}
void *work(void *args)
{
    int number = *(int *)args;
    delete (int *)args;
    while (true)
    {
        // 等待条件变量被设置
        pthread_cond_wait(&cond,&mtx);
        std::cout << "work: " << number << " is working..." << std::endl;
    }
}

// 一个主线程控制另外3个线程
int main()
{
#define NUM 3
    pthread_mutex_init(&mtx,nullptr);
    pthread_cond_init(&cond,nullptr);
    pthread_t master;
    pthread_t worker[NUM];
    pthread_create(&master, nullptr, ctrl, (void*)"boss");
    for (int i = 0; i < NUM; i++)
    {
        int *number = new int(i);
        pthread_create(worker + i, nullptr, work, (void *)number);
    }
    for (int i = 0; i < NUM; i++)
    {
        pthread_join(worker[i], nullptr);
    }
    pthread_join(master, nullptr);

    pthread_mutex_destroy(&mtx);
    pthread_cond_destroy(&cond);
    return 0;
}

结果展示:
在这里插入图片描述
现象:老板发出命令,线程才开始工作,而且每个线程都是按序工作(2->1->0)。
当2号线程,1号线程他们跑完自己的任务,去干什么了?
答案:他又跑去当前条件变量下等待去了

结论:每个线程运行完后都去死循环等待了,条件变量内部一定存在一个等待队列。

生产者消费者模型

在这里插入图片描述

1、理论部分

超市——生产者消费者模型
主要有两大优点

  • 提高效率
  • 将生产环节与消费环节解耦

为什么要有超市?
收集需求,减少交易成本

2、“3 2 1”原则
  • 3种关系
    生产者与生产者:竞争、互斥关系
    消费者与消费者:竞争、互斥关系
    生产者与消费者:互斥、同步关系
  • 2个角色
    生产者与消费者,通常n :n
  • 1个交易场所
    超市->(交易场所,通常就是一段缓冲区,可以是一段内存空间、STL容器等)
3、基于阻塞队列的生产者消费者模型

当前我们的策略
1、当生产满时,就不要生产了(不要竞争锁了),而应该让消费者来消费
2、当消费空了,就不应该消费(不要竞争锁了),而应该让生产者来生产

所以我们的条件变量有两个:

template <class T>
class BlockQueue
{
private:
	std::queue<T> bq_; // 阻塞队列
int cap_;
	pthread_mutex_t mtx_;    // 保护临界资源的锁
	pthread_cond_t is_full_; // bq_满的,消费者在该条件变量下等待
	pthread_cond_t is_empty; // bq_空的,生产者在该条件变量下等待
puulic:
// code
};

const &:输入型参数
*:输出型参数
&:输入输出型参数

void Push(const T &in)
{
    LockQueue();
    if (IsFull())
    {
        //等待会将线程挂起,且当前持有锁(别人来申请会造成死锁问题)
        ProducterWait();
    }
    bq_.push(in);

     if(bq_.size()>cap_/2)WakeupConsumer();
    UnLockQueue();
    //WakeupConsumer();  // 在内在外都可以
}

void Pop(T *out)
{
    LockQueue();
    if (IsEmpty())
    {
        //让消费者等待
        ConsumerWait();
    }
    *out = bq_.front();
    bq_.pop();

    if(bq_.size()<cap_/2)WakeupProducter();
    UnLockQueue();
   //WakeupProducter();
}
  

pthread_cond_wait函数的功能:
1、调用时,会首先自动释放mtx_,然后再挂起自己!
2、返回时,会首先自动竞争锁,获得锁之后,才能返回!

void ProducerWait()
{
    pthread_cond_wait(&is_empty_, &mtx_);
}
void ConsumerWait()
{
    pthread_cond_wait(&is_full_, &mtx_);
}

下面我们来实现我们全部的生产消费逻辑代码:
#pragma once
#include <iostream>
#include <string>
#include <queue>
#include <unistd.h>
#include <pthread.h>

namespace ns_blockqueue
{
    const int default_cap = 5;
    template <class T>
    class BlockQueue
    {
    private:
        std::queue<T> bq_; // 阻塞队列
        int cap_;
        pthread_mutex_t mtx_;     // 保护临界资源的锁
        pthread_cond_t is_full_;  // bq_满的,消费者在该条件变量下等待
        pthread_cond_t is_empty_; // bq_空的,生产者在该条件变量下等待
    private:
        // 判断队列是否为满
        bool IsFull()
        {
            return bq_.size() == cap_;
        }
        bool IsEmpty()
        {
            return bq_.size() == 0;
        }
        void LockQueue()
        {
            pthread_mutex_lock(&mtx_);
        }
        void UnlockQueue()
        {
            pthread_mutex_unlock(&mtx_);
        }

        void ProducerWait()
        {
            pthread_cond_wait(&is_empty_, &mtx_);
        }
        void ConsumerWait()
        {
            pthread_cond_wait(&is_full_, &mtx_);
        }

        void WakeupConsumer()
        {
            pthread_cond_signal(&is_full_);
        }
        void WakeupProducer()
        {
            pthread_cond_signal(&is_empty_);
        }

    public:
        // 构造函数中对锁和条件变量进行初始化
        BlockQueue(int cap = default_cap)
            : cap_(cap)
        {
            pthread_mutex_init(&mtx_, nullptr);
            pthread_cond_init(&is_empty_, nullptr);
            pthread_cond_init(&is_full_, nullptr);
        }

        ~BlockQueue()
        {
            pthread_mutex_destroy(&mtx_);
            pthread_cond_destroy(&is_empty_);
            pthread_cond_destroy(&is_full_);
        }

    public:
        // 向队列中放数据
        void Push(const T &in)
        {
            LockQueue();
            if (IsFull()) // bug?
            {
                // 条件不满足时,让生产线程等待
                ProducerWait();
            }
            bq_.push(in);
            if (bq_.size() > cap_ / 2)
                WakeupConsumer();
            UnlockQueue();
        }
        // 从队列中出数据
        void Pop(T *out)
        {
            LockQueue();
            if (IsEmpty())
            {
                ConsumerWait();
            }
            *out = bq_.front();
            bq_.pop();
            if (bq_.size() < cap_ / 2)
                WakeupProducer();
            UnlockQueue();
        }
    };
}
//
#include "BlockQueue.hpp"
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
using namespace ns_blockqueue;

void *consumer(void *args)
{
    BlockQueue<int> *bq = (BlockQueue<int> *)(args);
    while (true)
    {
        sleep(2); //控制同步节奏
        int data=0;
        bq->Pop(&data);
        std::cout<<"消费者消费了一个数据:"<<data<<std::endl;
    }
}
void *producer(void *args)
{
    BlockQueue<int> *bq = (BlockQueue<int> *)(args);
    while (true)
    {
        // 制造数据
        //sleep(2); //控制同步节奏
        int data = rand() % 20 + 1;
        std::cout << "生产者生了一个数据:" << data << std::endl;
        bq->Push(data);
    }
}
int main()
{
    // 随机数种子
    srand((unsigned int)time(nullptr));
    BlockQueue<int> *bq = new BlockQueue<int>();

    pthread_t c;
    pthread_t p;

    pthread_create(&c, nullptr, consumer, (void *)bq); // 将bq传过去,两个线程就看到了同一份资源(缓冲区队列)
    pthread_create(&p, nullptr, producer, (void *)bq);

    pthread_join(c, nullptr);
    pthread_join(p, nullptr);
    return 0;
}

结果展示:
在这里插入图片描述


此处有个bug!!!!
在这里插入图片描述
使用if来进行条件判断是不太完善的!
如果出现了如图所示的两种情况,当函数ProducterWait返回时,那么if语句是默认顺序向下执行的,并不满足生产函数的条件,所以我们需要再次进行判断,将if换成while。

我们需要进行条件检测的时候,这里需要使用循环的方式,来保证退出循环一定是因为条件不满足导致的!

LockQueue();
// if (IsFull()) // bug?
while (IsEmpty())
{
    // 条件不满足时,让生产线程等待
    ProducerWait();
}
bq_.push(in);
if (bq_.size() > cap_ / 2)
    WakeupConsumer(); // 只有生产者知道,消费者什么时候可以消费
UnlockQueue();

改进
生产和消费,传输数据只是第一步
我们还需要解决两个问题
1、数据怎么来的?耗时吗?
2、数据怎么处理?耗时吗?

所以我们还需要添加一个场景–>任务处理

//  "task.hpp"
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
namespace ns_task
{
    class Task
    {
    private:
        int x_;
        int y_;
        char op_; // +-*/%
    public:
        Task() {}
        Task(int x, int y, char op) : x_(x), y_(y), op_(op)
        {
        }

        int Run()
        {
            int res = 0;
            switch (op_)
            {

            case '+':
                res = x_ + y_;
                break;
            case '-':
                res = x_ - y_;
                break;
            case '*':
                res = x_ * y_;
                break;
            case '/':
                res = x_ / y_;
                break;
            case '%':
                res = x_ % y_;
                break;
            default:
                std::cout << "错误的运算" << std::endl;
                break;
            }
            std::cout << "当前任务正在被[" << pthread_self() << "]处理" << x_ << op_ << y_ << "=" << res << std::endl;
            std::cout<<"-----------------------------"<<std::endl;
            return res;
        }

        int operator()()
        {
            return Run();
        }
        ~Task() {}
    };
} // namespace ns_task

///
#include "task.hpp"
using namespace ns_task;
void *consumer(void *args)
{
    BlockQueue<Task> *bq = (BlockQueue<Task> *)(args);
    while (true)
    {
        Task t;
        bq->Pop(&t);

        // 任务处理
        t();
    }
}
void *producer(void *args)
{
    BlockQueue<Task> *bq = (BlockQueue<Task> *)(args);
    std::string ops = "+-*/%";
    while (true)
    {
        // 制造数据
        int x = rand() % 20 + 1;
        int y = rand() % 10 + 1;
        char op = ops[rand() % 5];
        Task t(x, y, op);
        std::cout << "生产者派发了一个任务: " << x << op << y << "=?" << std::endl;
        bq->Push(t);
        sleep(1);  
    }
}
int main()
{
    // 随机数种子
    srand((unsigned int)time(nullptr));
    BlockQueue<Task> *bq = new BlockQueue<Task>();

    pthread_t c,p;
    pthread_t c1,c2,c3,c4;
    pthread_create(&c, nullptr, consumer, (void*)bq);
    pthread_create(&c1, nullptr, consumer, (void*)bq);
    pthread_create(&c2, nullptr, consumer, (void*)bq);
    pthread_create(&c3, nullptr, consumer, (void*)bq);
    pthread_create(&c4, nullptr, consumer, (void*)bq);
    pthread_create(&p, nullptr, producer, (void*)bq);

    pthread_join(c, nullptr);
    pthread_join(c1, nullptr);
    pthread_join(c2, nullptr);
    pthread_join(c3, nullptr);
    pthread_join(c4, nullptr);
    pthread_join(p, nullptr);
    return 0;
}

运行结果:
在这里插入图片描述

POSIX信号量

1、信号量的概念

信号量的本质是一把计数器,描述临界资源中资源数目的大小(最多能有多少资源分配给线程)

2、信号量操作函数

同样和互斥锁和条件变量的使用相同,需要定义一个sem_t类型的变量,就可以用户信号量的操作函数

①初始化信号量

int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:默认给0即可,0表示线程间共享 ,非0表示进行间共享
value:信号量的初值

②销毁信号量

int sem_destroy(sem_t *sem);

③等待信号量 P( )

int sem_wait(sem_t *sem);

④发布信号量 V( )

int sem_post(sem_t *sem);

3、基于环形队列的生产者消费者模型

①认识环形队列

在这里插入图片描述

问题:环形队列什么时候为空?什么时候为满?

刚刚开始的时候为空,拿和放在同一个位置;之后满了,拿和放还是在同一个位置。
言下之意:有数据在队列中就是——放!=拿

我们判断队列为空还是为满有两种方法:

  • 1、计数器
  • 2、镂空一个位置:放前先做判断,当前位置+1 != 拿,可以放;否则不放

在这里插入图片描述
此处的环形结构采用数组通过模运算来模拟环形队列
在这里插入图片描述

②多线程下的环形队列

目标:实现一个基于环形队列的生产者消费者模型!在多线程的情况下,来进行环形队列的并发访问!

①基本原理

  • 生产者和消费者刚开始的时候,队列为空,指向同一个位置!——应该让生产者先访问临界资源

  • 生产者和消费者在队列为满时,也是指向同一个位置!——应该让消费者先访问临界资源

  • 言下之意:队列不为空,或者不为满的时候,生产者和消费者一定指向的不是同一个位置!!!!

结论
前两点说明,队列为空或者为满时,不能让生产者和消费者同时访问临界资源(满足互斥和局部同步特性)。
最后一点说明,生产和消费可以并发执行。

②基本实现的思想:

生产者最关心什么资源?——队列中的空位置也是资源!
消费者最关心什么资源?——队列中的数据就是资源!

  • 规则1:生产者不能把消费者围成一个圈(意思就是不能超过,超过就是覆盖)
  • 规则2:消费者不能超过生产者
  • 规则3:当指向同一个位置时,要根据空、满状态判断,谁先执行!
  • 其他规则:除此之外,生产和消费都可以并发执行!
③具体代码实现

基于信号量的循环队列的单生产和单消费的生产者消费者模型

// ring_queue.hpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <semaphore.h>

namespace ns_ring_queue
{
    const int g_cap_default = 10;
    template <class T>
    class RingQueue
    {
    private:
        std::vector<T> ring_queue_;
        int cap_;
        sem_t blank_sem_; // 生产者关心空格资源
        sem_t data_sem_; // 消费者关心数据资源
		
		// 表明生产、消费的位置
        int c_step_;
        int p_step_;

    public:
        RingQueue(int cap = g_cap_default)
            : ring_queue_(cap), cap_(cap)
        {
            sem_init(&blank_sem_, 0, cap);
            sem_init(&data_sem_, 0, 0);
            c_step_ = p_step_ = 0;
        }
        ~RingQueue()
        {
            sem_destroy(&blank_sem_);
            sem_destroy(&data_sem_);
        }

    public:
     // 目前高优先级的先实现单生产和单消费
        void Push(const T &in)
        {
            // 生产接口
            // 1.申请信号量
            sem_wait(&blank_sem_); // P(空位置)
            ring_queue_[p_step_] = in;
            sem_post(&data_sem_); // V(数据)

            p_step_++;
            p_step_ %= cap_;
        }

        void Pop(T *out)
        {
            // 消费接口
            sem_wait(&data_sem_);
            *out = ring_queue_[c_step_];
            sem_post(&blank_sem_);

            c_step_++;
            c_step_ %= cap_;
        }
    };
}
/
#include <iostream>
#include <pthread.h>
#include <time.h>
#include "ring_queue.hpp"
#include <unistd.h> // sleep()
using namespace ns_ring_queue;

void *consumer(void *args)
{
    RingQueue<int> *rq = (RingQueue<int> *)args;
    while (true)
    {
        int data = 0;
        rq->Pop(&data);
        std::cout << "消费了一个数据:" << data << std::endl;
        sleep(1); //让消费者慢一点
    }
}
void *producer(void *args)
{
    RingQueue<int> *rq = (RingQueue<int> *)args;
    while (true)
    {
        // 生产随机数
        int data = rand() % 20 + 1;
        std::cout << "生产了一个数据:" << data << std::endl;
        rq->Push(data);
    }
}
int main()
{
    srand((unsigned int)time(0));
    RingQueue<int> *rq = new RingQueue<int>();
    pthread_t c, p;
    pthread_create(&c, nullptr, consumer, (void *)rq);
    pthread_create(&p, nullptr, producer, (void *)rq);

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

    return 0;
}

结果展示:
在这里插入图片描述

生产和消费函数最大的亮点:申请自己关心的资源;释放对方关心的资源!
对于生产函数,他关心的是空格子,所以先要申请空格子资源,申请成功,代码顺序往后执行,空格上就放上了数据,释放时,虽然格子被占据了,但是数据却多了,所以要释放数据资源!!
同理消费函数亦如此。

④改进:改成多生产者、多消费者模型
// task.hpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
namespace ns_task
{
    class Task
    {
    private:
        int x_;
        int y_;
        char op_; // +-*/%
    public:
        Task() {}
        Task(int x, int y, char op) : x_(x), y_(y), op_(op)
        {
        }

        std::string Show()
        {
            std::string message = std::to_string(x_);
            message += op_;
            message += std::to_string(y_);
            message +="=?";

            return message;
        }
        int Run()
        {
            int res = 0;
            switch (op_)
            {

            case '+':
                res = x_ + y_;
                break;
            case '-':
                res = x_ - y_;
                break;
            case '*':
                res = x_ * y_;
                break;
            case '/':
                res = x_ / y_;
                break;
            case '%':
                res = x_ % y_;
                break;
            default:
                std::cout << "错误的运算" << std::endl;
                break;
            }
            std::cout << "当前任务正在被[" << pthread_self() << "]处理" << x_ << op_ << y_ << "=" << res << std::endl;
            std::cout << "-----------------------------" << std::endl;
            return res;
        }

        int operator()()
        {
            return Run();
        }
        ~Task() {}
    };
} 
/
// ring_queue.hpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <semaphore.h>
#include <pthread.h>
namespace ns_ring_queue
{
    const int g_cap_default = 10;
    template <class T>
    class RingQueue
    {
    private:
        std::vector<T> ring_queue_;
        int cap_;
        sem_t blank_sem_; // 生产者关心空格资源
        sem_t data_sem_;  // 消费者关心数据资源

        // 表明生产、消费的位置
        int c_step_;
        int p_step_;

        // 引入两把互斥锁,分别维护生产者内部和消费者内部的关系
        pthread_mutex_t c_mtx_;
        pthread_mutex_t p_mtx_;

    public:
        RingQueue(int cap = g_cap_default)
            : ring_queue_(cap), cap_(cap)
        {
            sem_init(&blank_sem_, 0, cap);
            sem_init(&data_sem_, 0, 0);
            c_step_ = p_step_ = 0;

            pthread_mutex_init(&c_mtx_, nullptr);
            pthread_mutex_init(&p_mtx_, nullptr);
        }
        ~RingQueue()
        {
            sem_destroy(&blank_sem_);
            sem_destroy(&data_sem_);

            pthread_mutex_destroy(&c_mtx_);
            pthread_mutex_destroy(&p_mtx_);
        }

    public:
        // 目前高优先级的先实现单生产和单消费
        void Push(const T &in)
        {
            // 生产接口
            // 申请信号量

            // 多生产和多消费的优势并不在这里,而是在于并发的获取和处理任务
            sem_wait(&blank_sem_); // P(空位置)

            pthread_mutex_lock(&p_mtx_);
            ring_queue_[p_step_] = in;
             // 它也变成了临界资源
            p_step_++;
            p_step_ %= cap_;
            pthread_mutex_unlock(&p_mtx_);

            sem_post(&data_sem_); // V(数据)
        }

        void Pop(T *out)
        {
            // 消费接口
            sem_wait(&data_sem_);

            pthread_mutex_lock(&p_mtx_);
            *out = ring_queue_[c_step_];
            c_step_++;
            c_step_ %= cap_;
            pthread_mutex_unlock(&p_mtx_);

            sem_post(&blank_sem_);
        }
    };
}

/**
 * @file ring_cp.cc
 * @author sjj
 * @brief 改进基于环形队列的生产者消费者模型--->多生产者多消费者模型
 * @version 0.2
 * @date 2022-09-11
 *
 * @copyright Copyright (c) 2022
 *
 */
#include <iostream>
#include <pthread.h>
#include <time.h>
#include "ring_queue.hpp"
#include <unistd.h> // sleep()
#include "task.hpp"
using namespace ns_ring_queue;
using namespace ns_task;
void *consumer(void *args)
{
    RingQueue<Task> *rq = (RingQueue<Task> *)args;
    while (true)
    {
        Task t;
        rq->Pop(&t);
        t();
        sleep(1); //让消费者慢一点
    }
}
void *producer(void *args)
{
    RingQueue<Task> *rq = (RingQueue<Task> *)args;
    const std::string ops="+-*/%";
    while (true)
    {
        // 生产随机数
        int x = rand() % 20 + 1;
        int y=rand()%10+1;
        char  op=ops[rand()%5];
        Task t(x,y,op);

        std::cout << "生产了一个任务:" << t.Show() << " 我是[" << pthread_self() << "]" << std::endl;
        rq->Push(t);
    }
}
int main()
{
    srand((unsigned int)time(0));
    RingQueue<Task> *rq = new RingQueue<Task>();
    pthread_t c0, c1, c2, c3, p0, p1, p2;
    pthread_create(&c0, nullptr, consumer, (void *)rq);
    pthread_create(&c1, nullptr, consumer, (void *)rq);
    pthread_create(&c2, nullptr, consumer, (void *)rq);
    pthread_create(&c3, nullptr, consumer, (void *)rq);
    pthread_create(&p0, nullptr, producer, (void *)rq);
    pthread_create(&p1, nullptr, producer, (void *)rq);
    pthread_create(&p2, nullptr, producer, (void *)rq);

    pthread_join(c0, nullptr);
    pthread_join(c1, nullptr);
    pthread_join(c2, nullptr);
    pthread_join(c3, nullptr);
    pthread_join(p0, nullptr);
    pthread_join(p1, nullptr);
    pthread_join(p2, nullptr);

    return 0;
}

结果展示:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/621816.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

时间序列学习 经典案例(5)【stability_selection】股票数据特征分析与特征选择

0.环境配置 本文用到的环境是&#xff1a; jupyter 略python 3.9stability_selection 略&#xff0c;见下文。scikit-learn 1.2.2seaborn 0.12.2pandas 1.3.5numpy 1.22.3matplotlib 3.6.3tushare 1.2.89baostock 00.8.80yfinance 0.2.18 1.stability_selection模块安装【问…

【MySQL数据库 | 番外篇】 聚合函数

前言&#xff1a; 聚合函数是分组查询中一个重要的组成部分&#xff0c;想要利用分组查询&#xff0c;就要对聚合函数有不错的掌握&#xff0c;因此我们在这里开一篇番外&#xff0c;讲解SQL语法中的聚合函数 聚合函数&#xff1a; 聚合函数是SQL中一种特殊的函数&#xff0c;…

大模型信息提取、文本生成、视觉语音应用

448页新书《基础模型自然语言处理》&#xff0c;详述大模型在信息提取文本生成视觉语音应用。 Dr. Gerhard Paa 等人合著的《Foundation Models for Natural Language Processing》 一书系统介绍基础模型研究和应用的全面概述&#xff0c;而且是目前对此方面研究最新的综述。 …

【Java】Java核心要点总结:59

文章目录 1. 线程的run()和start()有什么区别&#xff0c;为什么不直接调用run()2. synchronized是什么&#xff0c;以及原理3. Java中如何实现多线程的通讯和协作4. Volatile有什么特点&#xff0c;为什么能够保证变量的可见性5. 为什么说synchronized是一个悲观锁&#xff0c…

chatgpt赋能python:Python开发环境的下载方法

Python开发环境的下载方法 Python是一种高级的编程语言&#xff0c;受到广泛的社区和商业支持。它用于数据分析、人工智能和Web开发等领域&#xff0c;成为业界最流行的编程语言之一。搭建Python开发环境需要安装解释器、编辑器、包管理器和库&#xff0c;下面介绍Python开发环…

chatgpt赋能python:用Python开发在线电影播放网站如何进行SEO

用Python开发在线电影播放网站如何进行SEO 随着人们对于电影、电视剧等视频娱乐的需求日益增长&#xff0c;越来越多的在线电影播放网站涌现出来。作为开发者&#xff0c;如何通过搜索引擎优化&#xff08;SEO&#xff09;来使你的在线电影播放网站具有更好的可见度&#xff0…

chatgpt赋能python:如何更新Python库?

如何更新Python库&#xff1f; Python语言已经成为现代编程语言中最受欢迎的一种&#xff0c;它的成功归功于它的灵活性、简洁性和可读性。Python库是它成功的关键&#xff0c;这些库是程序员的基本工具箱&#xff0c;可以更快地编写、测试和部署程序。 然而&#xff0c;Pyth…

多分类问题

目录 多分类问题介绍1 多分类1.1 数据集1.2 数据可视化1.3 逻辑回归的向量化1.3.1 代价函数的向量化1.3.2 梯度的向量化1.3.3 正则化逻辑回归的向量化 1.4 多分类-分类器 1.5 使用分类器进行预测 多分类问题 介绍 在本练习中&#xff0c;我们将使用逻辑回归来识别手写数字&…

python安装使用Flask框架(Vscode)

编译器&#xff1a;VsCode&#xff0c;python3.** 首先安装&#xff0c;在终端输入 pip install flask安装成功后新建文件&#xff0c;app.python&#xff0c;创建一个简单的Web应用。 from flask import Flaskapp Flask(__name__)app.route(/) def hello():return Hello, …

chatgpt赋能python:Python平均值命令在数据处理中的应用

Python平均值命令在数据处理中的应用 Python是一门广泛应用于数据分析和数据处理的编程语言。在数据处理中&#xff0c;我们常常需要计算数据的平均值。Python中提供了多种方法来计算平均值&#xff0c;比如使用内置函数mean()或使用numpy库中的mean()函数等。本文将着重介绍P…

Vue封装API,详细解释。

1、为什么我们要封装API ps: 如果已经有了明确要封装API的需求&#xff0c;直接看第二步。 在没有封装API之前&#xff0c;我们是类似这样使用 axios 的 this.$axios.post(blogArticle/frontList,parms).then((resp) > { this.blogList resp.data, this.blogTota…

【玩转Linux操作】Linux常用文件管理命令

&#x1f38a;专栏【玩转Linux操作】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【Counting Stars 】 欢迎并且感谢大家指出小吉的问题&#x1f970; 目录 ​编辑 &#x1f381;ctrlc &#x1f381;ctrlu &#x1f381;t…

chatgpt赋能python:Python平均分函数:简介和使用方法

Python平均分函数&#xff1a;简介和使用方法 如果您正在寻找如何计算Python中多个数字值的平均分数的方法&#xff0c;请继续阅读。本文将为您介绍Python中平均分函数的用途和使用方法。 什么是平均分函数&#xff1f; Python的平均分函数是一个计算多个数字值的平均值的函…

分享可以在线录音实时转写的方法

小伙伴们使用过录音记录吗&#xff1f;那知道录音实时转写吗&#xff1f;有没有听说过这个功能呢&#xff1f;它是可以通过语音识别技术&#xff0c;将录音中的信息快速转换为文本&#xff0c;并实现实时显示输出的功能。听起来是不是很有趣&#xff1f;而且它无需任何专业设备…

线程,你是个什么?

线程的基本定义 线程&#xff08;Thread&#xff09;是操作系统能够进行运算调度的最小单位&#xff0c;它被包含在进程&#xff08;Process&#xff09;中&#xff0c;是进程中的实际运作单位。一个线程可以与同一进程中的其他线程共享进程的全部资源&#xff0c;包括内存、文…

深入浅出讲解闭包及其原理

闭包 什么是闭包&#xff1f; 闭包的概念并不复杂&#xff0c;但是它的定义比较绕&#xff08;就像平时经常用到它&#xff0c;却又说不出来是什么&#xff09;。可以在一个作用域中调用函数的内部函数并访问到该函数中的作用域的成员&#xff0c;这就是闭包。给一个建议&…

springboot整合swagger3

目录 一、导入swagger3的依赖二、SwaggerConfig代码的解读三、整体代码四、访问swagger3 一、导入swagger3的依赖 <dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</versi…

【算法与数据结构】209.长度最小的子数组

文章目录 题目一、暴力穷解法二、滑动窗口法完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 题目 一、暴力穷解法 思路分析&#xff1a;这道题涉及到数组求和&#xff0c;那么我们很容易想到利用两个for循环来写&#xff0c;…

【科技素养题】少儿编程 蓝桥杯青少组科技素养题真题及解析第20套

少儿编程 蓝桥杯青少组科技素养题真题及解析第20套 1、“唐纳德特朗普 (Donald Trump) 曾经是美国总统”是一个 (),“特朗普关于新冠肺炎疫情的不实言论”是一个 ()。 A、事实;事实 B、观点;事实 C、观点;观点 D、事实;观点 答案:D 考点分析:主要考查小朋友们对时事的…

ChatGPT的未来发展

文章目录 1.什么是ChatGPT2.ChatGPT的基础技术3.ChatGPT工作原理4.ChatGPT应用场景5.ChatGPT局限性6.ChatGPT的未来发展 ✍创作者&#xff1a;全栈弄潮儿 &#x1f3e1; 个人主页&#xff1a; 全栈弄潮儿的个人主页 &#x1f3d9;️ 个人社区&#xff0c;欢迎你的加入&#xff…