【Linux】生产者消费者模型——阻塞队列BlockQueue

> 作者:დ旧言~
> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:理解【Linux】生产者消费者模型——阻塞队列BlockQueue。

> 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安!

> 专栏选自:Linux初阶

> 望小伙伴们点赞👍收藏✨加关注哟💕💕

​​

🌟前言

Linux有两个重要的模型,一个是生产者消费者模型——阻塞队列BlockQueue,另一个则是生产者消费者模型——环形队列RingQueue。今天我们学习其中一个模型:【Linux】生产者消费者模型——阻塞队列BlockQueue。

⭐主体

学习【Linux】生产者消费者模型——阻塞队列BlockQueue咱们按照下面的图解:

​🌙 生产者消费者模型


💫 生产者消费者模型的概念

概念:

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。

生产者和消费者彼此之间不进行直接通讯,而通过这个容器来通讯,所以生产者生产完数据之后不用等待消费者处理,直接将生产的数据放到这个容器中,消费者也不用找生产者要数据,而是直接从容器也就是阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

  • 这个阻塞队列就是用来给生产者和消费者解耦的
  • 如果缓冲区已经满了,则生产者线程阻塞;
  • 如果缓冲区为空,那么消费者线程阻塞。

图解:

💫 生产者消费者模型的特点

生产者消费者是多线程同步与互斥的一个经典场景,其特点如下:

  • 三种关系:生产者和生产者(互斥关系),生产者和消费者(互斥关系),生产者和消费者(互斥关系,同步关系)
  • 两种角色:生产者和消费者(通常由进程或线程承担)
  • 一个交易场所:通常指的是内存中的一段缓冲区。

生产者和生产者,消费者和消费者,生产者和消费者,它们之间为什么会存在互斥关系?

  • 介于生产者和消费者之间的容器可能会被多个执行流同时访问,因此我们需要将该临界资源用互斥锁保护起来。
  • 其中,所有的生产者和消费者都会竞争式的申请锁,因此生产者和生产者,消费者和消费者,生产者和消费者之间都存在互斥关系。

生产者和消费者之间为什么会存在同步关系?

  •  如果让生产者一直生产,那么当生产者生产的数据将容器塞满后,生产者再生产数据就会生产失败。
  • 反之,让消费者一直消费,那么当容器当中的数据被消费完后,消费者再进行消费就会消费失败。
  • 虽然这样不会造成任何数据不一致的问题,但是这样会引起另一方的饥饿问题,是非常低效的。我们应该让生产者和消费者访问该容器时具有一定的顺序性,比如让生产者先生产,然后再让消费者进行消。

注意:

互斥关系保证的是数据的正确性,而同步关系是为了让多线程之间协同起来。 

💫 生产者消费者模型优点

  • 解耦:假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。
  • 支持并发:生产者直接调用消费者的某个方法,还有另一个弊端,由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只好一直在等待。玩意消费者处理数据很慢,生产者就会白白浪费时间,使用了生产者/消费者模型后,生产者和消费者可以是两个独立的开发主体(常见并发类型有进程和线程两种)。生产者把制造出来的数据往缓冲区一丢,就可以再去生产下一个数据,基本上不用依赖消费者的处理速度。
  • 支持忙闲不均:缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造块的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造商速度慢下来,消费者再慢慢处理。

​🌙 基于Blockqueue的生产和消费模型

阻塞队列:阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。

  • 阻塞队列为空时,从阻塞队列中获取元素的线程将被阻塞,直到阻塞队列被放入元素。
  • 阻塞队列已满时,往阻塞队列放入元素的线程将被阻塞,直到有元素被取出。

图解:

💫 单生产单消费计算


1.随机数

下面以单生产单消费为例子:

