POSIX信号量

1.快速认识信号量接口

POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。我们之前认识SystemV信号量时有这样三个结论:

1.信号量的本质是一把计数器

2.申请信号量本质就是预定资源

3.PV操作是原子的

我们之前在基于阻塞队列中通过代码实现的生产者消费者模型是通过STL容器中的queue这个队列当作整体来使用的,当然因为是STL容器,也只能当作整体来使用,但是如果我们能通过自己模拟实现一种数据结构是可以将这个队列整体进行拆分成一小块一小块的临界资源,然后让多线程不访问临界资源的同一个区域,实现让每一个线程都能并发访问属于自己的一小块区域。

但是这样的话我们最怕的是如果只有7份资源,但是访问的线程有8,9个,那么就会造成冲突的问题,所以就可以使用信号量来解决。当线程要进行访问临界资源的时候:

①先申请信号量P

②然后再访问指定的某一个位置

③最后释放信号量。

而访问指定的某一个指定的位置本质就是访问临界资源,那么这里还需要进行判断能否访问吗?其实不用再进行判断了,因为如果申请信号量成功了就已经说明条件满足了,也可以说在信号量那个地方就已经进行判断过了。

初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值

销毁信号量

int sem_destroy(sem_t *sem);

等待信号量

功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()

发布信号量

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()

2.基于环形队列的CP问题——理论

环形队列采用数组模拟,用模运算来模拟环状特性。

我们把这个环形队列就看作是一个生产者和消费者的交易场所(临界资源)。

之前我们认识的环形队列有头尾指针head,tail,现在一样的,开始时我们让productor,consumer两个整数指向同一位置,然后1.我们想让多线程同时看到我们这个环形队列,2.让生产者和消费者能并发访问该环形队列。

刚开始的时候队列是空的,我们先让生产者先放数据,让消费者不动,如果生产者生产的数据已经在环形队列中绕了一圈了,那么生产者还能继续生产吗?

在这种情况下,生产者不能再继续生产数据了,因为如果生产者已经把消费者绕了一圈了之后如果再继续生产数据就会把历史上生产的数据都给覆盖了,但是这个时候消费者还并没有拿到对应的数据。如果此时让消费者拿数据,让生产者停下来,如果当消费者把数据都拿完了,那么也不能继续拿数据了,因为拿完之后生产者并没有生产新数据,如果继续拿数据拿到的都是乱码。

我们要正确的生产和消费需要满足下面几个原则:

①生产者不能把消费者套一个圈

②消费者不能超过生产者

用一个小游戏来理解:

下面我们把环形队列想象成一个圆形餐桌,假设你和小明在餐桌上玩一个游戏,你放苹果,小明拿苹果,一开始你和小明站在同一个盘子旁边,首先得你先放苹果,假设你已经放满了,那你就不能继续放了,因为遵循上面得原则你相当于生产者,小明相当于消费者,小明不能超过你。所以此时小明可以把放满了苹果得盘子一个一个拿走,然后盘子又变空了,但是我们的目的并不是让你放一个,小明拿一个,而是为了实现你放苹果的同时,小明也能拿苹果,只有当餐桌盘子苹果放满,或者一个苹果都没有的时候需要注意一点,其他情况下都是可以你放一个苹果的同时,小明可以拿另一个苹果,哪怕小明拿苹果的速度比较慢,你放的比较快,你和小明也不会指向同一个位置。此时就可以实现多线程并发进入。

总结下来就是,生产者和消费者只有两种场景会指向同一个位置:

③为空:只能(互斥)让生产者跑(同步)

④为满:只能(互斥)让消费者跑(同步)

其他情况根本不会指向同一个位置,这时就可以实现多线程并发进入临界区。

对资源的认识:生产者和消费者看待资源的角度是不同的,生产者会生产数据,但是需要空间来放数据,所以生产者认为空间是资源,而消费者则认为数据是资源。

刚开始时环形队列为空,空间是充足的,所以我们让生产者的信号量为:p->sem_space = N;

刚开始没有数据,所以设置消费者的信号量为:c->sem_data = 0;

下面我们用伪代码通过上面的4个原则来实现这一基于环形队列的CP问题:

生产者:                                                    消费者:

