【Linux】进程间通信介绍 | 管道
🌠 作者:@阿亮joy.
🎆专栏:《学会Linux》
🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
目录
- 👉进程间通信介绍👈
- 进程间通信目的
- 进程间通信发展和分类
- 👉管道👈
- 什么是管道
- 管道的原理
- 匿名管道
- 管道的特点
- mini版进程池的实现
- 命名管道
- 匿名管道与命名管道的区别
- 👉总结👈
👉进程间通信介绍👈
进程间通信(Interprocess Communication)就是两个进程之间进行通信。进程是具有独立性(虚拟地址空间 + 页表保证进程运行的独立性),所以进程间通信成本会比较高!进程间通信的前提条件是先让不同的进程看到同一份资源(内存空间),该资源不能隶属于任何一个进程,应该属于操作系统,被进行通信的进程所共享。
进程间通信目的
单进程无法使用并发能力,更加无法实现多进程协同,那么就有了进程间通信。进程间通信的目的如下:
- 数据传输:一个进程需要将它的数据发送给另一个进程。
- 资源共享:多个进程之间共享同样的资源。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
进程间通信发展和分类
进程间通信的发展和分类如下:
- Linux 原生能提供的管道,管道主要包括匿名管道 pipe 和命名管道。
- SystemV 进程间通信,System V IPC 主要包括 System V 消息队列、System V 共享内存和 System V 信号量。System V 只能本地通信。
- POSIX 进程间通信,POSIX IPC 主要包括消息队列、共享内存、信号量、互斥量、条件变量和读写锁。POSIX 进程通信既能进行本地通信,又能进行网络远程通信,具有高扩展和高可用性。
👉管道👈
什么是管道
日常生活中,有非常多管道,如:天然气管道、石油管道和自来水管道等。管道是 Unix 中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的一个数据流称为管道。管道传输的都是资源,并且只能单向通信。
管道的原理
管道的本质就是文件。与文件的区别就是管道中的数据是不用写入到磁盘中的(持久化)。进程间通信都是内存级别的通信,如果还要将数据写入到内存,那么通信的效率就会大大下降。
如何做到让不同的进程看到同一份资源的呢?fork 创建子进程,让子进程继承父进程的与进程管理相关的内核数据结构,这样就能够让具有血缘关系的进程进行进程间通信,常用于父子进程。
匿名管道
匿名管道就是没有名字的管道,可以通过系统调用 pipe 来创建匿名管道。pipe 函数的参数是 int pipefd[2],它是输出型参数,通过 pipefd 数组可以拿到系统为我们创建的匿名管道文件。pipefd[0] 是读端,pipefd[1] 是写端(巧记:0 像嘴巴,用来读书;1 像钢笔,用来写字)。如果管道创建成功,返回值为 0;如果管道创建失败,返回值为 -1,并设置相应的错误码。
Makefile 文件
mypipe:mypipe.ccg++ $^ -o $@ -std=c++11 #-D DEBUG
.PHONY:clean
clean:rm -f mypipe
注:.cc 后缀也是 C++ 文件的表示方法之一,-D 是命令行定义,可用于 Debug。如果一个变量只声明并没有被使用,在 Realease 版本下会有大量的告警。为了避免告警,可以将该变量强转为 void。assert 在 Realease 版本下不起作用。
#include <iostream>
#include <string>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <cstring>using namespace std;int main()
{// 1. 创建管道// pipefd[0]:读端(0像嘴巴,读书)// pipefd[1]:写端(1像钢笔,写字)int pipefd[2] = {0};int n = pipe(pipefd);assert(n != -1);(void)n; // 避免Realease编译时出现大量告警// 条件编译可以搭配命令行定义来进行debug
#ifdef DEBUGcout << "pipefd[0]:" << pipefd[0] << endl; // 3cout << "pipefd[1]:" << pipefd[1] << endl; // 4
#endif// 2. 创建子进程// fork创建子进程失败返回-1pid_t id = fork();assert(id != -1);if (id == 0){// 关闭子进程不需要的fd,子进程进行读取close(pipefd[1]);char buffer[1024]; // 缓冲区while (true){ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;cout << "child process[" << getpid() << "]" << "get a message, Fathe#" << buffer << endl;}}// close(pipefd[0]); exit(0); // 进程退出,文件描述符会被关掉,不代表文件被关掉}// 关闭父进程不需要的fd,父进程进行写入close(pipefd[0]);string message = "我是父进程,我正在给你发消息";char send_buffer[1024];int count = 0;while (true){// 构造变化的字符串snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d", message.c_str(), getpid(), count++);write(pipefd[1], send_buffer, strlen(send_buffer));sleep(1);}pid_t ret = waitpid(id, nullptr, 0); // 阻塞等待assert(ret > 0);(void)ret;close(pipefd[1]);return 0;
}

