进程间通信-进程池
目录
理解
完整代码
完善代码
回收子进程:
不回收子进程:
子进程使用重定向优化
理解
#include <iostream>
#include <unistd.h>
#include <string>
#include <vector>
#include <sys/types.h>void work(int rfd)
{
}// master
class Channel
{
private:int _wfd;pid_t _subprocessid;std::string _name; // 信道名字
public:Channel(int wfd, pid_t id, const std::string name) // 构造函数: _wfd(wfd), _subprocessid(id), _name(name) // 构造函数初始化列表{}int getfd() { return _wfd; }int getid() { return _subprocessid; }std::string getname() { return _name; }~Channel() // 析构函数{}
};// ./processpool 5
int main(int argc, char *argv[])
{std::vector<Channel> channels;if (argc != 2) // 说明不用创建子进程,不会用{std::cerr << "usage: " << argv[0] << "processnum" << std::endl;return 1;}int num = std::stoi(argv[1]); // 表明需要创建几个子进程,stoi转化整数for(int i=0;i<num;i++){int pipefd[2];int n=pipe(pipefd);//创建管道if(n<0)exit(1);//创建管道失败,那么就无需与子进程通信了pid_t id=fork();//创建子进程if(id==0){//子进程不用创建子进程,只需父进程即可//child --rclose(pipefd[1]);work(pipefd[0]);//进行工作exit(0);}//farther --wclose(pipefd[0]);//a.此时父进程已经有了子进程的pid b.父进程的w端 std::string channel_name="channel-"+std::to_string(i);//构建一个channel名称channels.push_back(Channel(pipefd[1],id,channel_name));}// testfor (auto &channel : channels){std::cout << "***************************************" << std::endl;std::cout << channel.getname() << std::endl; // 取出std::cout << channel.getid() << std::endl; // 取出std::cout << channel.getfd() << std::endl; // 取出}return 0;
}

完整代码
#include <iostream>
#include <unistd.h>
#include <string>
#include <vector>
#include <sys/types.h>
#include "task.hpp"void work(int rfd)
{while (true){int command = 0;int n = read(rfd, &command, sizeof(command)); // 父进程写了一个整数,那么我也读一个整数if (n == sizeof(int)){std::cout<<"pid is "<<getpid()<<"chuli task"<<std::endl;excuttask(command); // 执行}}
}// master
class Channel
{
private:int _wfd;pid_t _subprocessid;std::string _name; // 信道名字
public:Channel(int wfd, pid_t id, const std::string name) // 构造函数: _wfd(wfd), _subprocessid(id), _name(name) // 构造函数初始化列表{}int getfd() { return _wfd; }int getid() { return _subprocessid; }std::string getname() { return _name; }~Channel() // 析构函数{}
};// 形参类型和命名规范
// const & :输出
// & :输入输出型参数
// * :输出型参数
void creatchannelandsun(int num, std::vector<Channel> *channels)
{for (int i = 0; i < num; i++){int pipefd[2];int n = pipe(pipefd); // 创建管道if (n < 0)exit(1); // 创建管道失败,那么就无需与子进程通信了pid_t id = fork(); // 创建子进程if (id == 0){ // 子进程不用创建子进程,只需父进程即可// child --rclose(pipefd[1]);work(pipefd[0]); // 进行工作exit(0);}// farther --wclose(pipefd[0]);// a.此时父进程已经有了子进程的pid b.父进程的w端std::string channel_name = "channel-" + std::to_string(i); // 构建一个channel名称channels->push_back(Channel(pipefd[1], id, channel_name));}
}int nextchannel(int channelnum)
{ // 形成一个0 1 ...到channelnum的编号static int next = 0;int channel = next;next++;next %= channelnum;return channel;
}void sendtask(Channel &channel, int taskcommand)
{write(channel.getfd(), &taskcommand, sizeof(taskcommand)); // 写
}// ./processpool 5
int main(int argc, char *argv[])
{if (argc != 2) // 说明不用创建子进程,不会用{std::cerr << "usage: " << argv[0] << "processnum" << std::endl;return 1;}int num = std::stoi(argv[1]); // 表明需要创建几个子进程,stoi转化整数inittask(); // 装载任务std::vector<Channel> channels;creatchannelandsun(num, &channels); // 创建信道和子进程// 通过channel控制子进程while (true){sleep(1);//每隔一秒发布一个// 第一步选择一个任务 int taskcommand = selecttask();// 第二步选择一个信道和进程,其实是在vector中选择int index_channel = nextchannel(channels.size());// 第三步发送任务sendtask(channels[index_channel], taskcommand);std::cout<<std::endl;
std::cout<<"taskcommand: "<<taskcommand<<" channel: "<<channels[index_channel].getname()<<" subprocess: "<<channels[index_channel].getid()<<std::endl;}return 0;
}
//以前我们都是用.h表示头文件声明,.cpp表示实现
//那么我们用.hpp也是c++的一种头文件,他允许将声明和实现和在一个文件里,那么就有一个好处,像这种代码无法形成库,即使形成库也是开源形成的
#pragma once
#include<iostream>
#include<ctime>
#include<stdlib.h>
#include<unistd.h>
#include <sys/types.h>#define tasknum 3typedef void (*task_t)();//task_t 返回值为void,参数为空的函数指针void print(){//三个任务列表std::cout<<"i am a printf task"<<std::endl;
}
void download(){std::cout<<"i am a download task"<<std::endl;
}
void flush(){std::cout<<"i am a flush task"<<std::endl;
}task_t tasks[tasknum];//函数指针数组void inittask(){//初始化任务srand(time(nullptr)^getpid());//种时间和pid随机种子//srand(seed): 这个函数用于用指定的 seed(种子)初始化随机数生成器。//seed 的值决定了 rand() 生成的随机数序列。如果使用相同的种子值,每次生成的随机数序列都是一样的。//time(nullptr) 提供了一个基于当前时间的种子值。getpid() 提供了一个进程的唯一标识符。//用异或操作符 ^ 将这两个值混合在一起,产生一个更为变化的种子值。tasks[0]=print;tasks[1]=download;tasks[2]=flush;
}void excuttask(int n){//执行任务if(n<0||n>2)return;tasks[n]();//调用
}int selecttask(){//随机选择任务return rand()%tasknum;
}