//BlockQueue.hpp
#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
const int gmaxcap =5;
template <class T>
class BlockQueue
{
public:
    BlockQueue(const int&maxcap = gmaxcap):_maxcap(maxcap)
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_pcond,nullptr);
        pthread_cond_init(&_ccond,nullptr);
    }
    void push(const T& in)
    {
        pthread_mutex_lock(&_mutex);
        while(is_full())
        {
            pthread_cond_wait(&_pcond,&_mutex);//因为生产条件不满足无法生产,此时我们的生产者进行等待
        }
        _q.push(in);
        //pthread_cond_signal:这个函数可以放在临界区内部,也可以放在外部
        pthread_cond_signal(&_ccond);
        pthread_mutex_unlock(&_mutex);
        //pthread_cond_signal(&_ccond);
    }
    void pop(T*out)//输出型参数,*,输入输出型:&
    {
        pthread_mutex_lock(&_mutex);
        while(is_empty())
        {
            pthread_cond_wait(&_ccond,&_mutex);
        }
        *out = _q.front();
        _q.pop();
        pthread_cond_signal(&_pcond);
        pthread_mutex_unlock(&_mutex);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_pcond);
        pthread_cond_destroy(&_ccond);
    }
private:
    bool is_empty(){return _q.empty();}
    bool is_full(){return _q.size()==_maxcap;}
private:
    std::queue<T> _q;
    int _maxcap;//队列上限
    pthread_mutex_t _mutex;
    pthread_cond_t _pcond;//生产者条件变量
    pthread_cond_t _ccond;//消费者条件变量
};

//mainCp.cc
void* consumer(void * bq_)
{
    BlockQueue<int>* bq = static_cast<BlockQueue<int>*>(bq_);
    while(true)
    {
        int data;
        bq->pop(&data);
        std::cout<<"消费数据: "<<data<<std::endl;
        sleep(1);
    }
    return nullptr;
}
void*productor(void*bq_)
{
    BlockQueue<int>*bq = static_cast<BlockQueue<int>*>(bq_);
    while(true)
    {
        int data = rand()%10+1;
        bq->push(data);
        std::cout<<"生产数据: "<<data<<std::endl;
    }
    return nullptr;
}
int main()
{
    srand((unsigned long)time(nullptr)^getpid());
    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;
}

总结分析:

pthread_cond_wait函数的第二个参数必须是我们正在使用的互斥锁,满了就会进行等待,如果像之前一样把锁拿走,那么其他线程就无法访问共享资源。

  • a.pthread_cond_wait:该函数调用的时候,会以原子性的方式,将锁释放,并将自己挂起
  • b.pthread_cond_wait:该函数在被唤醒返回的时候,会自动的重新获取你传入的锁

pthread_cond_signal伪唤醒:判断的问题:假设生产者有10个,消费者只有一个,消费一下数据,如果是pthread_cond_broadcast是把10个线程同时唤醒,可是只需要生产一个数据,而同时把10个线程唤醒而如果是if判断的时候push就会出问题了。

如果生产者生产慢,消费者消费快生产一个消费一个,而且消费的都是最新的数据。

如果生产者生产快,消费者消费慢稳定后,消费一个生产一个。

2.计算器任务Task

Task.hpp:包含func_t的回调函数,这个函数就是进行数据计算的回调函数:

#pragma once
#include <iostream>
#include <functional>
#include <cstdio>
class Task
{
    using func_t = std::function<int(int,int,char)>;
public:
    Task()
    {}
    Task(int x,int y,char op,func_t func)
    :_x(x),_y(y),_op(op),_callback(func)
    {}
    std::string operator()()
    {
        int result = _callback(_x,_y,_op);
        char buffer[1024];
        snprintf(buffer,sizeof buffer,"%d %c %d = %d",_x,_op,_y,result);
        return buffer;
    }
    std::string toTaskString()
    {
        char buffer[1024];
        snprintf(buffer,sizeof buffer,"%d %c %d = ?",_x,_op,_y);
        return buffer;
    }
private:
    int _x;
    int _y;
    char _op;
    func_t _callback;
};

