Linux--构建进程池

目录

 1.进程池

1.1.我们先完成第一步,创建子进程和信道

1.2. 通过channel控制,发送任务

1.3回收管道和子进程

1.4进行测试

1.5完整代码


 1.进程池

进程池其产生原因主要是为了优化大量任务需要多进程完成时频繁创建和删除进程所带来的资源消耗,以及更好的实现多个进程间的协同。以下是关于进程池的一些关键点:

  1. 组成:进程池技术主要由两部分组成:资源进程和管理进程。资源进程是预先创建好的空闲进程,等待管理进程分发任务(maste向哪个管道写入,就是唤醒哪一个子进程处理任务)。管理进程则负责创建这些资源进程,将工作分配给空闲的资源进程处理,并在工作完成后回收这些资源进程。(父进程要进行后端任务的负载均衡)
  2. 管理:管理进程如何有效地管理资源进程是关键。这涉及到分配任务给资源进程、回收空闲资源进程等操作。管理进程和资源进程之间需要进行交互,这种交互可以通过管道(需要子进程执行一个任务我们就可以派发一个管道,连接一个子进程,执行任务)
  3. 使用场景:当我们需要并行的处理大规模任务时,比如服务器处理大量客户端的任务,进程池是一个很好的选择。它可以避免频繁创建和销毁进程的开销,提高应用的响应速度。
  4. 优化:使用进程池可以避免动态创建和销毁进程的开销,提高应用的性能和稳定性。此外,进程池还可以根据任务的执行情况尽量减少创建的进程数量,最多创建指定个数的进程。

代码实现:

        实现进程池首先我们需要提前准备好一批数量的进程,当一个任务队列被提交到线程池,每个工作进程都会不断从任务队列中取出任务并执行。


1.1.我们先完成第一步,创建子进程和信道

        

        Channel类是来管理一个子进程和与之相关联的管道写端,这个Channel类就是信道。用于一个需要管理多个子进程和它们各自管道写端的程序中。例如,你可能有一个父进程,它创建多个子进程,每个子进程都通过管道与父进程通信。在这种情况下,你可以为每个子进程和它的管道写端创建一个Channel对象,并使用这些对象来管理它们。

class Channel
{
public:Channel(int wfd, pid_t id, const std::string &name): _wfd(wfd), _subprocessid(id), _name(name){}int GetWfd() { return _wfd; }pid_t GetProcessId() { return _subprocessid; }std::string GetName() { return _name; }void CloseChannel(){close(_wfd);}void Wait(){pid_t rid = waitpid(_subprocessid, nullptr, 0);if (rid > 0){std::cout << "wait " << rid << " success" << std::endl;}}~Channel(){}private:int _wfd;//写端fdpid_t _subprocessid;//子进程id'std::string _name;//一个信道的名称
};

        下面函数的主要逻辑是创建多个管道和相应的子进程,每个子进程都将从它自己的管道中读取数据,而父进程则保留管道的写端以便后续向子进程发送数据。同时,这个函数也维护了一个 Channel 对象的向量(std::vector<Channel>),其中每个 Channel 对象代表一个与特定子进程和管道写端相关联的通道。

        task_t是一个函数指针类型,是为了泛型,将来可以传任意不同的任务交给子进程做。

可以理解为回调函数 task 的使用降低了任务与子进程之间的耦合度。在传统的编程模型中,你可能会为每个子进程编写特定的代码块,这些代码块直接嵌入在创建子进程的函数中。这样做会导致代码的高耦合性,因为每个子进程的行为都紧密地绑定在创建它们的函数中。(关于task的应用见下文)

        dup2(pipefd[0], 0)将管道的读端,重定向到标准输入。意思就是管道的读端变成了标准输入,无需修改代码以处理文件描述符,因为以后任意的子进程去读取任务,都是从标准输入去读取的。

        在子进程中,它关闭了管道的写端,将读端重定向到标准输入,执行给定的回调函数 task,然后关闭读端并退出。