注:不能定义全局缓冲区 buffer 来通信,因为有写时拷贝的存在会保证父子进程信息的独立,所以就无法通过全局的 buffer 来进行通信。
管道的特点
- 管道是用来进行具有血缘关系的进程进行进程间通信,常用于父子进程。
- 匿名管道需要在创建子进程之前创建,因为只有这样才能复制到管道的操作句柄,与具有亲缘关系的进程实现访问同一个管道通信。
- 管道本质是内核中的一块缓冲区,多个进程通过访问同一块缓冲区实现通信。
- 显示器也是一个文件,父子进程同时向显示器写入的时候,没有一个进程等另一个进程的情况,也就是说缺乏访问控制。而管道是为了让进程间协同,其提供了访问控制。
- 写快,读满,将管道文件写满了就不能再写了
- 写满,读快,管道文件中没有数据的时候,读端必须等写端进行数据写入
- 写关,读 0,标识读到了管道文件的结尾
- 读关,写继续写,操作系统会终止写进程。
- 管道提供的是面向流式的通信服务(面向字节流),需要定制协议来进行数据区分。
- 管道是基于文件的,文件的生命周期是随进程的,那么管道的生命周期也是随进程的。
- 一般而言,内核会对管道操作进行同步与互斥
- 管道是单向通信的,就是半双工通信的一种特殊情况,数据只能向一个方向流动。需要双方通信时,需要建立起两个管道。半双工通信就是要么在收数据,要么在发数据,不能同时在收数据和发数据(比如两个人在交流时,一个人在说,另一个人在听);而全双工通信是同时进行收数据和发数据(比如两个人吵架的时候,相互问候对方,一个人既在问候对方又在听对方的问候)。
- 当要写入的数据量不大于 PIPE_BUF 时,Linux 将保证写入的原子性。
- 当要写入的数据量大于 PIPE_BUF 时,Linux 将不再保证写入的原子性。
- 指事务的不可分割性,一个事务的所有操作要么不间断地全部被执行,要么一个也没有执行。
写关读 0 的情况
#include <iostream>
#include <string>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <cstring>using namespace std;int main()
{// 1. 创建管道// pipefd[0]:读端(0像嘴巴,读书)// pipefd[1]:写端(1像钢笔,写字)int pipefd[2] = {0};int n = pipe(pipefd);assert(n != -1);(void)n; // 避免Realease编译时出现大量告警// 条件编译可以搭配命令行定义来进行debug
#ifdef DEBUGcout << "pipefd[0]:" << pipefd[0] << endl; // 3cout << "pipefd[1]:" << pipefd[1] << endl; // 4
#endif// 2. 创建子进程// fork创建子进程失败返回-1pid_t id = fork();assert(id != -1);if (id == 0){// 关闭子进程不需要的fd,子进程进行读取close(pipefd[1]);char buffer[1024];while (true){// 写入的一方,fd没有关闭,如果有数据,就读,没有数据就等// 写入的一方,fd关闭, 读取的一方,read会返回0,表示读到了文件的结尾!ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;cout << "child process[" << getpid() << "]" << "get a message, Fathe#" << buffer << endl;}else if(s == 0){cout << "writer quit(father), me quit too!!!" << endl;break;}}// close(pipefd[0]); exit(0); // 进程退出,文件描述符会被关掉,不代表文件被关掉}// 关闭父进程不需要的fd,父进程进行写入close(pipefd[0]);string message = "我是父进程,我正在给你发消息";char send_buffer[1024];int count = 0;while (true){// 构造变化的字符串snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d", message.c_str(), getpid(), count++);write(pipefd[1], send_buffer, strlen(send_buffer));sleep(1);if(count == 5){cout << "writer quit(father)" << endl;break;}}close(pipefd[1]);pid_t ret = waitpid(id, nullptr, 0); // 阻塞等待assert(ret != -1);(void)ret;return 0;
}