BlockQueue.hpp:

#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
const int gmaxcap =500;
template <class T>
class BlockQueue
{
public:
    BlockQueue(const int&maxcap = gmaxcap):_maxcap(maxcap)
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_pcond,nullptr);
        pthread_cond_init(&_ccond,nullptr);
    }

    void push(const T& in)
    {
        pthread_mutex_lock(&_mutex);
        while(is_full())
        {
            pthread_cond_wait(&_pcond,&_mutex);
        }
        _q.push(in);
        pthread_cond_signal(&_ccond);
        pthread_mutex_unlock(&_mutex);
    }

    void pop(T*out)
    {
        pthread_mutex_lock(&_mutex);
        if(is_empty())
        {
            pthread_cond_wait(&_ccond,&_mutex);
        }
        *out = _q.front();
        _q.pop();
        pthread_cond_signal(&_pcond);
        pthread_mutex_unlock(&_mutex);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_pcond);
        pthread_cond_destroy(&_ccond);
    }
private:
    bool is_empty(){return _q.empty();}
    bool is_full(){return _q.size()==_maxcap;}
private:
    std::queue<T> _q;
    int _maxcap;
    pthread_mutex_t _mutex;
    pthread_cond_t _pcond;
    pthread_cond_t _ccond;
};

Main.cc:

#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include "BlockQueue.hpp"
#include "Task.hpp"
const std::string oper = "+-*/%"; 
int mymath(int x,int y,char op)
{
    int result = 0;
    switch (op)
    {
    case '+':
        result = x + y;
        break;
    case '-':
        result = x - y;
        break;
    case '*':
        result = x * y;
        break;
    case '/':
    {
        if (y == 0)
        {
            std::cerr << "div zero error!" << std::endl;
            result = -1;
        }
        else
            result = x / y;
    }
        break;
    case '%':
    {
        if (y == 0)
        {
            std::cerr << "mod zero error!" << std::endl;
            result = -1;
        }
        else
            result = x % y;
    }
        break;
    default:
        break;
    }
    return result;
}
void* consumer(void * bq_)
{
    BlockQueue<Task>* bq = static_cast<BlockQueue<Task>*>(bq_);
    while(true)
    {
        Task t;
        bq->pop(&t);
        std::cout<<"消费任务: "<<t()<<std::endl;
    }
    return nullptr;
}
void*productor(void*bq_)
{
    BlockQueue<Task>*bq = static_cast<BlockQueue<Task>*>(bq_);
    while(true)
    {
        int x = rand()%100+1;
        int y = rand()%10;
        int operCode = rand()%oper.size();
        Task t(x,y,oper[operCode],mymath);
        bq->push(t);
        std::cout<<"生产任务: "<<t.toTaskString()<<std::endl;  
        sleep(1);
    }
    return nullptr;
}
int main()
{
    srand((unsigned long)time(nullptr)^getpid());
    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);
    delete bq;
    return 0;
}

3.存储任务

定义结构体BlockQueues封装计算任务的阻塞队列和存储任务的阻塞队列,创建生产者线程,消费者线程,存储线程执行各自的方法:

blockqueue.hpp:

#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
const int gmaxcap = 500;
template <class T>
class BlockQueue
{   
public:
    BlockQueue(const int &maxcap = gmaxcap):_maxcap(maxcap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_pcond, nullptr);
        pthread_cond_init(&_ccond, nullptr);
    }
    void push(const T &in)
    {
        pthread_mutex_lock(&_mutex);
        while(is_full())
        {
            pthread_cond_wait(&_pcond, &_mutex); 
        }
        _q.push(in);
        pthread_cond_signal(&_ccond);
        pthread_mutex_unlock(&_mutex);
    }
    void pop(T *out)
    {
        pthread_mutex_lock(&_mutex);
        while(is_empty())
        {
            pthread_cond_wait(&_ccond, &_mutex);
        }
        *out = _q.front();
        _q.pop();
        pthread_cond_signal(&_pcond); 
        pthread_mutex_unlock(&_mutex);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_pcond);
        pthread_cond_destroy(&_ccond);
    }