void CreateChannelAndSub(int num, std::vector<Channel> *channels, task_t task)
{// BUG? --> fix bugfor (int i = 0; i < num; i++){// 1. 创建管道int pipefd[2] = {0};int n = pipe(pipefd);if (n < 0)exit(1);// 2. 创建子进程pid_t id = fork();if (id == 0){if (!channels->empty()){// 第二次之后,开始创建的管道for(auto &channel : *channels) channel.CloseChannel();}// child - readclose(pipefd[1]);dup2(pipefd[0], 0); // 将管道的读端,重定向到标准输入task();close(pipefd[0]);exit(0);}// 3.构建一个channel名称std::string channel_name = "Channel-" + std::to_string(i);// 父进程close(pipefd[0]);// a. 子进程的pid b. 父进程关心的管道的w端channels->push_back(Channel(pipefd[1], id, channel_name));}
}

提供一个任务文件,接下来让子进程执行任务:

#pragma once#include <iostream>
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>#define TaskNum 3typedef void (*task_t)(); // task_t 函数指针类型void Print()
{std::cout << "I am print 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 LoadTask()
{srand(time(nullptr) ^ getpid() ^ 17777);tasks[0] = Print;tasks[1] = DownLoad;tasks[2] = Flush;
}void ExcuteTask(int number)
{if (number < 0 || number > 2)return;tasks[number]();
}int SelectTask()
{return rand() % TaskNum;
}void work()
{while (true){int command = 0;int n = read(0, &command, sizeof(command));if (n == sizeof(int)){std::cout << "pid is : " << getpid() << " handler task" << std::endl;ExcuteTask(command);}else if (n == 0){std::cout << "sub process : " << getpid() << " quit" << std::endl;break;}}
}
  • 初始化阶段:LoadTask()函数被调用,设置tasks函数指针数组以包含所有可用的任务函数,通过索引调用三个具体的任务函数:Print()DownLoad(), 和 Flush()。
  • 任务选择阶段:SelectTask()函数在需要时用于生成一个随机任务索引。
  • 任务执行阶段:在子进程中,work()函数不断监听标准输入,等待一个任务索引。一旦接收到索引,就调用ExcuteTask()来执行相应的任务。

子进程退出:当子进程从标准输入读取到0时,它知道应该退出循环并结束。


1.2. 通过channel控制,发送任务

int NextChannel(int channelnum)
{static int next = 0;int channel = next;next++;next %= channelnum;return channel;
}
void SendTaskCommand(Channel &channel, int taskcommand)
{write(channel.GetWfd(), &taskcommand, sizeof(taskcommand));
}
void ctrlProcessOnce(std::vector<Channel> &channels)
{sleep(1);// a. 选择一个任务int taskcommand = SelectTask();// b. 选择一个信道和进程int channel_index = NextChannel(channels.size());// c. 发送任务SendTaskCommand(channels[channel_index], taskcommand);std::cout << std::endl;std::cout << "taskcommand: " << taskcommand << " channel: "<< channels[channel_index].GetName() << " sub process: " << channels[channel_index].GetProcessId() << std::endl;
}
void ctrlProcess(std::vector<Channel> &channels, int times = -1)
{if (times > 0){while (times--){ctrlProcessOnce(channels);}}else{while (true){ctrlProcessOnce(channels);}}
}
  1.   NextChannel 函数是一个轮询选择器,用于在给定的通道数量(channelnum)中循环选择一个通道索引。它使用了一个静态变量 next 来跟踪上一次选择的索引,并在每次调用时递增这个索引。
  2.  SendTaskCommand 函数用于向指定的 Channel 对象(代表一个与子进程的通信通道)发送一个任务命令(taskcommand。它通过调用 Channel 对象的 GetWfd 方法获取管道的写端文件描述符,然后使用 write 系统调用来将任务命令写入管道。这样,子进程就可以从管道中读取这个命令并执行相应的任务。
  3.  ctrlProcessOnce 函数模拟了控制进程的一次操作。调用 NextChannel 函数来选择一个通道索引,并通过该索引从 channels 向量中获取一个 Channel 对象。然后,它调用 SendTaskCommand 函数向选定的通道发送任务命令。最后,它打印出相关的信息,包括任务命令、通道名称和子进程的进程ID,以便进行调试和监控。               
  4. ctrlProcess 函数是控制进程的主循环。它接受一个 channels 向量和一个可选的 times 参数。如果 times 大于0,则控制进程会循环执行 ctrlProcessOnce 函数 times 次;如果 times 小于或等于0(默认为-1),则控制进程会无限循环地执行 ctrlProcessOnce 函数

1.3回收管道和子进程

class Channel
{
public:Channel(int wfd, pid_t id, const std::string &name): _wfd(wfd), _subprocessid(id), _name(name){}int GetWfd() { return _wfd; }pid_t GetProcessId() { return _subprocessid; }std::string GetName() { return _name; }void CloseChannel(){close(_wfd);}void Wait(){pid_t rid = waitpid(_subprocessid, nullptr, 0);if (rid > 0){std::cout << "wait " << rid << " success" << std::endl;}}~Channel(){}private:int _wfd;//写端fdpid_t _subprocessid;//子进程id'std::string _name;//一个信道的名称
};void CleanUpChannel(std::vector<Channel> &channels)
{for (auto &channel : channels){channel.CloseChannel();}for (auto &channel : channels){channel.Wait();}
}

a. 关闭所有的写端 b. 回收子进程

注意:一定是先关闭全部读端,然后统一读端,而不能关一个,等待一个。

在执行子进程创建逻辑的时候,是存在bug的。

我们在创建第一个信道的时候是没有什么问题的:

接着来看,我们创建第二个信道会发生什么:fd[4]的指向被第二个信道的子进程继承了,那么第一个管道的写端就个fd指向了,这就是坑

如果继续创建更多的信道,那么这种情况会一直累积,只有最后一个文件只有一个写端。如果我们是关一个等待一个,那么第一个信道,只会关闭一个读端,但还有其它的读端,这就会发生进程阻塞,导致管道文件一直在等待读取,无法被释放;如果统一的关闭读端,那么当关闭到最后一个信道的时候,最后一个信道读端只有一个,管道文件会被释放,然后就会递归式的逆向关闭其它信道的所有读端,最后将所有的管道文件释放,父进程只需要一 一等待,就能回收所有子进程了。


1.4进行测试

子进程执行任务成功,父进程回收子进程成功。


1.5完整代码

Task.hpp

#pragma once#include <iostream>
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>#define TaskNum 3typedef void (*task_t)(); // task_t 函数指针类型void Print()
{std::cout << "I am print 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 LoadTask()
{srand(time(nullptr) ^ getpid() ^ 17777);tasks[0] = Print;tasks[1] = DownLoad;tasks[2] = Flush;
}void ExcuteTask(int number)
{if (number < 0 || number > 2)return;tasks[number]();
}int SelectTask()
{return rand() % TaskNum;
}void work()
{while (true){int command = 0;int n = read(0, &command, sizeof(command));if (n == sizeof(int)){std::cout << "pid is : " << getpid() << " handler task" << std::endl;ExcuteTask(command);}else if (n == 0){std::cout << "sub process : " << getpid() << " quit" << std::endl;break;}}
}

test.cc

#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"class Channel
{
public:Channel(int wfd, pid_t id, const std::string &name): _wfd(wfd), _subprocessid(id), _name(name){}int GetWfd() { return _wfd; }pid_t GetProcessId() { return _subprocessid; }std::string GetName() { return _name; }void CloseChannel(){close(_wfd);}void Wait(){pid_t rid = waitpid(_subprocessid, nullptr, 0);if (rid > 0){std::cout << "wait " << rid << " success" << std::endl;}}~Channel(){}private:int _wfd;pid_t _subprocessid;std::string _name;
};//  task_t task: 回调函数
void CreateChannelAndSub(int num, std::vector<Channel> *channels, task_t task)
{// BUG? --> fix bugfor (int i = 0; i < num; i++){// 1. 创建管道int pipefd[2] = {0};int n = pipe(pipefd);if (n < 0)exit(1);// 2. 创建子进程pid_t id = fork();if (id == 0){if (!channels->empty()){// 第二次之后,开始创建的管道for(auto &channel : *channels) channel.CloseChannel();}// child - readclose(pipefd[1]);dup2(pipefd[0], 0); // 将管道的读端,重定向到标准输入task();close(pipefd[0]);exit(0);}// 3.构建一个channel名称std::string channel_name = "Channel-" + std::to_string(i);// 父进程close(pipefd[0]);// a. 子进程的pid b. 父进程关心的管道的w端channels->push_back(Channel(pipefd[1], id, channel_name));}
}int NextChannel(int channelnum)
{static int next = 0;int channel = next;next++;next %= channelnum;return channel;
}void SendTaskCommand(Channel &channel, int taskcommand)
{write(channel.GetWfd(), &taskcommand, sizeof(taskcommand));
}
void ctrlProcessOnce(std::vector<Channel> &channels)
{sleep(1);// a. 选择一个任务int taskcommand = SelectTask();// b. 选择一个信道和进程int channel_index = NextChannel(channels.size());// c. 发送任务SendTaskCommand(channels[channel_index], taskcommand);std::cout << std::endl;std::cout << "taskcommand: " << taskcommand << " channel: "<< channels[channel_index].GetName() << " sub process: " << channels[channel_index].GetProcessId() << std::endl;
}
void ctrlProcess(std::vector<Channel> &channels, int times = -1)
{if (times > 0){while (times--){ctrlProcessOnce(channels);}}else{while (true){ctrlProcessOnce(channels);}}
}void CleanUpChannel(std::vector<Channel> &channels)
{for (auto &channel : channels){channel.CloseChannel();}for (auto &channel : channels){channel.Wait();}
}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]);LoadTask();std::vector<Channel> channels;// 1. 创建信道和子进程CreateChannelAndSub(num, &channels, work);// 2. 通过channel控制子进程ctrlProcess(channels, 10);// 3. 回收管道和子进程. a. 关闭所有的写端 b. 回收子进程CleanUpChannel(channels);// sleep(100);return 0;
}#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"class Channel
{
public:Channel(int wfd, pid_t id, const std::string &name): _wfd(wfd), _subprocessid(id), _name(name){}int GetWfd() { return _wfd; }pid_t GetProcessId() { return _subprocessid; }std::string GetName() { return _name; }void CloseChannel(){close(_wfd);}void Wait(){pid_t rid = waitpid(_subprocessid, nullptr, 0);if (rid > 0){std::cout << "wait " << rid << " success" << std::endl;}}~Channel(){}private:int _wfd;pid_t _subprocessid;std::string _name;
};//  task_t task: 回调函数
void CreateChannelAndSub(int num, std::vector<Channel> *channels, task_t task)
{// BUG? --> fix bugfor (int i = 0; i < num; i++){// 1. 创建管道int pipefd[2] = {0};int n = pipe(pipefd);if (n < 0)exit(1);// 2. 创建子进程pid_t id = fork();if (id == 0){if (!channels->empty()){// 第二次之后,开始创建的管道for(auto &channel : *channels) channel.CloseChannel();}// child - readclose(pipefd[1]);dup2(pipefd[0], 0); // 将管道的读端,重定向到标准输入task();close(pipefd[0]);exit(0);}// 3.构建一个channel名称std::string channel_name = "Channel-" + std::to_string(i);// 父进程close(pipefd[0]);// a. 子进程的pid b. 父进程关心的管道的w端channels->push_back(Channel(pipefd[1], id, channel_name));}
}int NextChannel(int channelnum)
{static int next = 0;int channel = next;next++;next %= channelnum;return channel;
}void SendTaskCommand(Channel &channel, int taskcommand)
{write(channel.GetWfd(), &taskcommand, sizeof(taskcommand));
}
void ctrlProcessOnce(std::vector<Channel> &channels)
{sleep(1);// a. 选择一个任务int taskcommand = SelectTask();// b. 选择一个信道和进程int channel_index = NextChannel(channels.size());// c. 发送任务SendTaskCommand(channels[channel_index], taskcommand);std::cout << std::endl;std::cout << "taskcommand: " << taskcommand << " channel: "<< channels[channel_index].GetName() << " sub process: " << channels[channel_index].GetProcessId() << std::endl;
}
void ctrlProcess(std::vector<Channel> &channels, int times = -1)
{if (times > 0){while (times--){ctrlProcessOnce(channels);}}else{while (true){ctrlProcessOnce(channels);}}
}void CleanUpChannel(std::vector<Channel> &channels)
{for (auto &channel : channels){channel.CloseChannel();}for (auto &channel : channels){channel.Wait();}
}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]);LoadTask();std::vector<Channel> channels;// 1. 创建信道和子进程CreateChannelAndSub(num, &channels, work);// 2. 通过channel控制子进程ctrlProcess(channels, 10);// 3. 回收管道和子进程. a. 关闭所有的写端 b. 回收子进程CleanUpChannel(channels);// sleep(100);return 0;
}

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

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

