Linux多线程系列三: 生产者消费者模型,信号量使用,基于阻塞队列和环形队列的这两种生产者消费者代码的实现
Linux多线程系列三: 生产者消费者模型,信号量,基于阻塞队列和环形队列的这两种生产者消费者代码的实现
- 一.生产者消费者模型的理论
- 1.现实生活中的生产者消费者模型
- 2.多线程当中的生产者消费者模型
- 3.理论
- 二.基于阻塞队列的生产者消费者模型的基础代码
- 1.阻塞队列的介绍
- 2.大致框架
- 1.BlockQueue.hpp
- 2.cp_based_block_queue.cpp
- 3.LockGuard.hpp
- 3.BlockQueue.hpp的编写
- 4.测试代码的编写
- 5.演示
- 三.基于阻塞队列的生产者消费者模型的扩展代码
- 1.传递任务版本
- 1.Task.hpp
- 2.测试代码
- 3.演示
- 2.多生产者多消费者版本
- 1.改测试代码
- 四.生产者消费者模型的再一次理解与阻塞队列版本的优劣
- 1.多执行流解耦
- 2.提高效率
- 3.小结一下
- 五.信号量的深入理解与使用
- 1.理论
- 2.接口介绍
- 六.基于环形队列的单生产者单消费者模型
- 1.思路
- 2.基础代码
- 1.RingQueue.hpp
- 1.结构
- 2.代码
- 3.细节
- 3.扩展代码
- 七.基于环形队列的多生产者多消费者模型
- 1.先申请信号量,后申请锁的原因
- 2.RingQueue代码
- 3.测试
- 八.基于环形队列的生产者消费者模型与基于阻塞队列的生产者消费者模型的对比
学习了同步与互斥之后,我们来学一下应用同步与互斥的一个非常重要的模型:生产者消费者模型
一.生产者消费者模型的理论
1.现实生活中的生产者消费者模型
我们理解一下它们之间的关系
2.多线程当中的生产者消费者模型
3.理论
大家目前就先记住三种关系即可,知道目的和如何实现
然后我们直接开始写代码,写完扩展代码之后在解释原因
二.基于阻塞队列的生产者消费者模型的基础代码
1.阻塞队列的介绍
2.大致框架
1.BlockQueue.hpp
不要忘了条件变量是🔔哦
2.cp_based_block_queue.cpp
c:consumer
p:productor
基于阻塞队列的cp模型
3.LockGuard.hpp
#pragma once
//构造: 申请锁
//析构: 释放锁
class LockGuard
{
public:LockGuard(pthread_mutex_t* lock):pmutex(lock){pthread_mutex_lock(pmutex);}~LockGuard(){pthread_mutex_unlock(pmutex);}
private:pthread_mutex_t* pmutex;
};
这是我们之前利用RAII思想封装的锁的守卫者/聪明的锁,我们先不用它,最后再用(因为它太好用了,好用到不方便解释)
3.BlockQueue.hpp的编写
下面就剩下Push和Pop了
代码:
#pragma once
#include <pthread.h>
#include <queue>
#include "Lock_guard.hpp"
const int defaultSize=5;//默认大小为5template<class T>
class BlockQueue
{
public:BlockQueue(int maxSize=defaultSize)//工作: 初始化锁,条件变量,_maxSize:_maxSize(maxSize){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_c_cond,nullptr);pthread_cond_init(&_p_cond,nullptr);}~BlockQueue()//释放: 初始化锁,条件变量{pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_c_cond);pthread_cond_destroy(&_p_cond);}void Push(const T& data)//生产者放数据{//1. 生产者和生产者之间互斥,而且队列只能在队尾放数据: 因此需要加锁pthread_mutex_lock(&_mutex);//2. 如果队列满了,生产者需要去自己的条件变量下排队,等待消费者唤醒//if(Full()): 无法防止伪唤醒带来的bugwhile(Full())//能够防止伪唤醒带来的bug -> 代码的鲁棒性强{pthread_cond_wait(&_p_cond,&_mutex);}//3. 条件满足,直接放数据即可_q.push(data);//4. 大家可以自定义生产多少数据之后再唤醒消费者,我这里先暂且: 只要有一个数据就唤醒消费者pthread_cond_signal(&_c_cond);//摇消费者的铃铛,唤醒一个消费者//5. Push完成 -> 释放锁pthread_mutex_unlock(&_mutex);}void Pop(T& data)//消费者拿数据(跟Push异曲同工之妙){//1. 消费者跟消费者之间互斥,且队列只能从队头出数据: 因此需要加锁pthread_mutex_lock(&_mutex);//2. 判断是否空while(Empty()){pthread_cond_wait(&_c_cond,&_mutex);//去自己的条件变量那里排队,等着生产者唤醒}//3. 条件满足,直接拿数据即可data=_q.front();_q.pop();//4. 只要拿了一个数据就唤醒生产者,当然大家可以自定义pthread_cond_signal(&_p_cond);//摇生产者的铃铛,唤醒一个生产者//5. Pop完成 -> 释放锁pthread_mutex_unlock(&_mutex);}bool Full() const//判满{return _q.size()==_maxSize;//判断队列中的数据个数是否==_maxSize即可}bool Empty() const//判空{return _q.empty();//复用即可}private:queue<T> _q;//内部封装的STL的queuepthread_mutex_t _mutex;//一把互斥锁即可 (因为生产者之间互斥,消费者之间互斥,生产者和消费者之间互斥,因此阻塞队列在同一时刻只允许一个线程进行访问!!)pthread_cond_t _p_cond;//productor生产者的条件变量pthread_cond_t _c_cond;//consumer消费者的条件变量int _maxSize;//阻塞队列的大小(因为阻塞队列需要能够判满)
};
4.测试代码的编写
代码:
#include <iostream>
#include <unistd.h>
using namespace std;
#include "BlockQueue.hpp"#include <random>
#include <chrono> // 生成指定范围内的随机整数(不用管这个)
int generateRandomInt(int min, int max) { // 使用基于当前时间的种子 static random_device rd; static mt19937 gen(rd()); // 定义随机数分布 uniform_int_distribution<> dis(min, max); // 生成随机数 return dis(gen);
} void* productor_func(void* arg)//生产者:放数据
{BlockQueue<int>* bq=static_cast<BlockQueue<int>*>(arg);while(true){//1. 生产数据int data=generateRandomInt(1,9);//2. 放数据bq->Push(data); //3. 打印数据cout<<"productor_func put data: "<<data<<endl;//4. 休眠/不休眠随意sleep(1);}
}void* consumer_func(void* arg)//消费者:拿数据
{BlockQueue<int>* bq=static_cast<BlockQueue<int>*>(arg);while(true){//1. 拿数据int data=-1;bq->Pop(data);//2. 处理数据,我们就先暂且打印了cout<<"consumer_func get data: "<<data<<endl;//3. 休眠/不休眠随意}
}int main()
{srand(time(nullptr));BlockQueue<int>* bq=new BlockQueue<int>;pthread_t consumer_id,productor_id;pthread_create(&consumer_id,nullptr,consumer_func,bq);pthread_create(&productor_id,nullptr,productor_func,bq);pthread_join(consumer_id,nullptr);pthread_join(productor_id,nullptr);delete bq;return 0;
}
5.演示
情况1: 生产者不休眠,消费者休眠
情况2: 消费者不休眠,生产者休眠
看到了生产者和消费者的确具有同步的关系
三.基于阻塞队列的生产者消费者模型的扩展代码
1.传递任务版本
刚才的时候我们的阻塞队列当中放的是纯数据,我们可不可以放任务呢?
就像是我们进程池当中主进程向其他进程发送任务让其他线程执行似的
所以我们创建一个Task.hpp
1.Task.hpp
代码:
#pragma once
#include <unordered_map>
#include <functional>
//我们就模拟数学运算吧: + - * / % & | && ||
//因为~和!是单操作符,所以我们就不搞这两个操作符了enum State
{believable = 0,//可信division_by_zero,//除0mod_by_zero,//模0unknown,//非法操作符
};vector<string> opers={"+","-","*","/","%","&","|","&&","||"};class Task
{
public:Task()=default;Task(int left_operand,int right_operand,string op):_left_operand(left_operand),_right_operand(right_operand),_op(op){}string DebugForProductor() const{return to_string(_left_operand)+_op+to_string(_right_operand)+" = ?";}string DebugForConsumer() const{return to_string(_left_operand)+_op+to_string(_right_operand)+" = "+to_string(_ans)+"["+_stateMap[_state]+"]";}//进行操作运算void operator()(){if(_opMap.count(_op)==0)//操作符非法{_state=unknown;return;}_ans=_opMap[_op](_left_operand,_right_operand,_state);}private:int _left_operand;//左右操作数int _right_operand;string _op;//运算符int _ans;//答案State _state=believable;//答案的状态static unordered_map<string,function<int(int,int,State&)>> _opMap;//操作表static unordered_map<State,string> _stateMap;//状态表
};unordered_map<string,function<int(int,int,State&)>> Task::_opMap={{"+",[](int a,int b,State& s) {return a+b;}},{"-",[](int a,int b,State& s) {return a-b;}},{"*",[](int a,int b,State& s) {return a*b;}},{"&",[](int a,int b,State& s) {return a&b;}},{"|",[](int a,int b,State& s) {return a|b;}},{"&&",[](int a,int b,State& s) {return a&&b;}},{"||",[](int a,int b,State& s) {return a||b;}},{"/",[](int a,int b,State& s) {if(b==0) {s=division_by_zero; return 0;}else return a/b;}},{"%",[](int a,int b,State& s) {if(b==0) {s=mod_by_zero; return 0;}else return a%b;}}
};unordered_map<State,string> Task::_stateMap={{believable,"believable"},{division_by_zero,"division_by_zero"},{mod_by_zero,"mod_by_zero"},{unknown,"unknown"}
};
我们这份代码的好处是方便扩展,坏处是效率有些慢,没有疯狂if else或者switch case快
2.测试代码
3.演示
运行成功
2.多生产者多消费者版本
下面我们把它"改"成多生产多消费,这里加""是因为我们实现的时候就已经确保生产者生产者互斥,消费者消费者互斥,生产者消费者互斥了,所以根本无需改动我们的阻塞队列
但是我们要改测试代码了
因为有多生产,多消费,所以我们搞3生产者,2消费者,给它们做个编号,这5个线程共用同一个阻塞队列
因此我们封装一下阻塞队列,把阻塞队列和编号/名字封装一下,并且用一下我们的lockguard
1.改测试代码
代码:
#include <iostream>
#include <unistd.h>
#include <vector>
using namespace std;
#include "BlockQueue.hpp"
#include "Task.hpp"
#include <random>
#include <chrono>
// 生成指定范围内的随机整数
int generateRandomInt(int min, int max) { // 使用基于当前时间的种子 static random_device rd; static mt19937 gen(rd()); // 定义随机数分布 uniform_int_distribution<> dis(min, max); // 生成随机数 return dis(gen);
} template<class T>
struct ThreadData
{ThreadData(const string& name,BlockQueue<T>* bq):_name(name),_bq(bq){}string _name;BlockQueue<T>* _bq;
};pthread_mutex_t print_mutex=PTHREAD_MUTEX_INITIALIZER;void* productor_func(void* arg)//生产者:放数据
{ThreadData<Task>* td=static_cast<ThreadData<Task>*>(arg);while(true){//1. 生产数据int ldata=generateRandomInt(0,9),rdata=generateRandomInt(0,9);int i=generateRandomInt(0,opers.size()-1);Task t(ldata,rdata,opers[i]);//2. 放数据td->_bq->Push(t);//3. 打印数据LockGuard lockguard(&print_mutex);cout<<t.DebugForProductor()<<" # # : "<<td->_name<<endl;//4. 每放一次数据 -> 休眠100ms -> 0.1susleep(1000000);}
}void* consumer_func(void* arg)//消费者:拿数据
{ThreadData<Task>* td=static_cast<ThreadData<Task>*>(arg);while(true){//1. 拿数据Task t;td->_bq->Pop(t);//2. 处理数据,t();LockGuard lockguard(&print_mutex);cout<<t.DebugForConsumer()<<" # # : "<<td->_name<<endl;//3. 不休眠,疯狂拿数据}
}int main()
{BlockQueue<Task>* bq=new BlockQueue<Task>;vector<pthread_t> v(5);vector<ThreadData<Task>*> del;for(int i=0;i<5;i++){ThreadData<Task>* td=new ThreadData<Task>("thread - "+to_string(i),bq);if(i<3){pthread_create(&v[i],nullptr,productor_func,td);}else{pthread_create(&v[i],nullptr,consumer_func,td);}del.push_back(td);}for(auto& e:v) pthread_join(e,nullptr);delete bq;for(auto& e:del) delete e;return 0;
}
演示:
当然,大家可以自定义生产者生产多少数据之后再唤醒消费者,消费者消费多少数据之后在唤醒生产者
四.生产者消费者模型的再一次理解与阻塞队列版本的优劣
我们解释一下生产者消费者模型的优点:
1.多执行流解耦
2.提高效率
3.小结一下
生产者消费者模型:
通过交易场所这个大的临界资源来存放交易数据实现了多执行流之间的解耦,
从而使得生产者创建数据和消费者处理数据的工作能够跟其他线程实现并发执行,从而提高效率
只不过因为阻塞队列是把整个队列当作一个整体,所以阻塞队列在任意时刻只允许一个线程进行访问,其他线程必须正在阻塞
这个操作降低了阻塞队列的一点点效率,但是跟阻塞队列带来的优势相比,在权衡之下,依旧是优点大大高于不足
五.信号量的深入理解与使用
1.理论
我们之前在介绍System V版本的进程间通信的时候,介绍了信号量的理论,并且用信号量实现了共享内存的协同机制
下面我们稍微复习总结一下信号量的理论
还有一点:
信号量本身就具有互斥和同步的功能!!
而锁只有互斥的功能,想要同步,必须配合条件变量等等机制才能实现同步
记住: 锁:🔒,条件变量:🔔,信号量:🔢(计数器)
2.接口介绍
相比于System V的接口来说,pthread库当中信号量的接口就简洁很多
我们就只需要用这4个接口即可,下面直接在基于环形队列的生产者消费者模型的代码当中用一下信号量了
因为环形队列的生产者和消费者之间的互斥可以用信号量🔢来维护,所以我们用一下环形队列这个数据结构作为交易场所
又因为生产者和生产者,消费者和消费者之间也是互斥的,而它们之间的互斥怎么保证呢?
这点比起阻塞队列的统统挂锁🔒要难以理解一点,所以我们先实现单生产者单消费者模型,然后再改成多生产者多消费者模型
六.基于环形队列的单生产者单消费者模型
1.思路
这里我们用了信号量之后根本就不需要条件变量了,因为
队列为空时: Pop会阻塞消费者,但是当生产者Push数据之后,sem_data就++了,因此Pop阻塞的消费者就能够申请到sem_data了
同理,队列为满时: Push会阻塞生产者,但是当消费者Pop数据之后,sem_space就++了,因此Push阻塞的生产者就能够申请到sem_space了
而且环形队列的大小就是vector一开始初始化的size().因此也无需我们在设置一个变量了
2.基础代码
刚写完阻塞队列的生产者消费者模型,那单生产单消费的环形队列没啥大区别,这里直接用ThreadData了
用一下类似于适配器模式的样子,你给我传什么阻塞队列/环形队列/xxx容器/yyy容器,无所谓,我都给你绑定一个字符串
1.RingQueue.hpp
1.结构
2.代码
这里就先不给出源码了,因为这个场景对多生产多消费并不适用,为何?
那么push的时候先加锁还是先申请信号量呢??
这里比较不太好理解,我们放到改成多生产多消费的时候再谈,因为现在有一个更重要的发现需要我们介绍
3.细节
需要实现同步+互斥的时候
锁必须配合条件变量进行使用(不考虑锁能配合信号量一起使用)
而有时信号量可以无需配合条件变量进行使用
因此信号量才被称为"对资源的预定机制",因为这种情况下它不知不觉就自动实现了同步
因此信号量本身就具有互斥和同步的功能!!
而锁只有互斥的功能,想要同步,必须配合条件变量等等机制才能实现同步
3.扩展代码
下面直接用我们的Task.hpp,啥也不用改,拿过头文件来直接用就行
跟阻塞队列的一样,没啥好解释的
七.基于环形队列的多生产者多消费者模型
1.先申请信号量,后申请锁的原因
刚才我们说了,Push和Pop想要改成多生产者多消费者一定要加锁,那么先加锁还是先申请信号量呢?
代码的正确性上讲,其实是都可以,但是效率上是有区别的
申请信号量🔢: 本质是解决生产者和消费者之间的互斥(解决座位数目(图书馆资源)和读者需求之间的互斥)
申请锁🔒: 本质是解决生产者和生产者之间的互斥,消费者和消费者之间的互斥
因此申请信号量是解决外部矛盾,而申请锁是解决内部矛盾
而对于同时面临内外的非常严重的问题时: 解决矛盾一定是先解决外部矛盾,后解决内部矛盾
2.RingQueue代码
直接用我们的LockGuard秒了它
#pragma once
#include <semaphore.h>const int defaultSize = 5;template <class T>
class RingQueue
{
public:RingQueue(int size = defaultSize): _p_index(0), _c_index(0){_arr.resize(size);sem_init(&_sem_space, 0, size); //_space空间个数初始值为sizesem_init(&_sem_data, 0, 0); //_data数据个数初始值:0pthread_mutex_init(&_p_mutex,nullptr);pthread_mutex_init(&_c_mutex,nullptr);}~RingQueue(){sem_destroy(&_sem_space);sem_destroy(&_sem_data);pthread_mutex_destroy(&_p_mutex);pthread_mutex_destroy(&_c_mutex);}// 我们封装一个P操作和一个V操作,方便使用void P(sem_t &sem) // P -> wait --{sem_wait(&sem);}void V(sem_t &sem) // V -> post ++{sem_post(&sem);}void Push(const T &data){// 1. 申请信号量P(_sem_space);// 2. 放数据即可{LockGuard lockguard(&_p_mutex);_arr[_p_index] = data;_p_index = (_p_index + 1) % _arr.size();}// 3. 释放信号量V(_sem_data);}void Pop(T &data){// 1. 申请信号量P(_sem_data);// 2. 放数据即可{LockGuard lockguard(&_c_mutex);data = _arr[_c_index];_c_index = (_c_index + 1) % _arr.size();}// 3. 释放信号量V(_sem_space);}private:vector<T> _arr; // 环形队列底层容器,环形队列大小就是_arr.size()sem_t _sem_space; // 空间信号量sem_t _sem_data; // 数据信号量int _p_index; // 生产者放数据的下标int _c_index; // 消费者拿数据的下标pthread_mutex_t _p_mutex; // 解决生产者内部矛盾pthread_mutex_t _c_mutex; // 解决消费者内部矛盾
};
3.测试
直接上测试代码,2个消费者,3个生产者,给cout加锁,走起
#include <iostream>
using namespace std;
#include <vector>
#include <unistd.h>
#include <pthread.h>
#include "Lock_guard.hpp"
#include "RingQueue.hpp"
#include "Task.hpp"
#include <random>
#include <chrono>// 生成指定范围内的随机整数
int generateRandomInt(int min, int max)
{// 使用基于当前时间的种子static random_device rd;static mt19937 gen(rd());// 定义随机数分布uniform_int_distribution<> dis(min, max);// 生成随机数return dis(gen);
}// 直接搞成类似于容器适配器模式了
template <class Container>
struct ThreadData
{ThreadData(const string &name, Container *con): _name(name), _con(con) {}string _name;Container *_con;
};pthread_mutex_t print_mutex = PTHREAD_MUTEX_INITIALIZER;void *consumer_func(void *arg)
{ThreadData<RingQueue<Task>> *td = static_cast<ThreadData<RingQueue<Task>> *>(arg);while (true){int Ldata = generateRandomInt(0, 9), Rdata = generateRandomInt(0, 9), opi = generateRandomInt(0, opers.size() - 1);Task t(Ldata, Rdata, opers[opi]);td->_con->Push(t);LockGuard lockguard(&print_mutex);//改成多生产者多消费者时再给打印加锁cout << t.DebugForProductor() << " " << td->_name << endl;sleep(1); // 生产者休眠1s}
}void *productor_func(void *arg)
{ThreadData<RingQueue<Task>> *td = static_cast<ThreadData<RingQueue<Task>> *>(arg);while (true){Task t;td->_con->Pop(t);LockGuard lockguard(&print_mutex);//改成多生产者多消费者时再给打印加锁t();cout << t.DebugForConsumer() << " " << td->_name << endl;}
}int main()
{RingQueue<Task> *rq = new RingQueue<Task>;vector<pthread_t> v(5);vector<ThreadData<RingQueue<Task>>*> delv;//先生产者for(int i=0;i<3;i++){ThreadData<RingQueue<Task>> *td = new ThreadData<RingQueue<Task>>("thread - p"+to_string(i+1), rq);delv.push_back(td);pthread_create(&v[i],nullptr,productor_func,td);}//后消费者for(int i=0;i<2;i++){ThreadData<RingQueue<Task>> *td = new ThreadData<RingQueue<Task>>("thread - c"+to_string(i+1), rq);delv.push_back(td);pthread_create(&v[i+3],nullptr,consumer_func,td);}for(auto& e:v) pthread_join(e,nullptr);delete rq;for(auto& e:delv) delete e;return 0;
}
多生产多消费测试的修改跟阻塞队列的差不多,唯一最大的变化就是这里给cout也加锁了
八.基于环形队列的生产者消费者模型与基于阻塞队列的生产者消费者模型的对比
环形队列的生产者消费者模型通过将整个交易场所划分为若干个区域,
从而将使得生产者和消费者可以在一定条件下实现并发访问环形队列,从而相比于阻塞队列来说在这一点上提高了效率
但是也不能单纯地下定义说环形队列就是比阻塞队列好
别忘了: 阻塞队列还能够由我们自定义生产者生产多少数据之后再唤醒消费者,消费者消费多少数据之后在唤醒生产者的
条件变量允许开发者根据特定的条件来决定何时唤醒线程,而信号量则通过控制资源的并发访问量来实现同步
因此阻塞队列中互斥锁配合条件变量能够使得代码更加易于控制和变化
所以两种方法各有千秋,使用哪种看具体需求和场景而定
以上就是Linux多线程系列三: 生产者消费者模型,信号量使用,基于阻塞队列和环形队列的这两种生产者消费者代码的实现的全部内容,希望能对大家所有帮助!!!
相关文章:

Linux多线程系列三: 生产者消费者模型,信号量使用,基于阻塞队列和环形队列的这两种生产者消费者代码的实现
Linux多线程系列三: 生产者消费者模型,信号量,基于阻塞队列和环形队列的这两种生产者消费者代码的实现 一.生产者消费者模型的理论1.现实生活中的生产者消费者模型2.多线程当中的生产者消费者模型3.理论 二.基于阻塞队列的生产者消费者模型的基础代码1.阻塞队列的介绍2.大致框架…...

Mongodb介绍及springboot集成增删改查
文章目录 1. MongoDB相关概念1.1 业务应用场景1.2 MongoDB简介1.3 体系结构1.4 数据模型1.5 MongoDB的特点 2. docker安装mongodb3. springboot集成3.1 文件结构3.2 增删改查3.2.1 增加insert3.2.2 保存save3.2.3 更新update3.2.4 查询3.2.5 删除 1. MongoDB相关概念 1.1 业务…...

JAVA 中 HTTP 基本认证(Basic Authentication)
目录 服务端这么做服务端告知客户端使用 Basic Authentication 方式进行认证服务端接收并处理客户端按照 Basic Authentication 方式发送的数据 客户端这么做如果客户端是浏览器如果客户端是 RestTemplat如果客户端是 HttpClient 其它参考 服务端这么做 服务端告知客户端使用 …...
【Flutter】 webview请求权限问题
需求:webview可以通过录音后语音转文字。 使用插件: permission_handler: ^11.0.1 webview_flutter: ^4.7.0 代码如下: 1.添加权限 添加安卓的权限: android/app/src/main/AndroidManifest.xml <uses-permission android…...

rocketmq 学习二 基本概念
教程:基本概念 | RocketMQ 视频教程 https://www.bilibili.com/video/BV1d5411y7UW?vd_sourcef1bd3b5218c30adf0a002c8c937e0a27 版本:5.0 一 基本概念 1.1 生产者/Producer 1.1.1 定义 消息发布者。是构建并传输消息到服务端的运行实体。…...

C++初阶学习第十弹——探索STL奥秘(五)——深入讲解vector的迭代器失效问题
vector(上):C初阶学习第八弹——探索STL奥秘(三)——深入刨析vector的使用-CSDN博客 vector(中):C初阶学习第九弹——探索STL奥秘(四)——vector的深层挖掘和…...
C#自动实现缺陷数据增强
实现该自动缺陷数据增强需要以下几个方面: 1、正样本若干; 2、负样本若干(ps抠图,为png透明图像) using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing...

JPHS-JMIR Public Health and Surveillance
文章目录 一、期刊简介二、征稿信息三、期刊表现四、投稿须知五、投稿咨询 一、期刊简介 JMIR Public Health and Surveillance是一本多学科期刊,专注于公共卫生创新与技术的交叉领域,包括公共卫生信息学、监测(监测系统和快速报告ÿ…...
Flutter 中的 AnimatedThere 小部件:全面指南
Flutter 中的 AnimatedThere 小部件:全面指南 在Flutter中,动画是增强用户体验的强大工具。虽然Flutter没有一个名为AnimatedThere的官方小部件,但我们可以根据常见的动画模式来构建一个类似的自定义动画效果。本文将指导您如何使用Flutter的…...

2024南京智博会:展示国内外前沿科技成果,推动智能产业快速发展
2024南京智博会,一场科技盛宴的盛宴,汇聚了全球人工智能、物联网、大数据、机器人、自动驾驶等领域的最新技术和创新理念。作为一场国际性的盛会,它不仅展示了国内外前沿科技成果,更为参展者搭建了一个交流合作的平台,…...

基于springboot实现的校园博客系统
开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7(一定要5.7版本) 数据库工具:Navicat11 开发软件:eclipse/myeclipse/idea Maven…...
人从胚胎开始就要交税,直到死亡,是这样吗?
文章目录 梗概税收的基本概念从胚胎到死亡的税收分析胚胎到出生出生到成年成年到死亡 总结 梗概 人从胚胎阶段开始交税直到死亡,这个观点听起来有些戏剧化,但如果我们广义地理解“交税”这个概念,可以从不同的角度进行探讨。实际上ÿ…...
c语言指针入门(二)
今天学习了指针的两个常用场景,在此记录,以便后续查看。 场景1:传数组 在c语言中,我们在定义函数的时候是没有办法直接传一个数组进去的,为了解决这个问题,我们一般将数组的名称当作一个指针参数传入到函数…...
一篇讲透排序算法之插入排序and选择排序
1.插入排序 1.1算法思想 先将数组的第一个元素当作有序,让其后一个元素与其比较,如果比第一个元素小则互换位置,之后再将前两个元素当作有序的,让第三个元素与前两个元素倒着依次进行比较,如果第三个元素比第二个元素…...
CompletableFuture的主要用途是什么?
CompletableFuture 的主要用途是为复杂的异步编程模型提供一种更简单,更具可读性的方式。它主要用于以下几个方面: 非阻塞计算:CompletableFuture 为处理高延迟的计算任务提供了非阻塞的解决方案。你可以启动一个计算任务,而不需要…...

QtCreator,动态曲线实例
样式图: .ui 在sloem1.ui文件中,拖入一个label控件, 头文件.h #include "QtGui/QPainter.h" #include "QtCore/QTimer.h"protected:bool eventFilter(QObject *obj,QEvent *event);void labelPaint();public slots: /…...
Model-Based Pose Estimation for Rigid Objects(基于SIFT)
6D目标检测工程落地需求的小算力算法,本文具有借鉴意义,但对于特征点少的目标不太好用。 摘要 在多个实际应用中,经常会遇到确定图像中出现的物体姿态的问题。处理这一挑战的最有效策略是按照基于模型的范式进行,这涉及构建物体…...

STM32自己从零开始实操02:输入部分原理图
一、触摸按键 1.1指路 项目需求: 4个触摸按键,主控芯片 TTP224N-BSBN(嘉立创,封装 TSSOP-16),接入到 STM32 的 PE0,PE1,PE2,PE3。 1.2走路 1.2.1数据手册重要信息提…...
JavaScript异步编程——03-Ajax传输json和XML的技术文档
JavaScript异步编程——03-Ajax传输json和XML的技术文档 目录 JavaScript异步编程——03-Ajax传输json和XML的技术文档 一、引言 二、Ajax简介 三、Ajax传输JSON数据 四、Ajax传输XML数据 五、总结 一、引言 在现代Web开发中,Ajax技术已经成为实现前后端数据交…...
移动端常用meta
在移动端开发中,<meta> 标签用于提供关于HTML文档的元数据,这些元数据不会显示在页面上,但可以被浏览器解析,用于控制页面的行为和外观。以下是一些在移动端开发中常用的 标签: 1. 视口设置 这是移动端开发中最…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...

工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...

Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...