private:
    bool is_empty()
    {
        return _q.empty();
    }
    bool is_full()
    {
        return _q.size() == _maxcap;
    }
private:
    std::queue<T> _q;
    int _maxcap;
    pthread_mutex_t _mutex;
    pthread_cond_t _pcond; 
    pthread_cond_t _ccond;
};

Task.hpp:

#pragma once

#include <iostream>
#include <string>
#include <cstdio>
#include <functional>

class CalTask
{
    using func_t = std::function<int(int,int,char)>;
    // typedef std::function<int(int,int)> func_t;
public:
    CalTask()
    {}
    CalTask(int x, int y, char op, func_t func)
    :_x(x), _y(y), _op(op), _callback(func)
    {}
    std::string operator()()
    {
        int result = _callback(_x, _y, _op);
        char buffer[1024];
        snprintf(buffer, sizeof buffer, "%d %c %d = %d", _x, _op, _y, result);
        return buffer;
    }
    std::string toTaskString()
    {
        char buffer[1024];
        snprintf(buffer, sizeof buffer, "%d %c %d = ?", _x, _op, _y);
        return buffer;
    }
private:
    int _x;
    int _y;
    char _op;
    func_t _callback;
};

const std::string oper = "+-*/%";

int mymath(int x, int y, char op)
{
    int result = 0;
    switch (op)
    {
    case '+':
        result = x + y;
        break;
    case '-':
        result = x - y;
        break;
    case '*':
        result = x * y;
        break;
    case '/':
    {
        if (y == 0)
        {
            std::cerr << "div zero error!" << std::endl;
            result = -1;
        }
        else
            result = x / y;
    }
        break;
    case '%':
    {
        if (y == 0)
        {
            std::cerr << "mod zero error!" << std::endl;
            result = -1;
        }
        else
            result = x % y;
    }
        break;
    default:
        // do nothing
        break;
    }

    return result;
}

class SaveTask
{
    typedef std::function<void(const std::string&)> func_t;
public:
    SaveTask()
    {}
    SaveTask(const std::string &message, func_t func)
    : _message(message), _func(func)
    {}
    void operator()()
    {
        _func(_message);
    }
private:
    std::string _message;
    func_t _func;
};

void Save(const std::string &message)
{
    const std::string target = "./log.txt";
    FILE *fp = fopen(target.c_str(), "a+");
    if(!fp)
    {
        std::cerr << "fopen error" << std::endl;
        return;
    }
    fputs(message.c_str(), fp);
    fputs("\n", fp);
    fclose(fp);
}

MianCp.cc:

#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <ctime>
#include "blockqueue.hpp"
#include "Task.hpp"
template<class C,class K>
struct BlockQueues
{
    BlockQueue<C> *c_bq;
    BlockQueue<K> *s_bq;
};
void* productor(void*args)
{
    BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(args))->c_bq;
    while(true)
    {
        int x = rand()%10+1;
        int y = rand()%5+1;
        int operCode = rand()%oper.size();
        CalTask t(x,y,oper[operCode],mymath);
        bq->push(t);
        std::cout<<"productor thread,生产计算任务: "<<t.toTaskString()<<std::endl;
        sleep(1);
    }
    return nullptr;
}
void* consumer(void*args)
{
    BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(args))->c_bq;
    BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(args))->s_bq;
    while(true)
    {
        CalTask t;
        bq->pop(&t);
        std::string result = t();
        std::cout<<"cal thread,完成计算任务: "<<result<<" ... done"<<std::endl;

        SaveTask save(result,Save);
        save_bq->push(save);
        std::cout<<"cal thread,推送存储任务完成..."<<std::endl;
    }
    return nullptr;
}
void*saver(void*args)
{
    BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(args))->s_bq;
    while(true)
    {
        SaveTask t;
        save_bq->pop(&t);
        t();
        std::cout<<"save thread,保存任务完成..."<<std::endl;
    }
    return nullptr;
}
int main()
{
    srand((unsigned int)time(nullptr)^getpid());
    BlockQueues<CalTask,SaveTask> bqs;
    bqs.c_bq = new BlockQueue<CalTask>();
    bqs.s_bq = new BlockQueue<SaveTask>();
    pthread_t c,p,s;
    pthread_create(&p,nullptr,productor,&bqs);
    pthread_create(&c,nullptr,consumer,&bqs);
    pthread_create(&s,nullptr,saver,&bqs);
    pthread_join(c,nullptr);
    pthread_join(p,nullptr);
    pthread_join(s,nullptr);
    delete bqs.c_bq;
    delete bqs.s_bq;
    return 0;
}