完善代码
#include <iostream>
#include <unistd.h>
#include <string>
#include <vector>
#include<sys/wait.h>
#include <sys/types.h>
#include "task.hpp"void work(int rfd)
{while (true){int command = 0;int n = read(rfd, &command, sizeof(command)); // 父进程写了一个整数,那么我也读一个整数if (n == sizeof(int)){std::cout<<"pid is "<<getpid()<<"chuli task"<<std::endl;excuttask(command); // 执行}else if(n==0){std::cout<<"sub process: "<<getpid()<<" quit"<<std::endl;break;}}
}// master
class Channel
{
private:int _wfd;pid_t _subprocessid;std::string _name; // 信道名字
public:Channel(int wfd, pid_t id, const std::string name) // 构造函数: _wfd(wfd), _subprocessid(id), _name(name) // 构造函数初始化列表{}int getfd() { return _wfd; }int getid() { return _subprocessid; }std::string getname() { return _name; }void closechannel(){//关闭文件描述符close(_wfd);}void wait(){pid_t n=waitpid(_subprocessid,nullptr,0);if(n>0){std::cout<<"wait "<<n<<" success"<<std::endl;}}~Channel() // 析构函数{}
};// 形参类型和命名规范
// const & :输出
// & :输入输出型参数
// * :输出型参数
void creatchannelandsun(int num, std::vector<Channel> *channels)
{for (int i = 0; i < num; i++){int pipefd[2];int n = pipe(pipefd); // 创建管道if (n < 0)exit(1); // 创建管道失败,那么就无需与子进程通信了pid_t id = fork(); // 创建子进程if (id == 0){ // 子进程不用创建子进程,只需父进程即可// child --rclose(pipefd[1]);work(pipefd[0]); // 进行工作exit(0);}// farther --wclose(pipefd[0]);// a.此时父进程已经有了子进程的pid b.父进程的w端std::string channel_name = "channel-" + std::to_string(i); // 构建一个channel名称channels->push_back(Channel(pipefd[1], id, channel_name));}
}int nextchannel(int channelnum)
{ // 形成一个0 1 ...到channelnum的编号static int next = 0;int channel = next;next++;next %= channelnum;return channel;
}void sendtask(Channel &channel, int taskcommand)
{write(channel.getfd(), &taskcommand, sizeof(taskcommand)); // 写
}void ctrlprocessonce(std::vector<Channel> &channels){//只做一次任务sleep(1);//每隔一秒发布一个// 第一步选择一个任务 int taskcommand = selecttask();// 第二步选择一个信道和进程,其实是在vector中选择int index_channel = nextchannel(channels.size());// 第三步发送任务sendtask(channels[index_channel], taskcommand);std::cout<<std::endl;
std::cout<<"taskcommand: "<<taskcommand<<" channel: "<<channels[index_channel].getname()<<" subprocess: "<<channels[index_channel].getid()<<std::endl;
}void ctrlprocess(std::vector<Channel> &channels,int times=-1){if(times>0){//固定次数while(times--){//根据times控制ctrlprocessonce(channels);}}else{//缺省一直while(true){//一直控制ctrlprocessonce(channels);}}
}// ./processpool 5
int main(int argc, char *argv[])
{if (argc != 2) // 说明不用创建子进程,不会用{std::cerr << "usage: " << argv[0] << "processnum" << std::endl;return 1;}int num = std::stoi(argv[1]); // 表明需要创建几个子进程,stoi转化整数inittask(); // 装载任务std::vector<Channel> channels;creatchannelandsun(num, &channels); // 创建信道和子进程// 通过channel控制子进程ctrlprocess(channels,10);//控制10次退出//回收进程,把写端关闭那么所有子进程读到0就break退出了for(auto &channel:channels){channel.closechannel();}//注意进程回收,则遍历关闭for(auto &channel:channels){channel.wait();}//如果不等待那么子进程就是僵尸进程了return 0;
}
//以前我们都是用.h表示头文件声明,.cpp表示实现
//那么我们用.hpp也是c++的一种头文件,他允许将声明和实现和在一个文件里,那么就有一个好处,像这种代码无法形成库,即使形成库也是开源形成的
#pragma once
#include<iostream>
#include<ctime>
#include<stdlib.h>
#include<unistd.h>
#include <sys/types.h>#define tasknum 3typedef void (*task_t)();//task_t 返回值为void,参数为空的函数指针void print(){//三个任务列表std::cout<<"i am a printf task"<<std::endl;
}
void download(){std::cout<<"i am a download task"<<std::endl;
}
void flush(){std::cout<<"i am a flush task"<<std::endl;
}task_t tasks[tasknum];//函数指针数组void inittask(){//初始化任务srand(time(nullptr)^getpid());//种时间和pid随机种子//srand(seed): 这个函数用于用指定的 seed(种子)初始化随机数生成器。//seed 的值决定了 rand() 生成的随机数序列。如果使用相同的种子值,每次生成的随机数序列都是一样的。//time(nullptr) 提供了一个基于当前时间的种子值。getpid() 提供了一个进程的唯一标识符。//用异或操作符 ^ 将这两个值混合在一起,产生一个更为变化的种子值。tasks[0]=print;tasks[1]=download;tasks[2]=flush;
}void excuttask(int n){//执行任务if(n<0||n>2)return;tasks[n]();//调用
}int selecttask(){//随机选择任务return rand()%tasknum;
}
回收子进程:
不回收子进程:
子进程使用重定向优化
#include <iostream>
#include <unistd.h>
#include <string>
#include <vector>
#include<sys/wait.h>
#include <sys/types.h>
#include "task.hpp"// void work(int rfd)//执行任务工作
// {
// while (true)
// {
// int command = 0;
// int n = read(rfd, &command, sizeof(command)); // 父进程写了一个整数,那么我也读一个整数
// if (n == sizeof(int))
// {
// std::cout<<"pid is "<<getpid()<<"chuli task"<<std::endl;
// excuttask(command); // 执行
// }
// else if(n==0){
// std::cout<<"sub process: "<<getpid()<<" quit"<<std::endl;
// break;
// }
// }
// }void work()//执行任务工作
{while (true){int command = 0;int n = read(0, &command, sizeof(command)); // 父进程写了一个整数,那么我也读一个整数//此时从标准输入去读,没有管道的概念了,对于子进程来说有人通过标准输入将任务给你if (n == sizeof(int)){std::cout<<"pid is "<<getpid()<<"chuli task"<<std::endl;excuttask(command); // 执行}else if(n==0){std::cout<<"sub process: "<<getpid()<<" quit"<<std::endl;break;}}
}// master
class Channel
{
private:int _wfd;pid_t _subprocessid;std::string _name; // 信道名字
public:Channel(int wfd, pid_t id, const std::string name) // 构造函数: _wfd(wfd), _subprocessid(id), _name(name) // 构造函数初始化列表{}int getfd() { return _wfd; }int getid() { return _subprocessid; }std::string getname() { return _name; }void closechannel(){//关闭文件描述符close(_wfd);}void wait(){pid_t n=waitpid(_subprocessid,nullptr,0);if(n>0){std::cout<<"wait "<<n<<" success"<<std::endl;}}~Channel() // 析构函数{}
};// 形参类型和命名规范
// const & :输出
// & :输入输出型参数
// * :输出型参数
void creatchannelandsun(int num, std::vector<Channel> *channels)
{for (int i = 0; i < num; i++){int pipefd[2];int n = pipe(pipefd); // 创建管道if (n < 0)exit(1); // 创建管道失败,那么就无需与子进程通信了pid_t id = fork(); // 创建子进程if (id == 0){ // 子进程不用创建子进程,只需父进程即可// child --rclose(pipefd[1]);dup2(pipefd[0],0);//将管道的读端,重定向到标准输入//本来应该在管道的读端读任务,现在做重定向,把0号文件描述符指向管道的读端work(); // 进行工作,此时不用传参exit(0);}// farther --wclose(pipefd[0]);// a.此时父进程已经有了子进程的pid b.父进程的w端std::string channel_name = "channel-" + std::to_string(i); // 构建一个channel名称channels->push_back(Channel(pipefd[1], id, channel_name));}
}int nextchannel(int channelnum)//选定一个管道
{ // 形成一个0 1 ...到channelnum的编号static int next = 0;int channel = next;next++;next %= channelnum;return channel;
}void sendtask(Channel &channel, int taskcommand)//派发什么任务
{write(channel.getfd(), &taskcommand, sizeof(taskcommand)); // 写
}void ctrlprocessonce(std::vector<Channel> &channels){//只做一次任务sleep(1);//每隔一秒发布一个// 第一步选择一个任务 int taskcommand = selecttask();// 第二步选择一个信道和进程,其实是在vector中选择int index_channel = nextchannel(channels.size());// 第三步发送任务sendtask(channels[index_channel], taskcommand);std::cout<<std::endl;
std::cout<<"taskcommand: "<<taskcommand<<" channel: "<<channels[index_channel].getname()<<" subprocess: "<<channels[index_channel].getid()<<std::endl;
}void ctrlprocess(std::vector<Channel> &channels,int times=-1){//控制派发任务次数if(times>0){//固定次数while(times--){//根据times控制ctrlprocessonce(channels);}}else{//缺省一直while(true){//一直控制ctrlprocessonce(channels);}}
}// ./processpool 5
int main(int argc, char *argv[])
{if (argc != 2) // 说明不用创建子进程,不会用{std::cerr << "usage: " << argv[0] << "processnum" << std::endl;return 1;}int num = std::stoi(argv[1]); // 表明需要创建几个子进程,stoi转化整数inittask(); // 装载任务std::vector<Channel> channels;creatchannelandsun(num, &channels); // 创建信道和子进程// 通过channel控制子进程ctrlprocess(channels,10);//控制10次退出//或者ctrlprocess(channels);//使用缺省参数一直控制//回收进程,把写端关闭那么所有子进程读到0就break退出了for(auto &channel:channels){channel.closechannel();}//注意进程回收,则遍历关闭for(auto &channel:channels){channel.wait();}//如果不等待那么子进程就是僵尸进程了return 0;
}
//以前我们都是用.h表示头文件声明,.cpp表示实现
//那么我们用.hpp也是c++的一种头文件,他允许将声明和实现和在一个文件里,那么就有一个好处,像这种代码无法形成库,即使形成库也是开源形成的
#pragma once
#include<iostream>
#include<ctime>
#include<stdlib.h>
#include<unistd.h>
#include <sys/types.h>#define tasknum 3typedef void (*task_t)();//task_t 返回值为void,参数为空的函数指针void print(){//三个任务列表std::cout<<"i am a printf task"<<std::endl;
}
void download(){std::cout<<"i am a download task"<<std::endl;
}
void flush(){std::cout<<"i am a flush task"<<std::endl;
}task_t tasks[tasknum];//函数指针数组void inittask(){//初始化任务srand(time(nullptr)^getpid());//种时间和pid随机种子//srand(seed): 这个函数用于用指定的 seed(种子)初始化随机数生成器。//seed 的值决定了 rand() 生成的随机数序列。如果使用相同的种子值,每次生成的随机数序列都是一样的。//time(nullptr) 提供了一个基于当前时间的种子值。getpid() 提供了一个进程的唯一标识符。//用异或操作符 ^ 将这两个值混合在一起,产生一个更为变化的种子值。tasks[0]=print;tasks[1]=download;tasks[2]=flush;
}void excuttask(int n){//执行任务if(n<0||n>2)return;tasks[n]();//调用
}int selecttask(){//随机选择任务return rand()%tasknum;
}
那么有个问题?为什么不能关闭一个再等待
为什么呢?这是一个bug


为什么全部关闭后再挨个等待就可以呢?
假如有10个子进程,那么第一个子进程有一个读端10个写端,第二个子进程9个写端1个读端,最后一个进程1个读端1个写端;
如果我们把它全部关完了,那么此时他从上往下遍历,管道最后只有最后一个会释放,那么它对应的上一个管道的写端也会释放,所以递归式的逆向关闭;
需要注意的是函数名就是地址;
优化:
#include <iostream>
#include <unistd.h>
#include <string>
#include <vector>
#include<sys/wait.h>
#include <sys/types.h>
#include "task.hpp"// void work(int rfd)//执行任务工作
// {
// while (true)
// {
// int command = 0;
// int n = read(rfd, &command, sizeof(command)); // 父进程写了一个整数,那么我也读一个整数
// if (n == sizeof(int))
// {
// std::cout<<"pid is "<<getpid()<<"chuli task"<<std::endl;
// excuttask(command); // 执行
// }
// else if(n==0){
// std::cout<<"sub process: "<<getpid()<<" quit"<<std::endl;
// break;
// }
// }
// }void work()//执行任务工作
{while (true){int command = 0;int n = read(0, &command, sizeof(command)); // 父进程写了一个整数,那么我也读一个整数//此时从标准输入去读,没有管道的概念了,对于子进程来说有人通过标准输入将任务给你if (n == sizeof(int)){std::cout<<"pid is "<<getpid()<<"chuli task"<<std::endl;excuttask(command); // 执行}else if(n==0){std::cout<<"sub process: "<<getpid()<<" quit"<<std::endl;break;}}
}// master
class Channel
{
private:int _wfd;pid_t _subprocessid;std::string _name; // 信道名字
public:Channel(int wfd, pid_t id, const std::string name) // 构造函数: _wfd(wfd), _subprocessid(id), _name(name) // 构造函数初始化列表{}int getfd() { return _wfd; }int getid() { return _subprocessid; }std::string getname() { return _name; }void closechannel(){//关闭文件描述符close(_wfd);}void wait(){pid_t n=waitpid(_subprocessid,nullptr,0);if(n>0){std::cout<<"wait "<<n<<" success"<<std::endl;}}~Channel() // 析构函数{}
};// 形参类型和命名规范
// const & :输出
// & :输入输出型参数
// * :输出型参数
void creatchannelandsun(int num, std::vector<Channel> *channels)
{for (int i = 0; i < num; i++){int pipefd[2];int n = pipe(pipefd); // 创建管道if (n < 0)exit(1); // 创建管道失败,那么就无需与子进程通信了pid_t id = fork(); // 创建子进程if (id == 0){ // 子进程不用创建子进程,只需父进程即可if(!channels->empty()){//表示第二次之后创建管道for(auto &channel :*channels){//遍历channel.closechannel();//关掉//第一次创建好后channel为空什么也不做,因为还没入栈//例如我是第八次创建,那么我的channel保存的是前七个的写端,那么我就把前七个关掉,关闭的是子进程的没关父进程的}}// child --rclose(pipefd[1]);dup2(pipefd[0],0);//将管道的读端,重定向到标准输入//本来应该在管道的读端读任务,现在做重定向,把0号文件描述符指向管道的读端work(); // 进行工作,此时不用传参exit(0);}// farther --wclose(pipefd[0]);// a.此时父进程已经有了子进程的pid b.父进程的w端std::string channel_name = "channel-" + std::to_string(i); // 构建一个channel名称channels->push_back(Channel(pipefd[1], id, channel_name));//}
}int nextchannel(int channelnum)//选定一个管道
{ // 形成一个0 1 ...到channelnum的编号static int next = 0;int channel = next;next++;next %= channelnum;return channel;
}void sendtask(Channel &channel, int taskcommand)//派发什么任务
{write(channel.getfd(), &taskcommand, sizeof(taskcommand)); // 写
}void ctrlprocessonce(std::vector<Channel> &channels){//只做一次任务sleep(1);//每隔一秒发布一个// 第一步选择一个任务 int taskcommand = selecttask();// 第二步选择一个信道和进程,其实是在vector中选择int index_channel = nextchannel(channels.size());// 第三步发送任务sendtask(channels[index_channel], taskcommand);std::cout<<std::endl;
std::cout<<"taskcommand: "<<taskcommand<<" channel: "<<channels[index_channel].getname()<<" subprocess: "<<channels[index_channel].getid()<<std::endl;
}void ctrlprocess(std::vector<Channel> &channels,int times=-1){//控制派发任务次数if(times>0){//固定次数while(times--){//根据times控制ctrlprocessonce(channels);}}else{//缺省一直while(true){//一直控制ctrlprocessonce(channels);}}
}// ./processpool 5
int main(int argc, char *argv[])
{if (argc != 2) // 说明不用创建子进程,不会用{std::cerr << "usage: " << argv[0] << "processnum" << std::endl;return 1;}int num = std::stoi(argv[1]); // 表明需要创建几个子进程,stoi转化整数inittask(); // 装载任务std::vector<Channel> channels;creatchannelandsun(num, &channels); // 创建信道和子进程// 通过channel控制子进程ctrlprocess(channels,10);//控制10次退出//或者ctrlprocess(channels);//使用缺省参数一直控制//回收进程,把写端关闭那么所有子进程读到0就break退出了// int nuum=channels.size()-1;// while(nuum>=0){// channels[nuum].closechannel();// channels[nuum--].wait();// }for(auto &channel:channels){channel.closechannel();channel.wait();}//注意进程回收,则遍历关闭//for(auto &channel:channels){// channel.wait();//}//如果不等待那么子进程就是僵尸进程了return 0;
}
//以前我们都是用.h表示头文件声明,.cpp表示实现
//那么我们用.hpp也是c++的一种头文件,他允许将声明和实现和在一个文件里,那么就有一个好处,像这种代码无法形成库,即使形成库也是开源形成的
#pragma once
#include<iostream>
#include<ctime>
#include<stdlib.h>
#include<unistd.h>
#include <sys/types.h>#define tasknum 3typedef void (*task_t)();//task_t 返回值为void,参数为空的函数指针void print(){//三个任务列表std::cout<<"i am a printf task"<<std::endl;
}
void download(){std::cout<<"i am a download task"<<std::endl;
}
void flush(){std::cout<<"i am a flush task"<<std::endl;
}task_t tasks[tasknum];//函数指针数组void inittask(){//初始化任务srand(time(nullptr)^getpid());//种时间和pid随机种子//srand(seed): 这个函数用于用指定的 seed(种子)初始化随机数生成器。//seed 的值决定了 rand() 生成的随机数序列。如果使用相同的种子值,每次生成的随机数序列都是一样的。//time(nullptr) 提供了一个基于当前时间的种子值。getpid() 提供了一个进程的唯一标识符。//用异或操作符 ^ 将这两个值混合在一起,产生一个更为变化的种子值。tasks[0]=print;tasks[1]=download;tasks[2]=flush;
}void excuttask(int n){//执行任务if(n<0||n>2)return;tasks[n]();//调用
}int selecttask(){//随机选择任务return rand()%tasknum;
}

相关文章:
进程间通信-进程池
目录 理解 完整代码 完善代码 回收子进程: 不回收子进程: 子进程使用重定向优化 理解 #include <iostream> #include <unistd.h> #include <string> #include <vector> #include <sys/types.h>void work(int rfd) {…...
【PYTHON 基础系列-request 模块介绍】
一、requests库简介 使用requests库能快速构建 HTTP 请求,而无需深入了解底层网络协议细节。其API设计直观,使得发送请求就像调用函数一样简单,同时提供了丰富的选项以满足复杂网络交互的需求。这种设计使得无论是初学者还是经验丰富的开发者…...
springboot 实现策略模式通过id进入不同的服务类service
在Spring Boot中实现策略模式,通常是将不同的算法封装在单独的类中,并使它们可以相互替换。这些类通常都实现同一个接口。在Spring Boot应用中,你可以通过Spring的依赖注入(DI)来管理这些策略类的实例,并通…...
AUC真的什么情形下都适合吗
AUC(Area Under the ROC Curve)是一个广泛使用的性能评价指标,它衡量了分类模型在不同阈值下区分正类和负类的能力。然而,在某些情况下,AUC可能不是最准确的评价指标,以下是几种可能的情况: 数据极度不均衡:在数据极度不均衡的情况下,即使模型只预测多数类,AUC也可能…...
Flutter基本组件Text使用
Text是一个文本显示控件,用于在应用程序界面中显示单行或多行文本内容。 Text简单Demo import package:flutter/material.dart;class MyTextDemo extends StatelessWidget {const MyTextDemo({super.key});overrideWidget build(BuildContext context) {return Sca…...
DDS基本原理--FPGA学习笔记
DDS信号发生器原理: timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2024/09/04 15:20:30 // Design Name: hilary // Module Name: DDS_Module //module DDS_Module(Clk,Reset_n,Fword,Pword,Data);input Clk;input Reset_n;input [31:0]…...
有temp表包含A,B两列,使用SQL,对B列进行处理,形成C列,按A列顺序,B列值不变,则C列累计技术,B列值变化,则C列重新开始计数
有temp表,使用SQL,对B列进行处理,形成C列,按A列顺序,B列值不变,则C列累计技术,B列值变化,则C列重新开始计数 建表语句如下 CREATE TABLE temp(A STRING ,B STRING );INSERT INTO …...
【H2O2|全栈】关于HTML(6)HTML基础(五 · 完结篇)
HTML基础知识 目录 HTML基础知识 前言 准备工作 标签的具体分类(五) 本文中的标签在什么位置中使用? 表单(二) 下拉选择菜单 文本域 案例 拓展标签 iframe框架 案例 预告和回顾 后话 前言 本系列博客介…...
2024第三届大学生算法大赛 真题训练一 解题报告 | 珂学家
前言 题解 这是第三届大学生算法大赛(第二届为清华社杯)的赛前练习赛一. 这是上界比赛的体验报告: 2023第二届“清华社杯”大学生算法大赛 解题报告(流水账版) | 珂学家,个人还是非常推荐这个比赛。 难度分布:4 easy/4 mid-hard/2 hard 赛前练习赛一…...
IIS网站允许3D模型类型的文件
参与threejs项目的研发,本地开发完成后,发布后使用时发现模型文件不能正常获取资源,原因是IIS站点默认不支持模型类型。 一开始是通过直接在IIS网站管理中的类型添加来实现网站对类型的支持。 后来发现一段对于后端来说可以直接实现代码上添加…...
Linux 性能调优之CPU上下文切换
写在前面 博文内容为 Linux 性能指标 CPU 上下文切换认知内容涉及: 上下文认知,发生上下文切换的场景有哪些上下文指标信息查看,内核上下文切换事件跟踪,系统上下文切换统计上下文异常场景分析,CPU亲和性配置优化上下文…...
【无标题】符文价值的退化页
我们利用现有的符文体系建立了一个健全的符文扩展空间,可假若符文让我们感到十分困惑,我们不介意毁灭它们,让一切回到没有字迹的蛮荒纪。 如此,眼睛也失去了作用。我们的成GUO也会给后来者提供又是一DUI 令人眼花缭乱的无用符咒。…...
DFS 算法:洛谷B3625迷宫寻路
我的个人主页 {\large \mathsf{{\color{Red} 我的个人主页} } } 我的个人主页 往 {\color{Red} {\Huge 往} } 往 期 {\color{Green} {\Huge 期} } 期 文 {\color{Blue} {\Huge 文} } 文 章 {\color{Orange} {\Huge 章}} 章 DFS 算法:记忆化搜索DFS 算法…...
结构开发笔记(七):solidworks软件(六):装配摄像头、摄像头座以及螺丝,完成摄像头结构示意图
若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/141931518 长沙红胖子Qt(长沙创微智科)博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV…...
Android 15 新特性快速解读指南
核心要点 16K 页面大小支持目前作为开发人员选项提供,并非强制要求。 引入多项提升开发体验、多语言支持、多媒体功能、交互体验和隐私安全的更新。 重点关注前台服务限制、Window Insets 行为变化、AndroidManifest 文件限制等适配要求。 开发体验 ApplicationS…...
【机器人工具箱Robotics Toolbox开发笔记(十九)】机器人工具箱Link类函数参数说明
机器人工具箱中的Link对象保存于机器人连杆相关的所有信息,如运动学参数、刚体惯性参数、电机和传动参数等。 与Link对象有关参数如表1所示。 表1 Link对象参数 参 数 意 义 参 数 意 义 A 连杆变换矩阵 islimit 测试关节是否超过软限制 RP RP关节类型 isrevo…...
排查SQL Server中的内存不足及其他疑难问题
文章目录 引言I DMV 资源信号灯资源信号灯 DMV sys.dm_exec_query_resource_semaphores( 确定查询执行内存的等待)查询性能计数器什么是内存授予?II DBCC MEMORYSTATUS 查询内存对象III DBCC 命令释放多个 SQL Server 内存缓存 - 临时度量值IV 等待资源池 %ls (%ld)中的内存…...
输送线相机拍照信号触发(博途PLC高速计数器中断立即输出应用)
博途PLC相关中断应用请参考下面文章链接: T法测速功能块 T法测速功能块(博途PLC上升沿中断应用)-CSDN博客文章浏览阅读165次。本文介绍了博途PLC中T法测速的原理和应用,包括如何开启上升沿中断、配置中断以及T法测速功能块的使用。重点讲述了在中断事件发生后执行的功能块处…...
【数学分析笔记】第3章第1节 函数极限(6)
3. 函数极限与连续函数 3.1 函数极限 【例3.1.12】 f ( x ) a n x n a n − 1 x n − 1 ⋯ a k x k b m x m b m − 1 x m − 1 ⋯ b j x j , b m , b j ≠ 0 , a n , a k ≠ 0 f(x) \frac{a_{n} x^{n}a_{n-1} x^{n-1}\cdotsa_{k} x^{k}}{b_{m} x^{m}b_{m-1} x^{m-1}\…...
程序员如何写笔记?
word。没错,我也看了网上一大堆软件,还有git管理等等。个人认为如果笔记只是记录个人的经验积累,一个word就够了,那些notepad,laTex个人觉得不够简练。word。 1.word可以插入任何文件附件(目前最大的word 200MB也没出现…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...
Qt 事件处理中 return 的深入解析
Qt 事件处理中 return 的深入解析 在 Qt 事件处理中,return 语句的使用是另一个关键概念,它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别:不同层级的事件处理 方…...
零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程
STM32F1 本教程使用零知标准板(STM32F103RBT6)通过I2C驱动ICM20948九轴传感器,实现姿态解算,并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化,适合嵌入式及物联网开发者。在基础驱动上新增…...
【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验
Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...
倒装芯片凸点成型工艺
UBM(Under Bump Metallization)与Bump(焊球)形成工艺流程。我们可以将整张流程图分为三大阶段来理解: 🔧 一、UBM(Under Bump Metallization)工艺流程(黄色区域ÿ…...
DAY 45 超大力王爱学Python
来自超大力王的友情提示:在用tensordoard的时候一定一定要用绝对位置,例如:tensorboard --logdir"D:\代码\archive (1)\runs\cifar10_mlp_experiment_2" 不然读取不了数据 知识点回顾: tensorboard的发展历史和原理tens…...