相关文章

CentOS7 部署单机版 ElasticSearch + Logstash

一、部署ElasticSearch Elasticsearch部署参考下面文章&#xff1a; CentOS7 部署单机版 elasticsearch-CSDN博客文章浏览阅读285次&#xff0c;点赞6次&#xff0c;收藏3次。ElasticSearch&#xff0c;用于检索、聚合分析和大数据存储https://blog.csdn.net/weixin_44295677…

后端经典三层架构

大家好&#xff0c;这里是教授.F 引入&#xff1a; MVC 全称∶ Model 模型、View 视图、 Controller 控制器。MVC 最早出现在 JavaEE 三层中的 Web 层&#xff0c;它可以有效的指导WEB 层的代码如何有效分离&#xff0c;单独工作。 View 视图∶只负责数据和界面的显示&#…

拼多多买家关注店铺 id 导出商品收藏导出查看全店spu_id

拼多多买家账号导出关注列表店铺 id、商品 id 登录网页端拼多多界面&#xff0c;清缓存后 F12加载数据&#xff0c;下滑界面自动加载缓存&#xff1b; 查看商品收藏 登录网页端拼多多界面&#xff0c;清缓存后 F12加载数据&#xff0c;下滑界面自动加载缓存&#xff1b; 一…

【招聘帖】资深数通专家 ,薪资最高3.5W