💫 多生产多消费

只需要稍微改一改MainCp.cc即可完成多生产多消费,其他文件代码不需要更改

MainCp.cc:

#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include "blockqueue.hpp"
#include "Task.hpp"
template<class C,class S>
class BlockQueues
{
public:
    BlockQueue<C>*c_bq;
    BlockQueue<S>*s_bq;
}; 
void*productor(void*bqs_)
{
    BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(bqs_))->c_bq;
    while(true)
    {
        int x = rand()%100+1;
        int y = rand()%10;
        int operCode = rand()%oper.size();
        CalTask t(x,y,oper[operCode],mymath);
        bq->push(t);
        std::cout<<"productor thread,生产计算任务: "<<t.toTaskString()<<std::endl;
        sleep(1);
    }
    return nullptr;
}
void* consumer(void * bqs_)
{
    BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(bqs_))->c_bq;
    BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(bqs_))->s_bq;

    while(true)
    {
        CalTask t;
        bq->pop(&t);
        std::string result = t();
        std::cout<<"cal thread,完成计算任务: "<<result<<" ... done"<<std::endl;

        // SaveTask save(result,Save);
        // save_bq->push(save);
        // std::cout<<"cal thread,推送存储任务完成..."<<std::endl;
        // sleep(1);
    }
    return nullptr;
}
void* saver(void* bqs_)
{
    BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(bqs_))->s_bq;
    while(true)
    {
        SaveTask t;
        save_bq->pop(&t);
        t();
        std::cout<<"save thread,保存任务完成..."<<std::endl;
    }
    return nullptr;
};
int main()
{
    srand((unsigned long)time(nullptr)^getpid());
    BlockQueues<CalTask,SaveTask> bqs;
    bqs.c_bq = new  BlockQueue<CalTask>();
    bqs.s_bq = new  BlockQueue<SaveTask>();
    pthread_t c[2],p[3];
    pthread_create(p,nullptr,productor,&bqs);
    pthread_create(p+1,nullptr,productor,&bqs);
    pthread_create(p+2,nullptr,productor,&bqs);
    pthread_create(c,nullptr,consumer,&bqs);
    pthread_create(c+1,nullptr,consumer,&bqs);
  
    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);

    delete bqs.c_bq;
    delete bqs.s_bq;
    return 0;
}

​🌙 总结

生产者消费模型高效在哪里?

高效体现在一个线程拿出来任务可能正在做计算,它在做计算的同时,其他线程可以继续从队列中拿,继续做运算,高效并不是体现在从队列中拿数据高效!而是我们可以让一个、多个线程并发的同时计算多个任务!在计算多个任务的同时,并不影响其他线程,继续从队列里拿任务的过程。也就是说,生产者消费者模型的高效:可以在生产之前与消费之后让线程并行执行,不要认为生产者消费模式仅仅只是把任务生产到队列的过程就是生产过程,生产过程:1.拿任务、需要费点劲2.拿到后再放到队列里面整个一体,整个生产的过程;整个消费的过程:不是把任务拿到线程的上下文中就完了,拿到之后还要进行计算或存储这些工作才是消费的过程在生产前和和消费后我们多个线程是可以并发的。