读关,写继续写,操作系统终止写进程
#include <iostream>
#include <string>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <cstring>using namespace std;int main()
{// 1. 创建管道// pipefd[0]:读端(0像嘴巴,读书)// pipefd[1]:写端(1像钢笔,写字)int pipefd[2] = {0};int n = pipe(pipefd);assert(n != -1);(void)n; // 避免Realease编译时出现大量告警// 条件编译可以搭配命令行定义来进行debug
#ifdef DEBUGcout << "pipefd[0]:" << pipefd[0] << endl; // 3cout << "pipefd[1]:" << pipefd[1] << endl; // 4
#endif// 2. 创建子进程// fork创建子进程失败返回-1pid_t id = fork();assert(id != -1);if (id == 0){// 关闭子进程不需要的fd,子进程进行读取close(pipefd[1]);char buffer[1024];int count = 0;while (true){// 写入的一方,fd没有关闭,如果有数据,就读,没有数据就等// 写入的一方,fd关闭, 读取的一方,read会返回0,表示读到了文件的结尾!ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;++count;cout << "child process[" << getpid() << "]" << "get a message, Fathe#" << buffer << endl;}else{cout << "writer quit(father), me quit too!!!" << endl;break;}// 验证读提前退出,写继续写,操作系统终止写进程的情况if(count == 5){cout << "child quit!" << endl;break;}}close(pipefd[0]); exit(0); // 进程退出,文件描述符会被关掉,不代表文件被关掉}// 关闭父进程不需要的fd,父进程进行写入close(pipefd[0]);string message = "我是父进程,我正在给你发消息";char send_buffer[1024];int count = 0;while (true){// 构造变化的字符串snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d", message.c_str(), getpid(), count++);write(pipefd[1], send_buffer, strlen(send_buffer));sleep(1);if(count == 10){cout << "writer quit(father)" << endl;break;}}close(pipefd[1]);pid_t ret = waitpid(id, nullptr, 0); // 阻塞等待assert(ret > 0);(void)ret;return 0;
}