资深数通专家 薪资&#xff1a;25000-35000 元/月 工作职责 岗位职责&#xff1a; 1、负责统筹管理客户全网问题&需求跟踪管理及汇报、日常数通团队工作的每日分解协调、人员技能内训及跟踪闭环&#xff1b; 2、负责统筹对架构基线梳理评估、软件版本管理、应急方案可执行…

Git-01

Git是一个免费且开源的分布式版本控制系统&#xff0c;它可以跟踪文件的修改、记录变更的历史&#xff0c;并且在多人协作开发中提供了强大的工具和功能。 Git最初是由Linus Torvalds开发的&#xff0c;用于Linux内核的开发&#xff0c;现在已经成为了广泛使用的版本控制系统&a…

拉普拉斯IPO:新能源产业快速发展,为低碳转型注入强劲动力

近年来&#xff0c;我国新能源产业快速发展&#xff0c;为全球绿色低碳转型注入强劲动力。国际人士认为&#xff0c;中国新能源产业快速发展&#xff0c;得益于超大规模市场优势、完整的工业体系和丰富的人力资源&#xff0c;得益于企业对研发创新的巨大投入&#xff0c;我国市…

安装PyTorch详细步骤

&#x1f4a5;注意事项&#xff1a; CPU版和GPU版选一个进行安装即可 如果有Nvidia显卡&#xff0c;则安装cuda版本的PyTorch&#xff0c;如没有nvidia显卡&#xff0c;则安装cpu版。 目前常见的深度学习框架有很多&#xff0c;最出名的是&#xff1a;PyTorch&#xff08;faceb…