🌟结束语 

       今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

​​​ 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Git】Windows下使用可视化工具Sourcetree

参考&#xff1a;[最全面] SourceTree使用教程详解(连接远程仓库&#xff0c;克隆&#xff0c;拉取&#xff0c;提交&#xff0c;推送&#xff0c;新建/切换/合并分支&#xff0c;冲突解决&#xff0c;提交PR) 1.Git工具–sourcetree 之前文章介绍过Linux系统中的Git工具&…

C++ 11 【可变参数模板】【lambda】

&#x1f493;博主CSDN主页:麻辣韭菜&#x1f493;   ⏩专栏分类&#xff1a;C修炼之路⏪   &#x1f69a;代码仓库:C高阶&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多C知识   &#x1f51d;&#x1f51d; 目录 前言 一、新的类功能 1.1默认成员函数—…

78%的中小企业担心网络攻击会导致其业务中断,中小企业如何确保网络安全?

在当今数字化时代&#xff0c;网络攻击手段层出不穷&#xff0c;网络安全事件不断增加&#xff0c;根据ConnectWise的一项调查数据显示&#xff0c;94%的中小企业至少经历过一次网络攻击&#xff0c;78%的中小企业担心网络攻击会导致其业务中断&#xff0c;企业声誉受损。由此&…

opencv-python(七)

import cv2img cv2.imread(view.jpg) cv2.imshow(view.jpg, img) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # BGR转RGB cv2.imshow(RGB Color Space, img_rgb) img_bgr cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR) # RGB转BGR cv2.imshow(BGR Color Space, img_bgr) c…

javaspringbootmysql小程序的竞赛管理系统71209-计算机毕业设计项目选题推荐(附源码)

摘 要 随着社会的发展,社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景, 运用软件工程原理和开发方法,它主要是采用java语言技术和mysql数库来完成对系统的设计。整个开发过程首先对竞赛管理系统进行需求分…

【网络编程开发】11.IO模型 12.IO多路复用

11.IO模型 什么是IO: IO 是 Input/Output 的缩写&#xff0c;指的是输入和输出。在计算机当中&#xff0c;IO 操作通常指将数据从一个设备或文件中读取到计算机内存中&#xff0c;或将内存中的数据写入设备或文件中。这些设备可以包括硬盘驱动器、网卡、键盘、屏幕等。 通常用…

FiRa标准UWB MAC实现(三)——距离如何获得?

继续前期FiRa MAC相关介绍,将FiRa UWB MAC层相关细节进一步进行剖析,介绍了UWB技术中最重要的一个点,高精度的距离是怎么获得的,具体使用的测距方法都有哪些,原理又是什么。为后续FiRa UWB MAC的实现进行铺垫。 3、测距方法 3.1 SS-TWR SS-TWR为Single-Sided Two-Way Ra…

01 Linux网络设置

目录 1.1 查看及测试网络 1.1.1 查看网络配置 1. 查看网络接口地址 1. 查看活动的网络接口设备 2. 查看指定的网络接口信息 2. 查看主机名称 3. 查看路由表条目 4. 查看网络连接情况 1.1.2 测试网络连接 1. 测试网络连通性 2. 跟踪数据包的路由途径 3. 测试DNS域名解析 1.2 设…

[leetcode]将二叉搜索树转化为排序的双向链表

. - 力扣&#xff08;LeetCode&#xff09; /* // Definition for a Node. class Node { public:int val;Node* left;Node* right;Node() {}Node(int _val) {val _val;left NULL;right NULL;}Node(int _val, Node* _left, Node* _right) {val _val;left _left;right _rig…