读快写满和读慢写快的两种情况,大家可以自己尝试一下!
mini版进程池的实现
实现思路:首先先定义一些任务并将这些任务加载。然后创建管道文件和子进程,将子进程的写端关闭并等待父进程派发任务(父进程向管道文件中写入数据就是某个子进程派发任务)。如果父进程没有给子进程派发任务的话,子进程只能阻塞等待(对应写满读快的情况)。注:该进程池是单机的负载均衡。
// hpp为后缀的文件既有函数的声明又有函数的定义
// Task.hpp
#pragma once#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <functional>
#include <map>// 包装器:定义一种函数类型,其返回值为void,没有参数
// using func = std::function<void()> ; // C++11的做法
typedef std::function<void()> func;// callBacks存的是函数类型,也就是任务
std::vector<func> callBacks;
// desc是任务的下标和任务的描述
std::map<int, std::string> desc;void readMySQL()
{std::cout << "sub process[" << getpid() << "] 执行访问数据的任务\n" << std::endl;
}void execulUrl()
{std::cout << "sub process[" << getpid() << "] 执行URL解析\n" << std::endl;
}void cal()
{std::cout << "sub process[" << getpid() << "] 执行加密任务\n" << std::endl;
}void save()
{std::cout << "sub process[" << getpid() << "] 执行数据持久化任务\n" << std::endl;
}// 加载任务
void load()
{desc[callBacks.size()] = "readMySQL: 读取数据库";callBacks.push_back(readMySQL);desc[callBacks.size()] = "execulUrl: 进行URL解析";callBacks.push_back(execulUrl);desc[callBacks.size()] = "cal: 进行加密计算";callBacks.push_back(cal);desc[callBacks.size()] = "save: 进行数据的文件保存";callBacks.push_back(save);
}// 展示任务列表
void showHandler()
{for(const auto& kv : desc){std::cout << kv.first << "\t" << kv.second << std::endl;}
}// 返回任务的个数
int handlerSize()
{return callBacks.size();
}// ProcessPool.cc
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"using namespace std;#define PROCESS_NUM 5// 如果父进程没有给子进程派发任务,子进程就阻塞等待任务
int waitCommand(int waitFd, bool& quit)
{uint32_t command = 0;ssize_t s = read(waitFd, &command, sizeof(command));// 写端退出,读端读到0,则写端也要退出if(s == 0){quit = true;return -1;}assert(s == sizeof(uint32_t)); // 要求必须读到4给字节return command;
}void sendAndWakeup(pid_t who, int fd, uint32_t command)
{write(fd, &command, sizeof(command));// 父进程通过fd唤醒子进程并给它派发任务desc[command]cout << "father process call child process[" << who << "] execul " << desc[command] << " through " << fd << endl;
}int main()
{// 加载任务load();// pid_t是子进程的id, int是写端的fdvector<pair<pid_t, int>> slots;// 先创建多个进程for(int i = 0; i < PROCESS_NUM; ++i){// 创建管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n == 0); // 判断管道是否创建成功(void)n;// 创建子进程pid_t id = fork();assert(id != -1); // 判断子进程是否创建成功// child processif(id == 0){// 关闭子进程的写端close(pipefd[1]);// 子进程等待父进程派发任务while(true){// false表示父进程的写端没有关闭bool quit = false;// 如果父进程不派发任务,子进程就阻塞等待int command = waitCommand(pipefd[0], quit);// 父进程的写端关闭,子进程的读端也要退出if(quit)break;// 执行对应的任务if(command >= 0 && command < handlerSize())callBacks[command]();elsecout << "非法command: " << command << endl;}cout << "sender quit, receiver quit too!!!" << endl; close(pipefd[0]);exit(0);}// father process// 关闭父进程的读端,将子进程的id和父进程的写端保存到slots中close(pipefd[0]);slots.push_back(make_pair(id, pipefd[1]));}// 父进程随机给子进程派发任务srand((unsigned int)(time(nullptr) ^ getpid() ^ 2023222)); // 让数据源更随机int count = 0; // 父进程给子进程总共派发5个任务后,关闭父进程的所有写端while(true){// 随机选取一个任务int command = rand() % handlerSize();// 随机选取一个子进程,随机数方式的负载均衡int choice = rand() % slots.size();// 把任务派发给指定的进程sendAndWakeup(slots[command].first, slots[command].second, command);sleep(1);++count;if(count == 5){cout << "父进程的任务全部派发完成" << endl;break;}// 下方的代码是用户指定做哪一个任务// int select;// int command;// cout << endl;// cout << "############################################" << endl;// cout << "# 1. show funcitons 2.send command #" << endl;// cout << "############################################" << endl;// cout << "Please Select> ";// cin >> select;// if (select == 1)// showHandler();// else if (select == 2)// {// cout << "Enter Your Command> ";// // 选择任务// cin >> command;// // 选择进程// int choice = rand() % slots.size();// // 把任务给指定的进程// sendAndWakeup(slots[choice].first, slots[choice].second, command);// sleep(1);// }// else// {// cout << "select error!" << endl;// continue;// }}// 关闭fd, 所有的子进程都会读到0,关闭读端并退出for (const auto &slot : slots){close(slot.second);}// 等待子进程for (const auto &slot : slots){waitpid(slot.first, nullptr, 0);}return 0;
}
随机派发任务

用户派发指定任务

命名管道
匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。如果我们想在不相关的进程之间交换数据,可以使用 FIFO 文件来做这项工作,它经常被称为命名管道,命名管道是一种特殊类型的文件。
命令行创建管道文件
while :; do echo "hello world"; sleep 1; done > name_pipe #向命名管道写入数据cat < name_pipe #读取命名管道中的数据