expect自动交互

在执行命令或脚本的时候&#xff0c;当控制台提示我们需要输入账号密码、参数等信息的时候&#xff0c;expect可以将预设的参数值自动输入到控制台&#xff0c;实现了自动交互。 1. 安装expect yum install expect 2. 案例&#xff1a; 创建 demo.exp 文件&#xff0c;并添…

Linux 系统中 ODBC 驱动的安装与配置指南

Linux 下的 ODBC 包 从发布包中获取&#xff0c;包名为 openGauss-*.*.0-ODBC.tar.gz。Linux 环境下&#xff0c;开发应用程序要用到 unixODBC 提供的头文件&#xff08;sql.h、sqlext.h 等&#xff09;和库 libodbc.so。这些头文件和库可从 unixODBC-2.3.0 的安装包中获得。 …

降压芯片SL3036耐压100V 电机驱动板应用48-85V降压12V 1A以内

降压芯片SL3036以其卓越的耐压特性&#xff0c;能够在高达100V的电压环境下稳定运行&#xff0c;为电机驱动板等应用提供了强大的电源支持。这款芯片在电机驱动板中发挥着至关重要的作用&#xff0c;特别是在那些需要48-85V宽范围输入电压并降压至稳定12V输出的场景中&#xff…

delphi advstringgrid 表格中加checkbox