小型气象站:现代气象监测的便携化解决方案

TH-QC12在气象监测领域&#xff0c;技术的不断创新和进步推动了监测设备的多样化和便携化。小型气象站作为这一趋势下的产物&#xff0c;以其体积小、功能全、操作简便等特点&#xff0c;受到了广泛的关注和应用。 小型气象站的技术特点 小型气象站集成了多种气象传感器&…

ChatGPT做2024高考数学题能得多少分?

文章目录 1. 前言2. AI答题2.1 第一部分试题图2.2 第二部分试题图2.3 第三部分试题图 3. 结论 1. 前言 2024年高考基本上落下帷幕&#xff08;特殊省份除外&#xff09;&#xff0c;我们关注下曾经“最喜欢”的数学用 AI 能得多少分&#xff1f;当前采用 ChatGPT-4o 模型&…

说说医院网间文件传输的进化之路

作为一家大型综合医院的信息科管理人员&#xff0c;我亲眼见证了跨网间文件交换技术的不断进步。每一个阶段都伴随着不同的挑战和惊喜&#xff0c;以下是我的一些经历和感悟&#xff0c;希望能对同行们有所帮助。 在2000年代初期&#xff0c;我们医院采用物理隔离和手动传输的方…

固态硬盘格式化后能恢复数据吗?四种数据恢复方法,赶紧收藏!

格式化的固态硬盘能恢复吗&#xff1f;硬盘是电脑里面的储存配件&#xff0c;虽然说当前种类比较多&#xff0c;但是在平常使用的过程中&#xff0c;也会遇到一些数据丢失的问题。而当前造成数据丢失的很多&#xff0c;例如&#xff1a;手动误删、清空回收站、磁盘格式化等。 …

【ppyoloe+】19届智能车完全模型组非官方基线

基于十九届智能车百度完全模型组线上赛baseline修改 调整参数最高能到0.989吧 一、环境准备 1.安装PaddleDetection In [1] # 解压PaddleDetection压缩包 %cd /home/aistudio/data/data267567 !unzip -q PaddleDetection-release-2.6.zip -d /home/aistudio /home/aistud…

工业无线通信解决方案,企业在进行智能化升级改造

某大型制造企业在进行智能化升级改造,需要将分布在各个车间的数控机床、自动化生产线、AGV小车等设备连接到云端,实现设备的远程监控、数据采集分析等功能。之前工厂内部是用工业以太网连接,存在布线难、成本高、灵活性差等问题。 在了解客户需求后,我司星创易联的工程师建议客…

等保一体机在中小型企业的应用

等保一体机比较适合中小型企业。大型企业、重点行业过等保现象确实已经十分普遍。因为国家推行等级保护制度&#xff0c;面向的重点就是那些信息系统十分关键、数据价值高的领域&#xff0c;如政府机关、金融、医疗、教育等行业。 但是有一些用户需要满足等保合规要求&#xff…

[图解]企业应用架构模式2024新译本讲解13-服务层1

1 00:00:00,150 --> 00:00:05,030 接下来我们来看服务层模式 2 00:00:06,070 --> 00:00:11,740 这也是领域逻辑类型的最后一个模式 3 00:00:13,700 --> 00:00:17,110 我们前面实际上已经见过了&#xff0c;事务脚本 4 00:00:17,240 --> 00:00:19,900 的时候&…

数据结构基础(基于c++)

数据结构基础&#xff08;基于c&#xff09; 文章目录 数据结构基础&#xff08;基于c&#xff09;前言1. 递归、迭代、时间复杂度、空间复杂度2. 数据结构 数组与链表1. 数组2. 链表3. 动态数组4. 数组与链表对比 前言 参考资料&#xff1a;Hello 算法 (hello-algo.com) 1. 递…

HTML静态网页成品作业(HTML+CSS)—— 小米商城首页网页(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有1个页面。 二、作品演示 三、代…