命名管道就是有名字的管道文件,如上图所示。命名管道主要用于没有任何血缘关系的两个进程进行通信。创建命名管道文件的接口如下:
- int mkfifo(const char *pathname, mode_t mode);
- pathname 是命名管道所在的路径和命名管道的名字,如果是在当前路径下创建管道文件,只需要提供管道文件的名字即可。如果不是,需要指明管道文件所处的路径。
- mode 是管道文件的权限。
模拟客户端和服务端
# Makefile
.PHONY:all
all: server clientserver:server.cxxg++ $^ -o $@ -std=c++11
client:client.cxxg++ $^ -o $@ -std=c++11.PHONY:clean
clean:rm -f client server
// Log.hpp
#ifndef _LOG_H_
#define _LOG_H_#include <iostream>
#include <ctime>// 日志信息等级
#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3const std::string msg[] = {"Debug", "Notice", "Warning", "Error"};// 输入日志信息的函数
std::ostream& Log(std::string message, int level)
{std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;return std::cout;
}#endif// Common.hpp
#ifndef _COMM_H_
#define _COMM_H_#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include "Log.hpp"using namespace std;#define MODE 0666 // 权限
#define SIZE 128 // 缓冲区大小string ipcPath = "./fifo.ipc";#endif// server.cxx
#include "Comm.hpp"int main()
{// 1. 创建管道文件if(mkfifo(ipcPath.c_str(), MODE) < 0){perror("mkfifo");exit(1);}Log("创建管道文件成功", Debug) << "step 1" << endl;// 2. 正常的文件操作int fd = open(ipcPath.c_str(), O_RDONLY);if(fd < 0){perror("server open");exit(2);}Log("打开管道文件成功", Debug) << "step 2" << endl;// 3. 编写正常的通信代码char buffer[SIZE];while(true){memset(buffer, '\0', sizeof(buffer));ssize_t s = read(fd, buffer, sizeof(buffer) - 1);if(s > 0){cout << "client say: " << buffer << endl;}else if(s == 0) // 客户端退出{// end of filecerr << "read end of file, client quit, server quit too!!!" << endl;break;}else{perror("read error");exit(3);}}// 4. 关闭管道文件close(fd);Log("关闭管道文件成功", Debug) << "step 3" << endl;// 5. 删除管道文件unlink(ipcPath.c_str());Log("删除管道文件成功", Debug) << "step 4" << endl;return 0;
}// client.cxx
#include "Comm.hpp"int main()
{// 1. 获取管道文件int fd = open(ipcPath.c_str(), O_WRONLY);if(fd < 0){perror("client open");exit(1);}// 2. IPC过程string buffer;while(true){cout << "Please Enter Message :>";getline(cin, buffer);write(fd, buffer.c_str(), buffer.size());}// 3. 关闭管道文件close(fd);return 0;
}