加checkbox for i : 0 to sg.RowCount - 1 dobeginsg.AddCheckBox(1,i,false,false);for j : 0 to sg.ColCount - 1 dobeginif j <> 1 thensg.CellProperties[j,i].ReadOnly : true;//只读end;end; 读取&#xff0c;设置checkbox&#xff0c;全选&#xff0c;反选 proced…

信息系统项目管理师0134:输出(8项目整合管理—8.8实施整体变更控制—8.8.3输出)

点击查看专栏目录 文章目录 8.8.3 输出 8.8.3 输出 批准的变更请求 由项目经理、CCB或指定的团队成员&#xff0c;根据变更管理计划处理变更请求&#xff0c;做出批准、推迟或否决的决定。批准的变更请求应通过指导与管理项目工作过程加以实施。对于推迟或否决的变更请求&…

AI 前沿发展摘要

&#x1f514; AI 前沿发展摘要 1⃣️ ChatScene: 一句话生成自动驾驶关键场景 主要功能: Chat Scene 能够在CARLA仿真环境中创建多样化和复杂的场景, 有效地弥合了交通场景文本描述和CARLA实际仿真之间的鸿沟 效果: 通过使用生成的安全关键场景来微调不同的基于RL的自动驾…

远程桌面连接--“发生身份验证错误。要求的函数不受支持”

出现身份验证错误 要求的函数不受支持的问题&#xff0c;可以通过以下几种方法尝试解决&#xff1a;12 对于Windows 10家庭版用户&#xff0c;需要修改注册表信息。具体步骤如下&#xff1a; 按下WIN R&#xff0c;输入regedit&#xff0c;点击确定&#xff0c;打开注册表编辑…

如何使用Studio 3T导出MongoDB数据成excel?

导出MongoDB查询集合数据成excel 1. 新建查询页面&#xff0c;输入指定的查询语句&#xff0c;执行查询获取结果。 这里以查询集合accountbackLogger表中的reqTime字段日期是2024年5月的数据为列。 db.getCollection("accountbackLogger").find({reqTime:{$gte: IS…

讯方·智汇云校助力多所高校斩获华为ICT大赛2023-2024全球总决赛佳绩

2024年5月26日&#xff0c;华为ICT大赛2023-2024全球总决赛闭幕式暨颁奖典礼在深圳举行。讯方技术14所合作院校的18支队伍在此次大赛中荣获系列大奖。 本届大赛为华为历届最大规模的线下比赛&#xff0c;共吸引了全球80多个国家和地区、2000多所院校、17万余名学生报名参赛&am…

超好用!图像去雾算法C2PNet介绍与使用指南

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

hive3从入门到精通(一)

Hive3入门至精通(基础、部署、理论、SQL、函数、运算以及性能优化)1-14章 第1章:数据仓库基础理论 1-1.数据仓库概念 数据仓库&#xff08;英语&#xff1a;Data Warehouse&#xff0c;简称数仓、DW&#xff09;,是一个用于存储、分析、报告的数据系统。 数据仓库的目的是构…

红酒与不同烹饪方法的食物搭配原则

红酒与食物的搭配是一门艺术&#xff0c;而不同烹饪方法的食物与红酒的搭配也有其与众不同之处。红酒与食物的搭配不仅涉及到口感、风味和营养&#xff0c;还与烹饪方法和食物质地等因素息息相关。云仓酒庄雷盛红酒以其卓着的品质和丰富的口感&#xff0c;成为了实现完善搭配的…

Java解析并修改JSON:将isShow属性改为false

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 在Java中&#xff0c;可以使用各种库来处理JSON数据。其中&#xff0c;Jackson和Gson是两个非常流行且功能强大的库。在这篇文章中&#xff0c;将使用Jackson库来解析给定的JSON字符串&#xff0c;将其转换为Map对象…