Linux——进程间通信之管道
进程间通信之管道
文章目录
- 进程间通信之管道
- 1. 进程间通信
- 1.1 为什么要进行进程间的通信
- 1.2 如何进行进程间的通信
- 1.3 进程间通信的方式
- 2. 管道
- 2.1 匿名管道
- 2.1.1 系统调用pipe()
- 2.1.2 使用匿名管道进行通信
- 2.1.1 匿名管道四种情况
- 2.1.2 匿名管道的五大特性
- 2.1.3 进程池
- 2.2 命名管道
- 2.2.1 创建命名管道
- 2.2.2 利用命名管道进行进程间通信
- 2.2.3 命名管道的一个特性
1. 进程间通信
1.1 为什么要进行进程间的通信
对于这个问题,答案显而易见:一个进程必然不能解决所有的问题,系统中往往需要多个进程的协作来进行工作,而进程间的协作就需要进程之间进行信息的交互,这个过程也叫做进程间的通信
简单来说,进程间的通信可以实现以下功能:
- 数据传输
- 进程控制
- 资源共享
- 事件通知
- ………………
1.2 如何进行进程间的通信
由于进程具有独立性,因此两个进程之间不能直接进行数据之间的传输(否则就会破坏进程的独立性)
因此我们需要一片公共的区域来供进程之间进行信息交流,而这片公共区域就是由OS操作系统提供
根据上面的分析,我们也可以得出关于进程间通信的本质:
进程间通信的实质上就是:让不同的进程看到同一份资源
1.3 进程间通信的方式
根据操作系统OS提供公共区域方式的不同,进程间的通信也会有不同的形式,例如:
- 管道
- 共享内存
- 信号量
- 消息队列
2. 管道
我们来假设这样一个场景:
一个进程同时以读写的方式打开一个文件,那么就会产生两个文件描述符fd:
此时,创建子进程,子进程会继承父进程属性:
我们可以发现,这时子进程和父进程就同时看到同一份文件file
了。这时,如果我们关闭父进程的写端,再关闭子进程的读端:
这样,就可以通过子进程向公共文件写数据,父进程向公共文件读数据的方式,进行父子进程之间的通信了。
向上面这样的,基于文件的形式进行进程间通信的方式,叫做管道
通过上面的例子,我们也知道:管道只允许一端读,一端写,因此基于管道的通信都是单向的
管道可以分为匿名管道和命名管道
2.1 匿名管道
2.1.1 系统调用pipe()
系统不允许用磁盘文件当作进程间通信的公共资源,为此系统提供了系统调用pipe()
来为我们创建进程通信需要的文件:
#include <unistd.h> int pipe(int pipefd[2]);
- 该系统调用会创建并以读写方式打开一个不存在于磁盘且不需要向磁盘刷新的,只存在于内存中的匿名文件(因为这个文件只需用来给两个进程进行通信,不需要持久的保存),这个文件就叫做匿名管道
- 数组
pipefd
是一个输出型参数,用于存放匿名管道的fd,其中pipefd[0]
存放的是读端;pipefd[1]
存放的是写端
2.1.2 使用匿名管道进行通信
使用pipe()
创建了系统调用后,我们再创建一个子进程,让子进程继承父进程的数据,此时父子进程就会同时以读写的方式指向同一个匿名管道了。最后只要确定数据传输方向,关闭父进程的读(写)端,关闭子进程的写(读)端,就可以正常进程进程间的通信了
同时,我们应该清楚:
由于是通过创建子进程的方式,来让子进程继承父进程的数据,使其指向相同的匿名管道来进行通信,因此匿名管道只能用来进行血缘进程之间的通信(通常用于父子进程)
示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wait.h>void my_write(int wfd) //写端操作
{const char* str = "i am child process: ";int cnt = 0;char buf[128] = {0};pid_t id = getpid();while (cnt != 20){snprintf(buf, sizeof(buf), "message: %s, id: %d, cnt: %d\n", str, id, cnt++);write(wfd, buf, strlen(buf));sleep(1);}
}void my_read(int rfd) //读端操作
{char buf[1024] = {0};int cnt = 20;while(cnt--){read(rfd, buf, sizeof(buf) - 1); //-1:预留'\0',防止出现错误printf("%s\n", buf);}
}int main()
{int pipefd[2] = {0}; int ret = pipe(pipefd); //创建匿名管道if (-1 == ret){perror("pipe:");return 1;}pid_t id = fork();if (0 == id) //子进程{//chile(w)close(pipefd[0]); //关闭读端my_write(pipefd[1]); //进行写操作exit(-1);}//father(r)close(pipefd[1]); //关闭写端my_read(pipefd[0]); //进行读操作wait(NULL); //等待子进程退出,防止出现僵尸进程return 0;
}
效果如图所示:
2.1.1 匿名管道四种情况
情况一:写端停止写入并且管道没有数据,那么读端就会阻塞,直到读到数据
例如,将上述代码的
my_write()
函数修改:void my_write(int wfd) {//写端不写 }
重新编译后,运行效果如图:
可以看到,如果写端不向读端写入数据,并且管道没有数据,读端就会陷入阻塞等待状态
情况二:写端一直在写直到管道写满,读端不读,那么写端就会阻塞等待,直到管道的数据被读取
例如,将上述代码的
my_write()、my_read()
函数修改:void my_write(int wfd) {int cnt = 0;char a = 'A';//每次只写入一个字符,并输出输入的字符个数while(1){write(wfd, &a, 1);printf("cnt: %d\n", cnt++);} }void my_read(int rfd) {//读端不读 }
重新编译后,运行效果如图:
可以看到:当读端不读时,写端向管道写入了
65535 + 1
个字符后,就进入了阻塞等待状态,这说明管道已经被填满了。同时也可以推出,在博主所用的系统中,默认的管道大小为65536Byte
也就是64kB
如果我们再次修改代码,让读端每2秒读取一次管道:
void my_read(int rfd) {char buf[1024] = {0};while(1){sleep(2);int n = read(rfd, buf, sizeof(buf) - 1);if (0 == n)break;printf("%s\n", buf);} }
重新编译后,运行效果如图:
可以总结:当管道的部分数据被读取后,写端有重新写入数据了
情况三:写端关闭,当读端读完管道的数据后,读端就会读到管道的末尾(相当于读到文件尾),自动关闭读端
例如,将上述代码的
my_write()、my_read()
函数修改:void my_write(int wfd) {const char* str = "i am child process: ";int cnt = 0;char buf[128] = {0};pid_t id = getpid();while (cnt != 5){snprintf(buf, sizeof(buf), "message: %s, id: %d, cnt: %d\n", str, id, cnt++);write(wfd, buf, strlen(buf));sleep(1);} }void my_read(int rfd) {char buf[1024] = {0};while(1){int n = read(rfd, buf, sizeof(buf) - 1);if (0 == n) //如果read的返回值为0,说明写端关闭了fd,读端读到了管道末尾{printf("no data be read, read exit\n");break;}printf("%s\n", buf);} }
重新编译后,运行效果如图:
情况四:读端关闭,写端还在写,系统就会通过发送信号的方式强制终止写端(kill -13 child_pid
)
将整体代码修改如图:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <wait.h>void my_write(int wfd) {const char* str = "i am child process: ";char buf[128] = {0};pid_t id = getpid();while (1){snprintf(buf, sizeof(buf), "message: %s, id: %d\n", str, id);write(wfd, buf, strlen(buf));sleep(1);} }void my_read(int rfd) {char buf[1024] = {0};int cnt = 5;while(cnt--){int n = read(rfd, buf, sizeof(buf) - 1);if (0 == n){printf("no data be read, read exit\n");break;}printf("%s\n", buf);}printf("read will close\n");sleep(1);close(rfd); }int main() {int pipefd[2] = {0};int ret = pipe(pipefd);if (-1 == ret){perror("pipe:");return 1;}pid_t id = fork();if (0 == id){//chile(w)close(pipefd[0]);my_write(pipefd[1]);printf("child close wfd\n\n");close(pipefd[1]);exit(-1);}//father(r)close(pipefd[1]);my_read(pipefd[0]);int status = 0;int rid = waitpid(id, &status, 0);if (rid == id){printf("child process singal code: %d\n", status & 0x7f);}return 0; }
重新编译后,运行效果如图:
2.1.2 匿名管道的五大特性
- 同步机制:管道在处理数据读写时,确保数据的有序性和正确性的一种控制方式
- 血缘进程通信:由于匿名管道的构建建立在进程继承的基础上,因此匿名管道只允许血缘进程的通信
- 单向通信(半双工):同一时间,只允许一端读,一端写
- 文件的生命周期是随进程的:父子进程退出,管道(文件)自动释放
- pipe是面向字节流的
2.1.3 进程池
进程池的基本概念:
进程池是一组预先创建好的进程集合,这些进程处于空闲等待状态,随时准备接收任务并进行处理。父进程可以在任意时候控制子进程的休眠、工作与退出。
进程池的优点:
- 可以复用进程,从而避免了频繁的调用系统函数,节省了资源开销与时间
- 使用进程池有利于管理,并充分利用系统资源
实现进程池:
进程池程序myprocesspool
的程序流程大致如下:
实现代码:
task.hpp:
#pragma once#include<iostream> #include<unistd.h>typedef void(*work_t)(pid_t); typedef void(*task_t)(pid_t);void task_1(pid_t id) {std::cout << "task_1" << std::endl; }void task_2(pid_t id) {std::cout << "task_2" << std::endl; }void task_3(pid_t id) {std::cout << "task_3" << std::endl; }task_t tasks[3] = {task_1, task_2, task_3};void worker(int channel_index) {while(1){int task_index = 0;int n = read(0, &task_index, sizeof(int));if (0 == n){std::cout << "write close, channel: " << channel_index << " closing…………" << std::endl; break;}std::cout << "channel: " << channel_index << " pid: " << getpid() << ": i am working: ";tasks[task_index](getpid());} }
processpool.cc:
#include <iostream> #include <string> #include <vector> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <ctime> #include "task.hpp"#define TIME 5//return value enum {USAGE_ERROR = 1,PROCESS_NUM_ERROR, PIPE_ERROR,SELECT_CHANNEL_ERROR,SELEDT_TASK_ERROR };void usage() {std::cout << "Usage: ./processpool [process_num]" << std::endl; }class channel { public:channel(std::string name, int rfd, int wfd, pid_t child_id): _name(name), _rfd(rfd), _wfd(wfd), _child_id(child_id){}const std::string& get_name(){return _name;}int get_rfd(){return _rfd;}int get_wfd(){return _wfd;}pid_t get_child_id(){return _child_id;}void close_fd(){close(_wfd);} private:const std::string _name;const int _rfd;const int _wfd;const pid_t _child_id; };class processpool { public:processpool(int process_num): _process_num(process_num){}//创建管道int create_channel(){std::vector<int> fd;for (int i = 0; i < _process_num; i++){int pipefd[2] = {0};int ret = pipe(pipefd);if (-1 == ret){perror("pipe:");return PIPE_ERROR;}pid_t id = fork();if (id == 0){//child: readclose(pipefd[1]);dup2(pipefd[0], 0);//关闭除第一个子进程外的多个写端if (!fd.empty()){for (auto k : fd)close(k);}//workworker(i);std::cout << "channel " << i << " pid: " << getpid() << " exit" << std::endl;exit(-1);}//father: writeclose(pipefd[0]);std::string name = std::string("channel: ") + std::to_string(i);channel c = channel(name, pipefd[0], pipefd[1], id);_channels.push_back(c);fd.push_back(pipefd[1]);}return 0;} int select_channel(){static int c = 0;int ret = c;c = (++c) % _process_num;return ret;}int select_task(){return rand() % 3;}int control_child(){int cnt = TIME;while(cnt--){//选择一个进程(管道),一个任务int channel_index = select_channel();int task_index = select_task();if (channel_index >= _process_num){std::cout << "select channel error" << std::endl;return SELECT_CHANNEL_ERROR;}if (task_index >= 3){std::cout << "select task error" << std::endl;return SELEDT_TASK_ERROR;}//发送任务int wfd = _channels[channel_index].get_wfd();write(wfd, &task_index, sizeof(int));sleep(2);}return 0;}void PrintDebug(){for (auto channel: _channels){std::cout << channel.get_name() << ": rfd: " << channel.get_rfd() << ": child_id: " << channel.get_child_id() << std::endl;}}void clean_wait(){//关闭所有的写端for (auto channel : _channels){channel.close_fd();pid_t id = waitpid(channel.get_child_id(), nullptr, 0);if (-1 == id){perror("waitpid:");}if(id == channel.get_child_id()){std::cout << "wait child: " << channel.get_child_id() << " success\n" << std::endl;}}} private:const int _process_num;std::vector<channel> _channels; };// ./process num int main(int argc, char* argv[]) {if (1 == argc){usage();return USAGE_ERROR;}int process_num = std::stoi(argv[1]);if (process_num <= 0){std::cout << "process_num should be grearter than 0" << std::endl;return PROCESS_NUM_ERROR;}srand((unsigned int)time(nullptr));std::cout << "process nums: " << process_num << std::endl;//创建进程池processpool* processpool_1 = new processpool(process_num);//创建管道int ret = processpool_1->create_channel(); if (0 != ret){return ret;}std::cout << "create channels complete" << std::endl; //工作ret = processpool_1->control_child();if (0 != ret){return ret;}//回收 processpool_1->clean_wait();return 0; }
2.2 命名管道
上文提到,匿名管道只适用于血缘进程之间的通信,那么为了解决没有关系进程间通信的问题,操作系统提供了命名管道
2.2.1 创建命名管道
使用命令创建命名管道:
mkfifo [filename]
例如:
可以看到,通过命令mkfifo fifo
在当前目录创建了一个名为fifo
的管道文件,同时可以看到,这个管道文件的文件类型为p(pipe)
使用系统调用创建命名管道:
int mkfifo(const char *pathname, mode_t mode);
pathname
是管道文件的文件名mode
是管道文件的权限- 返回值:成功返回0;失败返回-1
2.2.2 利用命名管道进行进程间通信
知道了系统调用后,我们就可以利用管道来进行进程间的通信了:
我们用下面的代码进行演示:
头文件:
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#define PATH "FIFO" //默认文件名
#define MODE 0666 //默认权限//命名管道类
class fifo
{
public:fifo(const std::string& name) //构造函数创建管道文件: _name(name){int ret = mkfifo(_name.c_str(), MODE);if (-1 == ret){std::cerr << "mkfifo error, " << "errno: " << errno << ", errorstring: " << strerror(errno) << std::endl;exit(-1);}std::cout << "fifo made success" << std::endl;}~fifo(){unlink(_name.c_str()); //进程结束时,利用系统调用unlink()删除管道文件}
private:const std::string _name;
};
服务端server:读
#include "common.hpp"int main()
{std::cout << "I am server" << std::endl; fifo named_pipe(PATH); //服务端创建管道文件,读端int rfd = open(PATH, O_RDONLY); char buffer[1024] = {0};while (1){int n = read(rfd, buffer, sizeof(buffer) - 1);if (0 == n) //返回值为0,说明读到文件尾,即写端关闭{std::cout << "client exit, server also exit" << std::endl;break;}else if (-1 == n){std::cerr << "read error, " << "errno: " << errno << ", errorstring: " << strerror(errno) << std::endl;exit(-1);}else{buffer[n] = 0;std::cout << buffer << std::endl;}}close(rfd);return 0;
}
客户端client:写
#include "common.hpp"int main()
{std::cout << "I am client" << std::endl;int wfd = open(PATH, O_WRONLY); //客户端,写std::string buffer;while(1){printf("message # ");std::getline(std::cin, buffer);if ("quit" == buffer){printf("client exit\n");break;}int n = write(wfd, buffer.c_str(), buffer.size());if (n != buffer.size()){std::cerr << "write error, " << "errno: " << errno << ", errorstring: " << strerror(errno) << std::endl;exit(-1);}}close(wfd);return 0;
}
效果如图:
2.2.3 命名管道的一个特性
如果我们在服务端server.cc
代码中加一行代码:
#include "common.hpp"int main()
{std::cout << "I am server" << std::endl;fifo named_pipe(PATH);int rfd = open(PATH, O_RDONLY);std::cout << "open success" << std::endl; //查看客户端是否成功打开管道文件//……………………}
并运行服务端(读端)一段时间后再打开客户端(写端):
可以看到:在服务端(读端)打开到客户端(写端)未打开的这段时间中,服务端(读端)并没有打开管道文件,而是等客户端(写端)启动后,再打开的管道。
通过这个现象,我们可以得出命名管道的一个特性:
当写端未打开而读端打开时,读端会阻塞,直至写端也打开
本篇完
相关文章:

Linux——进程间通信之管道
进程间通信之管道 文章目录 进程间通信之管道1. 进程间通信1.1 为什么要进行进程间的通信1.2 如何进行进程间的通信1.3 进程间通信的方式 2. 管道2.1 匿名管道2.1.1 系统调用pipe()2.1.2 使用匿名管道进行通信2.1.1 匿名管道四种情况2.1.2 匿名管道的五大特性2.1.3 进程池 2.2 …...

java-排序算法汇总
排序算法: 冒泡排序(Bubble Sort) 选择排序(Selection Sort) 插入排序(Insertion Sort) 快速排序(Quick Sort) 归并排序(Merge Sort) 堆排序&…...

Vscode进行Java开发环境搭建
Vscode进行Java开发环境搭建 搭建Java开发环境(Windows)1.Jdk安装2.VsCode安装3.Java插件4.安装 Spring 插件5.安装 Mybatis 插件5.安装Maven环境6.Jrebel插件7.IntelliJ IDEA Keybindings8. 收尾 VS Code(Visual Studio Code)是由微软开发的一款免费、开…...

算法学习笔记(五):二叉树一遍历、DFS
一.遍历二叉树 二叉树TreeNode类 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, Tree…...
#Verilog HDL# Verilog中的generate用法集锦
生成块允许复制模块实例或有条件地实例化任何模块。它提供了基于Verilog参数构建设计的能力。当相同的操作或模块实例需要重复多次,或者当某些代码需要根据给定的Verilog参数有条件地包含时,这些语句特别方便。 生成块不能包含端口、参数、specparam声明或指定块。但是,允许…...

简述C++map容器
pair键值对 std::pair在很多关联容器(如std::map、std::multimap、std::set、std:multiset等)中被广泛应用。以std::map为例,std::map是一个键值对的容器,其中每个元素都是一个std::pair,键用于唯一标识元…...
Vue 学习随笔系列十七 -- 表格样式修改
表格样式修改 文章目录 表格样式修改一、表格背景颜色修改1、方法一2、方法二 二、多级表头颜色修改 一、表格背景颜色修改 1、方法一 表格外套一个 div ,修改div的背景色,并将表格背景色设置为透明 参考代码: <template><div cl…...

08 —— Webpack打包图片
【资源模块 | webpack 中文文档 | webpack中文文档 | webpack中文网】https://www.webpackjs.com/guides/asset-modules/?sid_for_share99125_3 Webpack打包图片以8KB为临界值判断 大于8KB的文件:发送一个单独的文件并导出URL地址 小于8KB的文件:导出一…...

01.Django快速入门
一、Django 快速入门 使用最新版本 Django4.2LTS 版本,3 年内不需要更换版本由浅入深讲解,浅显易懂课程大纲全面包含 Django 框架知识点,内容丰富全面细致知识点结合项目实战实现全栈项目应用 Django 官网(文档): https://docs.djangoproject.com/zh-h…...

【大数据学习 | Spark-Core】spark-shell开发
spark的代码分为两种 本地代码在driver端直接解析执行没有后续 集群代码,会在driver端进行解析,然后让多个机器进行集群形式的执行计算 spark-shell --master spark://nn1:7077 --executor-cores 2 --executor-memory 2G sc.textFile("/home/ha…...
Modern Effective C++ Item 14 如果函数不抛出异常请使用noexcept
C11 noexcept关键字用于指定函数不会抛出异常,有助于提高程序的异常安全性,还能够使编译器生成更加高效的代码。 noexcept 是函数接口的一部分 函数是否声明为 noexcept 是接口设计的一部分,客户端代码可能会依赖这一点。如果一个函数被声明…...
cudatoolkit安装(nvcc -V错误版本解决)
CudaToolKit安装(nvcc) cudatoolkit 是 CUDA 开发工具包(CUDA Toolkit) 的核心部分,包含了一系列用于开发和运行 CUDA 应用程序的软件组件。nvcc 是 NVIDIA CUDA 编译器驱动,用于将 CUDA C/C 代码编译成可…...
DTO和VO的区别及使用场景详解
随着互联网的发展,前后端分离的开发模式越来越流行。在前后端数据交互过程中,为了保证数据的安全性和效率,通常会采用 DTO 和 VO 来封装数据。本篇博客将详细介绍 DTO 和 VO 的区别以及使用场景。 大家可能会有个疑问,既然DTO是展…...

百度在下一盘大棋
这两天世界互联网大会在乌镇又召开了。 我看到一条新闻,今年世界互联网大会乌镇峰会发布“2024 年度中国互联网企业创新发展十大典型案例”,百度文心智能体平台入选。 这个智能体平台我最近也有所关注,接下来我就来讲讲它。 百度在下一盘大棋…...
第十六届蓝桥杯模拟赛第二期题解—Java
第十六届蓝桥杯模拟赛/校赛第二期个人题解,有错误的地方欢迎各位大佬指正 问题一(填空题) 【问题描述】 如果一个数 p 是个质数,同时又是整数 a 的约数,则 p 称为 a 的一个质因数。 请问, 2024 的最大的质因数是多少? …...
驱动开发笔记:关于3588GPIO
1.概要 2.内容 1.3588GPIO 关于RK3588的GPIO(General-Purpose Input/Output,通用输入输出引脚),以下是一些关键信息和操作指南: 一、GPIO基本概念 定义:GPIO是嵌入式系统中常见的通信接口,…...
【RK3588 Linux 5.x 内核编程】-内核线程与Mutex
内核线程与Mutex 文章目录 内核线程与Mutex1、Mutex介绍1.1 竞争条件1.2 Mutex特性2、Linux内核中的Mutex2.1 初始化Mutex2.1.1 静态方式初始化2.1.2 动态方式初始化2.2 互斥锁获取2.3 互斥锁释放3、Mutex使用示例4、驱动验证在前面的文章中,介绍了如何Linux内核中的线程,但是…...
【0342】分配并初始化 Proc Signal 共享内存 (1)
1. Proc Signal (procsignal)共享内存 Postgres内核在启动postmaster守护进程时候, 会通过函数 ProcSignalShmemInit() 去为 Proc Signal 分配并初始化指定大小的共享内存空间。整个调用链路如下。 (gdb) bt #0 ProcSignalShmemInit () at procsignal.c:118 #1 0x000000000…...

管家婆财贸ERP BR035.回款利润明细表
最低适用版本: 财贸系列 23.5 插件简要功能说明: 报表统计销售单/销售退货单/销售发票回款情况更多细节描述见下方详细文档插件操作视频: 进销存类定制插件--回款利润明细表 插件详细功能文档: 1. 应用中心增加报表【回款利润明细表】 a. b. 查询条件: ⅰ. 日期区间:…...

数据库MYSQL——表的设计
文章目录 前言三大范式:几种实体间的关系:一对一关系:一对多关系:多对多关系: 前言 之前的博客中我们讲解的是关于数据库的增删改查与约束的基本操作, 是在已经创建数据库,表之上的操作。 在实…...

label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...