注:client 是客户端,客户端向管道文件写入数据,也就是给服务端发信息;server 是服务端,服务端读取管道文件的数据,接收客户端发过来的信息。管道文件只要在服务端创建接口,客户端不需要创建管道文件。Ctrl + Backspace 可以删除字符。
服务端有多个子进程竞争客户端发来的信息
# Makefile
.PHONY:all
all: multiServer clientmultiServer:server.cxxg++ $^ -o $@ -std=c++11
client:client.cxxg++ $^ -o $@ -std=c++11.PHONY:clean
clean:rm -f client multiServer
// server.cxx
#include "Comm.hpp"static void getMessage(int fd)
{char buffer[SIZE];while(true){memset(buffer, '\0', sizeof(buffer));ssize_t s = read(fd, buffer, sizeof(buffer) - 1);if(s > 0){cout << "[" << getpid() << "] " << "client say> " << buffer << endl;}else if(s == 0) // 客户端退出{// end of filecerr << "[" << getpid() << "] " << "read end of file, client quit, server quit too!!!" << endl;break;}else{perror("read error");exit(3);}}
}int main()
{// 1. 创建管道文件if(mkfifo(ipcPath.c_str(), MODE) < 0){perror("mkfifo");exit(1);}Log("创建管道文件成功", Debug) << "step 1" << endl;// 2. 正常的文件操作int fd = open(ipcPath.c_str(), O_RDONLY);if(fd < 0){perror("server open");exit(2);}Log("打开管道文件成功", Debug) << "step 2" << endl;int nums = 3;for(int i = 0; i < nums; ++i){pid_t id = fork();if(id == 0){// 3. 编写正常的通信代码getMessage(fd);exit(1);}}for(int i = 0; i < nums; ++i){// -1表示等待任意一个子进程waitpid(-1, nullptr, 0);}// 4. 关闭管道文件close(fd);Log("关闭管道文件成功", Debug) << "step 3" << endl;// 5. 删除管道文件unlink(ipcPath.c_str());Log("删除管道文件成功", Debug) << "step 4" << endl;return 0;
}

匿名管道与命名管道的区别
- 匿名管道由pipe函数创建并打开。
- 命名管道由 mkfifo 函数创建,打开用 open。
- FIFO(命名管道)与 pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。
👉总结👈
本篇博客主要讲解了什么是进程间通信、进程间通信的目的、什么是管道、管道的原理、匿名管道、管道的特点、命名管道等等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️
相关文章:
【Linux】进程间通信介绍 | 管道
🌠 作者:阿亮joy. 🎆专栏:《学会Linux》 🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根 目录👉进程间通信…...
这次说说腾讯的一场 35K—55K 的 Android 高工面试
一、面试的由来 事情是这样的,因为跟公司发展一些想法的不同,早在十月份的时候就有了跳槽的想法,但是碍于老大的面子就一直就没有跟人事说出口,打算着等到年后金三银四在试试跳槽。 但是发生一件事终于让我忍不住了,…...
Jenkins第一讲
目录 一、Jenkins 1.1 敏捷开发与持续集成 1.1.1 敏捷开发 1.1.2 持续集成 1.2 持续集成工具 1.2.1 jenkins和hudson 1.2.2 技术组合 1.2.3 部署方式对比 1.3 安装Jenkins 1.3.1 下载Jenkins的war包 1.3.2 开启Jenkins 1.4 Jenkins全局安全配置 1.5 使用Jenkins部…...
变分推断 | MATLAB实现VBMC变分贝叶斯蒙特卡洛模拟的贝叶斯推断
变分推断 | MATLAB实现变分贝叶斯蒙特卡洛模拟的贝叶斯推断 目录 变分推断 | MATLAB实现变分贝叶斯蒙特卡洛模拟的贝叶斯推断效果一览基本介绍研究内容模型描述模型设计参考资料效果一览 基本介绍 MATLAB实现变分贝叶斯蒙特卡洛模拟的贝叶斯推断。变分贝叶斯蒙特卡洛(VBMC)是…...
代码随想录【Day25】| 216. 组合总和 III、17. 电话号码的字母组合
216. 组合总和 III 题目链接 题目描述: 找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。 说明: 所有数字都是正整数。 解集不能包含重复的组合。 示例 1: 输入: k 3, n 7 输…...
web中git漏洞的形成的原理及使用
目录 1.Git漏洞的成因 1.不正确的权限设置: 2.代码注入漏洞: 3.未经身份验证的访问: 4.非安全传输: 5.跨站脚本攻击(XSS): 2.git泄露环境的搭建 git init: git add࿱…...
【SPSS】单样本T检验分析详细操作教程(附案例实战)
🤵♂️ 个人主页:艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞Ǵ…...
计算机网络笔记、面试八股(三)—— HTTPS协议
本章目录3. HTTPS协议3.1 HTTPS协议简介3.2 SSL/TLS协议3.2.1 SSL/TLS功能的实现3.3 HTTP和HTTPS的区别3.4 HTTPS协议的优点3.5 HTTPS协议的缺点3.6 HTTPS协议的工作流程3.7 HTTPS是如何解决HTTP的缺点的3.7.1 解决内容可能被窃听的问题——加密3.7.1.1 方法1.对称加密3.7.1.2 …...
浅谈liunx init.d 和 rc.local 两种起动方式
浅谈liunx init.d 和 rc.local 两种起动方式 以rabbitmq 举例 (一).init.d 方式 开机自动重启设置 1.在/etc/init.d 目录下新建一个 rabbitmq [rootlocalhost init.d]# vi rabbitmq具体脚本如下所示: #!/bin/bash # # chkconfig: 2345 …...
元宇宙+教育,正在引发哪些剧烈变革?机会在哪里?丨圆桌实录
图片来源:由无界AI绘画工具生成2月23日,温州元宇宙创新中心为2023年第一批申请入驻的项目企业举办了签约仪式。温州临境网络科技有限公司、温州好玩文化产业有限公司、温州云兮科技有限公司(筹)等企业完成签约。这意味着ÿ…...
追梦之旅【数据结构篇】——详解C语言实现顺序队列
详解C语言实现顺序队列~😎前言🙌预备小知识🙌队列的概念及结构😊1.顺序队列头文件编写🙌2.Queue.c文件的编写🙌1)队列的初始化函数实现😊2)队列的销毁函数实现Ƕ…...
使用自己的数据集Fine-tune PaddleHub预训练模型
使用自己的数据Fine-tune PaddleHub预训练模型 果农需要根据水果的不同大小和质量进行产品的定价,所以每年收获的季节有大量的人工对水果分类的需求。基于人工智能模型的方案,收获的大堆水果会被机械放到传送带上,模型会根据摄像头拍到的图片…...
带组态物联网平台源码 代码开源可二次开发 web MQTT Modbus
物联网IOT平台开发辅助文档 技术栈:JAVA [ springmvc / spring / mybatis ] 、Mysql 、Html 、 Jquery 、css 使用协议和优势: TCP/IP、HTTP、MQTT 通讯协议 1.1系统简介 IOT通用物联网系统平台带组态,是一套面向通用型业务数据处理的系统…...
计算机网络的发展历程
计算机网络的历史可以追溯到20世纪60年代。那个时候,计算机还非常昂贵,只有少数大型机可以被用于处理重要任务。这些大型机通常被安装在大型企业、政府机构和大学中。由于这些机器非常昂贵,许多企业、机构和大学只能通过终端连接来访问它们。…...
【华为OD机试模拟题】用 C++ 实现 - 不含 101 的数(2023.Q1)
最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…...
面试题-下单后位置信息上报的方案
面试题:外卖下单后每10min上报位置事件的具体实现方案。需要考虑哪些点。存储方案:考虑到数据量很大,需要快速响应查询请求,建议使用分布式存储方案,如 HBase、MongoDB 等。这些分布式存储系统可以水平扩展,…...
视觉人培训团队把它称之为,工业领域人类最伟大的软件创造,它的名字叫Halcon
目前为止,世界上综合能力强大的机器视觉软件,,它的名字叫Halcon。 视觉人培训团队把它称之为,工业领域人类最伟大的软件创造,它的名字叫Halcon。 持续不断更新最新的图像技术,软件综合能力持续提升。 综…...
干了2年的手工点点点,感觉每天浑浑噩噩,我的自动化测试之路...
作为一个测试人员,从业年期从事手工测试的工作是没有太多坏处的,当然,如果一直点来点去那么确实自身得不到提高,这时候选择学习自动化测试是一件很有必要的事情,一来将自己从繁重的重复工作中解放出来,从事…...
嵌入式系统硬件设计与实践(学习方法)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 刚读书的时候,对什么是嵌入式,其实并不太清楚。等到自己知道的时候,已经毕业很多年了。另外对于计算机毕业的学…...
如何拥有自己的Gitee代码仓库
本教程适用码云代码托管平台 https://gitee.com/ 首先在电脑上安装Git(哔站有安装Git教程)和注册gitee账号后再来阅读此教程 1、在设置页面中点击 SSH公钥 2、点击 怎样生成公钥 3、点击公钥管理 4、点击 生成\添加SSH公钥 5、打开终端 输入如图红框中的…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...
Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解
文章目录 一、开启慢查询日志,定位耗时SQL1.1 查看慢查询日志是否开启1.2 临时开启慢查询日志1.3 永久开启慢查询日志1.4 分析慢查询日志 二、使用EXPLAIN分析SQL执行计划2.1 EXPLAIN的基本使用2.2 EXPLAIN分析案例2.3 根据EXPLAIN结果优化SQL 三、使用SHOW PROFILE…...
车载诊断架构 --- ZEVonUDS(J1979-3)简介第一篇
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…...