P(sem_space)                                           P(sem_data)

//生产行为,位置待定                                 //消费行为,位置待定

V(sem_data)                                             V(sem_space)

那么下面我们争对上面的4种情况来跑一下这段伪代码:

如果一开始生产一直生产,生产一个sem_space--,当sem_space减到0时,再想生产就会被阻塞,申请不了信号量,这就叫做生产者不会把消费者套一个圈。假设现在已经生产满了,那么sem_data也就变成了N,此时生产者阻塞着,我们让消费者一直消费,也就是让sem_data--,直到减到0的时候消费者会被阻塞住,那么消费者把数据消费完就不会再消费了。

消费者生产者哪个线程先运行我们是说不准的,但是一开始队列是空的,只有空间资源,没有数据资源,所以一开始消费者就是会被阻塞,让生产者生产数据,也就是为空的时候只能让生产者跑,而如果生产的数据满了,生产者也会被阻塞住,也就是为满时只能让消费者跑,所以说这份伪代码是满足上面的四个原则的。

3.基于环形队列的CP问题——代码

RingQueue.hpp

#pragma once#include<iostream>
#include<vector>
#include<semaphore.h>const int defaultsize = 5;
const int defaultvalue = 0;template<class T>
class RingQueue
{
public:RingQueue(int size = defaultsize):ringqueue(size),_size(size),_p_index(defaultvalue),_c_index(defaultvalue){sem_init(&_space_sem,0,size);sem_init(&_data_sem,0,0);}void Push(const T& in){//生产P(_space_sem);ringqueue[_p_index] = in;_p_index++;_p_index%=_size;V(_data_sem);}void Pop(T* out){//消费P(_data_sem);*out = ringqueue[_c_index];_c_index++;_c_index%=_size;V(_space_sem);}~RingQueue(){sem_destroy(&_space_sem);sem_destroy(&_data_sem);}private:void P(sem_t & sem){sem_wait(&sem);}void V(sem_t &sem){sem_post(&sem);}private:std::vector<T> ringqueue;int _size;sem_t _space_sem;sem_t _data_sem;int _p_index;int _c_index;
};

Main.cc 

#include "RingQueue.hpp"
#include<pthread.h>
#include<unistd.h>void * productor(void * args)
{RingQueue<int>* rq = static_cast<RingQueue<int>*>(args);int cnt = 100;sleep(5);while(true){//rq->push();rq->Push(cnt);std::cout<<"Productor done,data is: "<<cnt<<std::endl;cnt--;sleep(1);}return nullptr;
}void * consumer(void * args)
{RingQueue<int>* rq = static_cast<RingQueue<int>*>(args);int data = 0;while(true){//rq->pop();rq->Pop(&data);std::cout<<"Consumer done,data is: "<<data<<std::endl;}return nullptr;
}int main()
{pthread_t c,p;RingQueue<int>* rq = new RingQueue<int>();pthread_create(&c,nullptr,consumer,rq);pthread_create(&p,nullptr,productor,rq);pthread_join(c,nullptr);pthread_join(p,nullptr);return 0;
}

Makefile:

testRingQueue:Main.ccg++ -o $@ $^ -lpthread -std=c++11.PHONY:clean
clean:rm -rf testRingQueue

刚开始我们的消费者运行还是生产者运行我们不太清楚,但是刚开始执行Push,Pop函数时,消费者一定是被阻塞的,而生产者是可以直接往后执行进行生产的,所以这时一定是生产者先生产,但是生产者生产前我们sleep(6),所以6s后是生产一个数据,消费者消费一个数据,生产一个,消费一个这样的顺序执行下去,下面我们看运行结果:

前6秒:

后面执行:

下面我们再修改一小段代码实现一瞬间让生产者把环形队列空间的数据生产满了,然后生产者被阻塞,然后让消费者sleep(1);之后进行消费,后面的现象就变成了消费一个生产一个,消费一个生产一个,下面我们把上述Main.cc中的consumer函数和productor函数进行修改部分代码:

void * productor(void * args)
{RingQueue<int>* rq = static_cast<RingQueue<int>*>(args);int cnt = 100;while(true){//rq->push();rq->Push(cnt);std::cout<<"Productor done,data is: "<<cnt<<std::endl;cnt--;}return nullptr;
}void * consumer(void * args)
{RingQueue<int>* rq = static_cast<RingQueue<int>*>(args);int data = 0;while(true){sleep(1);//rq->pop();rq->Pop(&data);std::cout<<"Consumer done,data is: "<<data<<std::endl;}return nullptr;
}

下面我们看运行结果:

我们发现确实一开始生产者直接生产了5个数据,然后消费一个生产一个,消费一个生产一个。

我们上面仅仅只是对整形数据做了测试,如果我们把整形数据换成类对象可以吗?

下面我们就把Task封装成一个类作为数据传进来,该类实现的任务是生产者生产数据通过随机传入两个操作数一个操作数,消费者对传入的操作数进行处理输出对应的结果:

Task.hpp

#pragma once
#include<string>
const int defaultvalue = 0;
enum
{ok = 0,div_zero,mod_zero,unknow
};const std::string opers = "+-*/%&()";
class Task
{
public:Task(){}Task(int x, int y, char op, int result=defaultvalue, int code=ok): _data_x(x), _data_y(y), _oper(op){}std::string PrintTask(){std::string s;s= std::to_string(_data_x);s+=_oper;s+=std::to_string(_data_y);s+="=?";return s;}std::string PrintResult(){std::string s;s= std::to_string(_data_x);s+=_oper;s+=std::to_string(_data_y);s+="=";s+=std::to_string(_result);s+=" [";s+=std::to_string(_code);s+="]";return s;}void operator()(){Run();}void Run(){switch (_oper){case '+':_result = _data_x + _data_y;break;case '-':_result = _data_x - _data_y;break;case '*':_result = _data_x * _data_y;break;case '/':{if (_data_y == 0){_code = div_zero;}else _result = _data_x / _data_y;}break;case '%':{if (_data_y == 0){_code = mod_zero;}else _result = _data_x % _data_y;}break;default:_code = unknow;break;}}~Task() {}private:int _data_x; // 操作数int _data_y; // 操作数char _oper;  // 运算符int _result; // 结果int _code;   // 结果码 0:可信  !0:不可信
};

Makefile和RingQueue.hpp的代码不修改,对Main.cc修改:

#include "RingQueue.hpp"
#include<pthread.h>
#include<unistd.h>
#include"task.hpp"
#include<ctime>
void * productor(void * args)
{RingQueue<Task>* rq = static_cast<RingQueue<Task>*>(args);//sleep(5);while(true){int data1 = rand()%10;usleep(rand()%123);int data2 = rand()%10;usleep(rand()%123);char oper = opers[rand() % opers.size()];//2.生产数据 Task t(data1,data2,oper);rq->Push(t);std::string task_string = t.PrintTask();std::cout<<"productor task: "<<task_string<<std::endl;//rq->push();}return nullptr;
}void * consumer(void * args)
{RingQueue<Task>* rq = static_cast<RingQueue<Task>*>(args);int data = 0;while(true){//sleep(1);//rq->pop();Task t;rq->Pop(&t);t();std::cout<<"consumer Result: "<<t.PrintResult()<<std::endl;}return nullptr;
}int main()
{srand((uint64_t)time(nullptr) ^ getpid() ^ pthread_self());//只是为了生成更随机的数据pthread_t c,p;RingQueue<Task>* rq = new RingQueue<Task>();pthread_create(&c,nullptr,consumer,rq);pthread_create(&p,nullptr,productor,rq);pthread_join(c,nullptr);pthread_join(p,nullptr);return 0;
}

运行结果:

目前我们上面写的代码都是基于单生产单消费 的代码,如果我们要修改成多生产和多消费的话我们就需要维护生产者和消费者之间的互斥关系。

4.修改整体代码为多生产者和多消费者

RingQueue.hpp

#pragma once#include <iostream>
#include <vector>
#include <semaphore.h>
#include "LockGuard.hpp"
const int defaultsize = 5;
const int DefaultValue = 0;template <class T>
class RingQueue
{
public:RingQueue(int size = defaultsize): ringqueue(size), _size(size), _p_index(DefaultValue), _c_index(DefaultValue){sem_init(&_space_sem, 0, size);sem_init(&_data_sem, 0, 0);pthread_mutex_init(&_p_mutex, nullptr);pthread_mutex_init(&_c_mutex, nullptr);}void Push(const T &in){// 生产P(_space_sem);{LockGuard lockguard(&_p_mutex);ringqueue[_p_index] = in;_p_index++;_p_index %= _size;}V(_data_sem);}void Pop(T *out){// 消费P(_data_sem);{LockGuard lockguard(&_c_mutex);*out = ringqueue[_c_index];_c_index++;_c_index %= _size;}V(_space_sem);}~RingQueue(){sem_destroy(&_space_sem);sem_destroy(&_data_sem);pthread_mutex_destroy(&_p_mutex);pthread_mutex_destroy(&_c_mutex);}private:void P(sem_t &sem){sem_wait(&sem);}void V(sem_t &sem){sem_post(&sem);}private:std::vector<T> ringqueue;int _size;sem_t _space_sem;sem_t _data_sem;int _p_index;int _c_index;pthread_mutex_t _p_mutex;pthread_mutex_t _c_mutex;
};

Task.hpp

#pragma once
#include<string>
const int defaultvalue = 0;
enum
{ok = 0,div_zero,mod_zero,unknow
};const std::string opers = "+-*/%&()";
class Task
{
public:Task(){}Task(int x, int y, char op, int result=defaultvalue, int code=ok): _data_x(x), _data_y(y), _oper(op){}std::string PrintTask(){std::string s;s= std::to_string(_data_x);s+=_oper;s+=std::to_string(_data_y);s+="=?";return s;}std::string PrintResult(){std::string s;s= std::to_string(_data_x);s+=_oper;s+=std::to_string(_data_y);s+="=";s+=std::to_string(_result);s+=" [";s+=std::to_string(_code);s+="]";return s;}void operator()(){Run();}void Run(){switch (_oper){case '+':_result = _data_x + _data_y;break;case '-':_result = _data_x - _data_y;break;case '*':_result = _data_x * _data_y;break;case '/':{if (_data_y == 0){_code = div_zero;}else _result = _data_x / _data_y;}break;case '%':{if (_data_y == 0){_code = mod_zero;}else _result = _data_x % _data_y;}break;default:_code = unknow;break;}}~Task() {}private:int _data_x; // 操作数int _data_y; // 操作数char _oper;  // 运算符int _result; // 结果int _code;   // 结果码 0:可信  !0:不可信
};

Main.cc

#include "RingQueue.hpp"
#include<pthread.h>
#include<unistd.h>
#include"task.hpp"
#include<ctime>class ThreadData
{
public:ThreadData(const std::string & name,RingQueue<Task>* rq): threadname(name),_rq(rq){}RingQueue<Task> * getRQ(){return this->_rq;}void setRQ(RingQueue<Task>* rq){this->_rq = rq;}std::string getName(){return this->threadname;}void setName(std::string name){this->threadname = name;}private:std::string threadname;RingQueue<Task>* _rq;
};void * productor(void * args)
{ThreadData* td = static_cast<ThreadData*>(args);//sleep(5);while(true){int data1 = rand()%10;usleep(rand()%123);int data2 = rand()%10;usleep(rand()%123);char oper = opers[rand() % opers.size()];//2.生产数据 Task t(data1,data2,oper);td->getRQ()->Push(t);std::string task_string = t.PrintTask();std::cout<<"productor task: "<<task_string<<" threadname: "<<td->getName()<<std::endl;//rq->push();sleep(1);}return nullptr;
}void * consumer(void * args)
{ThreadData* td = static_cast<ThreadData*>(args);while(true){//sleep(1);//rq->pop();Task t;td->getRQ()->Pop(&t);t();std::cout<<"consumer Result: "<<t.PrintResult()<<" threadname: "<<td->getName()<<std::endl;}return nullptr;
}int main()
{srand((uint64_t)time(nullptr) ^ getpid() ^ pthread_self());//只是为了生成更随机的数据pthread_t c[3],p[2];RingQueue<Task>* rq = new RingQueue<Task>();ThreadData td1("consumer-1",rq),td2("consumer-2",rq),td3("consumer-3",rq),td4("productor-1",rq),td5("productor-2",rq);pthread_create(&p[0],nullptr,productor,&td4);pthread_create(&p[1],nullptr,productor,&td4);pthread_create(&c[0],nullptr,consumer,&td1);pthread_create(&c[1],nullptr,consumer,&td2);pthread_create(&c[2],nullptr,consumer,&td3);pthread_join(c[0],nullptr);pthread_join(c[1],nullptr);pthread_join(c[2],nullptr);pthread_join(p[0],nullptr);pthread_join(p[1],nullptr);return 0;
}

运行结果:

所以以上就是基于环形队列的多生产多消费者模型的代码实现。

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

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

相关文章

进程调度算法

进程调度算法 进程调度算法先来先服务调度基于优先级调度&#xff08;Priority Scheduling&#xff09;短进程优先 / 最短剩余时间优先轮转法&#xff08;Round-Robin Scheduling&#xff09;高响应比优先调度算法&#xff08;Highest Response Ratio Next&#xff09;多级反馈…

jupyter 设置工作目录

本博客主要介绍&#xff1a; 如何为jupyter设置工作目录 1.打开 anaconda prompt , 执行 jupyter notebook --generate-config 执行这个命令后会生成一个配置文件 2. 打开jupyter_notebook_config.py文件编辑 搜索notebook_dir&#xff0c;把这行代码的注释取消&#xff0c;…

stm32再实现感应开关盖垃圾桶

一、项目需求 检测靠近时&#xff0c;垃圾桶自动开盖并伴随滴一声&#xff0c;2秒后关盖 发生震动时&#xff0c;垃圾桶自动开盖并伴随滴一声&#xff0c;2秒后关盖 按下按键时&#xff0c;垃圾桶自动开盖并伴随滴一声&#xff0c;2秒后关盖 硬件清单 SG90 舵机&#xff0c;…

HTTP 与 HTTPS 的区别

基本概念 HTTP&#xff08;HyperText Transfer Protocol&#xff1a;超文本传输协议&#xff09;是一种应用层协议&#xff0c;主要用于在网络上进行信息的传递&#xff0c;特别是用于Web浏览器和服务器之间的通信。 它使用明文方式发送数据&#xff0c;这意味着传输的内容可…

公司服务器被.rmallox攻击了如何挽救数据?

公司服务器被.rmallox攻击了如何挽救数据&#xff1f; .rmallox这种病毒与之前的勒索病毒变种有何不同&#xff1f;它有哪些新的特点或功能&#xff1f; .rmallox勒索病毒与之前的勒索病毒变种相比&#xff0c;具有一些新的特点和功能。这种病毒主要利用加密技术来威胁用户&am…

【JavaScript】数组 ③ ( JavaScript 数组长度 | 修改数组长度 | 数组案例 )

文章目录 一、JavaScript 数组长度1、数组长度2、修改数组长度 二、数组案例1、求数组元素平均值2、求数组元素最大值 一、JavaScript 数组长度 1、数组长度 在 JavaScript 中 , 数组长度 可以通过 数组变量的 length 属性 获取 , 该属性 返回 数组中的元素数量 , 也就是 数组长…

基于微信小程序的日语词汇学习设计与实现(论文+源码)_kaic

日语词汇学习小程序 摘 要 日语词汇学习小程序是高校人才培养计划的重要组成部分&#xff0c;是实现人才培养目标、培养学生科研能力与创新思维、检验学生综合素质与实践能力的重要手段与综合性实践教学环节。本学生所在学院多采用半手工管理日语词汇学习小程序的方式&#x…

如何用Python脚本自动发送邮件?

目录 1. 基础知识 1.1. SSH&#xff08;Secure Shell&#xff09;协议 1.2. SMTP&#xff08;Simple Mail Transfer Protocol&#xff09;协议 1.3. SSH协议与SMTP协议之间的关系 2. QQ邮箱设置 2.1. 开启SMTP服务 2.2. 编写脚本 3. 测试成功 1. 基础知识 邮件的发送过…

从TCP/IP协议到socket编程详解

​ 我的所有学习笔记&#xff1a;https://github.com/Dusongg/StudyNotes⭐⭐⭐ ​ 文章目录 1 网络基础知识1.1 查看网络信息1.2 认识端口号1.3 UDP1.4 TCP1.4.1 确认应答机制1.4.2 TCP三次握手/四次挥手为什么是三次握手为什么是四次挥手listen 的第二个参数 backlog—— 全…

01_安装VMwareWorkstation虚拟机

环境&#xff1a;Win10 19045 软件版本&#xff1a;VMware-workstation-17.5.1 一、下载链接 Download VMware Workstation Pro 二、安装&#xff08;无脑下一步&#xff09; 安装位置自选&#xff0c;最好非系统盘。 增强型键盘驱动自选。 更新自选。 快捷方式自选。 三、…

fastllm在CPU上推理ChatGLM2-6b,就来看这篇文章,速度很快

介绍: GitHub - ztxz16/fastllm: 纯c++的全平台llm加速库,支持python调用,chatglm-6B级模型单卡可达10000+token / s,支持glm, llama, moss基座,手机端流畅运行纯c++的全平台llm加速库,支持python调用,chatglm-6B级模型单卡可达10000+token / s,支持glm, llama, moss基…

Linux系统----------探索mysql数据库MHA高可用

目录 一、MHA概述 1.1 什么是 MHA 1.2MHA 的组成 1.2.1MHA Node&#xff08;数据节点&#xff09; 1.2.2MHA Manager&#xff08;管理节点&#xff09; 1.3MHA 的特点 1.4MHA工作原理 1.5数据同步的方式 1.5.1同步复制 1.5.2异步复制 1.5.3半同步复制 二、搭建 MySQ…

小赢科技公布2023年业绩:业绩稳健增长,服务“触角”有效延伸

近日&#xff0c;金融科技公司小赢科技&#xff08;NYSE:XYF&#xff09;发布了2023年第四季度及全年未经审计的财务业绩。 财报显示&#xff0c;小赢科技2023年全年总净营收约为48.15亿元&#xff0c;同比增长35.1%&#xff1b;净利润约为11.87亿元&#xff0c;同比增长46.2%…

平价的挂耳式耳机有哪些?五大高口碑品牌,深度测评严选!

随着技术的发展&#xff0c;市面上的一些高端开放式耳机已经在音质上有了显著的提升&#xff0c;甚至可以媲美一些入耳式耳机。与传统入耳式耳机相比&#xff0c;开放式耳机不会对耳道造成压迫&#xff0c;这减少了耳朵的疲劳感&#xff0c;使得长时间聆听音乐变得更加舒适。由…

FastAPI+React全栈开发08 安装MongoDB

Chapter02 Setting Up the Document Store with MongoDB 08 Installing MongoDB and friends FastAPIReact全栈开发08 安装MongoDB The MongoDB ecosystem is composed of different pieces of software, and I remember that when I was starting to play with it, there w…

《AIGC重塑金融:AI大模型驱动的金融变革与实践》

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-oBSlqt4Vga1he7DL {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

亚信安全联合人保财险推出数字安全保障险方案,双重保障企业数字化转型

数字化发展&#xff0c;新兴技术的应用与落地带来网络攻击的进一步演进升级&#xff0c;同时全球产业链供应链融合协同的不断加深&#xff0c;更让网络威胁的影响范围与危害程度不断加剧。 企业单纯依靠自身安全能力建设&#xff0c;能否跟上网络威胁的进化速度&#xff1f;能否…

《算法笔记》系列----质数的判断(埃氏筛法)

目录 一、朴素算法 二、埃氏筛法 1、与朴素算法对比 2、算法介绍 3、例题即代码实现 一、朴素算法 从素数的定义中可以知道&#xff0c;一个整数n要被判断为素数&#xff0c;需要判断n是否能被2.3.n- 1中的一个整除。只2&#xff0c;3..n- 1都不能整除n&#xff0c;n才能…

基于el-table实现行内增删改

实现效果&#xff1a; 核心代码&#xff1a; <el-table :data"items"style"width: 100%;margin-top: 16px"border:key"randomKey"><el-table-column label"计划名称"property"name"><template slot-scope&q…

Learning To Count Everything

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 摘要Abstract文献阅读&#xff1a;学习数一切东西1、研究背景2、提出方法3、模块详细3.1、多尺度特征提取模块3.2、密度预测模块 4、损失函数5、性能对比6、贡献 二…