System-V共享内存和基于管道通信实现的进程池

文章目录
- 一.进程间通信:
- 进程间通信的本质:
- 二.Linux管道通信
- 匿名管道:
- 关于管道通信的要点:
- 基于匿名管道构建进程池:
- 三.System-V共享内存
- 共享内存和命名管道协同通信
参考Linux内核源码版本------linux-2.4.3
一.进程间通信:
- 操作系统中,为了保证安全性,进程之间具有严格的独立性(独立的
PCB,独立的虚拟地址空间mm_struct和页表…等各种独立的系统资源),即便是父子进程之间也通过数据的写时拷贝保证了两者之间的的数据独立性.因此要实现进程间通信和任务协作,就要让不同进程的共同读写同一份信息资源.由于违背了进程独立性的原则,要实现进程间共享资源就需要一定的技术成本. - 进程间的独立性:

进程间通信的本质:
- 不同的进程对同一块内存资源进行的一系列的读写操作.
二.Linux管道通信
- 将同一个管道文件的文件结构体指针分别填入两个进程的文件信息列表(通过父子进程的继承关系或者
open接口实现),之后两个进程便可以对管道文件的内核级读写缓冲区(本质上是一块内存)进行读写操作实现通信. - 管道通信是一种单向通信手段,有固定的读端进程和写端进程.
匿名管道:
- 匿名管道通信是父子进程间通信的一种方式.
- 匿名管道通信机制图解:



- 内核视角下管道文件结构体内部结构:

关于管道通信的要点:
- 管道通信可用于进程间协同,提供访问控制(同步与互斥):
- 管道读写端正常,如果管道中缓冲区为空,则读端进程进入阻塞状态
- 管道读写端正常,如果管道中缓冲区被写满,则写端进程进入阻塞状态
- 管道写端先关闭,管道读端
read接口返回0,标识读取结束 - 管道读端先关闭,操作系统会终止写端进程.
基于匿名管道构建进程池:

Task.hpp模拟任务列表
#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>//重定义函数指针
typedef void (*task_t)();void task1()
{std::cout << "执行任务1:矩阵计算" << std::endl;
}
void task2()
{std::cout << "执行任务2:pid控制算法" << std::endl;
}
void task3()
{std::cout << "执行任务3:图像计算" << std::endl;
}
void task4()
{std::cout << "执行任务4:人脸识别算法" << std::endl;
}//向数组中加载任务
void LoadTask(std::vector<task_t> &tasks)
{tasks.push_back(task1);tasks.push_back(task2);tasks.push_back(task3);tasks.push_back(task4);
}
#include "Task.hpp"
using namespace std;
#define ChildNum 5//子进程信息结构体
class Channel
{
public: Channel(){}Channel(const string & Name,pid_t Childpid,int Pipefd): _Childpid(Childpid),_Pipefd(Pipefd),_Name(Name){}pid_t getpid() const {return _Childpid;}string PrintName() const {return _Name;}int Getfd() const {return _Pipefd;}
private:pid_t _Childpid;int _Pipefd; //保存管道的写入端string _Name;
};//子进程任务执行函数
void slaver(const vector<task_t>&Taskarr)
{//任务码int CommandCode = 0;while(true){int check = read(0,&CommandCode,sizeof(CommandCode));//若管道中没有数据且写入段没有关闭,子进程就会阻塞assert(check!=-1);if(check > 0){std::cout <<"slaver say@ get a command: "<< getpid() << " : CommandCode: " << CommandCode << std::endl;//子进程解析并执行命令if(CommandCode < 0 || CommandCode >= Taskarr.size()){cout << "CommandCode Error! slaver exit!" << endl;exit(0);}//子进程根据任务码执行任务Taskarr[CommandCode]();}else {//一旦父进程关闭管道写入端,check就会接收到0,子进程退出break;}}
}//父进程向子进程发送任务的接口
void ctrlSlaver(const std::vector<Channel> & channels,const vector<task_t>&Taskarr)
{int count = 10;while(count--){sleep(1);//随机选择子进程发送任务码int choseSlaver = rand()%channels.size();int Task = rand()%Taskarr.size();cout << "父进程向子进程" << channels[choseSlaver].PrintName() << "写入命令:" << Task << endl;write(channels[choseSlaver].Getfd(),&Task,sizeof(Task));}sleep(1);cout << "\n所有任务执行完毕,系统准备退出\n" << endl;sleep(2);
}//构建进程池接口
void InitProcessPool(vector<Channel>& ChildProc,const vector<task_t>&Taskarr)
{for(int i = 0; i < ChildNum; ++i){int pipefd[2];int check = pipe(pipefd);assert(!check); (void)check;pid_t pid = fork();assert(pid != -1);//父进程写 子进程读if(pid == 0){//子进程执行流close(pipefd[1]);//将stdin对应文件指针修改为管道的读入端dup2(pipefd[0],0);//将文件信息列表中对应的指针位置空close(pipefd[0]);slaver(Taskarr);close(0);exit(0);}close(pipefd[0]);//将管道的写入端存入channel对象中ChildProc.push_back(Channel(string("Process ") + to_string(pid),pid,pipefd[1])); }
}//父进程轮询等待子进程退出
void WaitChildProc(const std::vector<Channel> & channels)
{//先关闭各个管道的写入端,相应的子进程会自动退出for(auto& e : channels){close(e.Getfd());}//等待各个子进程退出for(auto & e : channels){int Status = 0;waitpid(e.getpid(),&Status,0);cout << "写入端关闭,子进程:" << e.getpid() << "退出,退出码:"<< WIFEXITED(Status) << endl;}
}int main()
{vector<task_t>Taskarr;LoadTask(Taskarr);srand(time(nullptr)^getpid()^1023);vector<Channel> ChildProc;InitProcessPool(ChildProc,Taskarr);ctrlSlaver(ChildProc,Taskarr);WaitChildProc(ChildProc);return 0;
}

- 命名管道和匿名管道的内核原理相同
三.System-V共享内存
- 共享内存通信原理:

- 构建共享内存通信环境的系统接口:
int shmget(key_t key, size_t size, int shmflg);key是用户自定义共享内存标识键,用ftok接口获取size是申请共享内存的大小shmflg:取IPC_CREAT时,接口可以申请共享内存并获取共享内存的key,若参数指定的共享内存已存在则直接返回共享内存的key;取IPC_CREAT | IPC_EXCL时,接口只能用于申请新的共享内存.
void *shmat(int shmid, const void *shmaddr, int shmflg);- 接口作用:在当前进程的虚拟地址空间的共享区中为指定的共享内存块编址,并建立页表映射.
int shmdt(const void *shmaddr);- 接口作用:共享内存块与当前进程的虚拟地址空间取消关联,进程将无法再访问指定的共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);- 接口作用:对共享内存块进行
cmd码指定的控制操作(比如释放操作),也可以用于获取共享内存块在内核中的描述信息
- 接口作用:对共享内存块进行
- 共享内存通信环境中,由于多个进程可以对同一个内存块直接进行读写操作,因此,共享内存通信缺少同步互斥机制,无法保证数据的读写安全,为此,可以借助命名管道为共享内存通信提供读写控制.
共享内存和命名管道协同通信
- 构建通信环境的接口头文件:
#ifndef __COMM_HPP__
#define __COMM_HPP__#include "log.hpp"
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>using namespace std;
const int SIZE = 4096;
const string pathname = "/home/user1/LinuxLearning/sharedMEM";
const int pro_id = 0x123456;log LOG;//获取自定义共享内存key
key_t GetKey(const string pathname,const int pro_id)
{//KEY生成器key_t K = ftok(pathname.c_str(),pro_id);if(K < 0){LOG(Fatal,"GetKey Error, message: %s\n",strerror(errno));exit(-1);}LOG(Info,"key generated, message: %s\n",strerror(errno));return K;
}//调用系统接口申请共享内存
int GetShareMemHelper(int flag)
{key_t KEY = GetKey(pathname,pro_id);//系统调用接口shmget申请共享内存或返回已存在的共享内存idint shmid = shmget(KEY,SIZE,flag);if(shmid == -1){LOG(Fatal,"Get ShareMem failed, message: %s\n",strerror(errno));exit(-1);}LOG(Info,"Get ShareMem completed, message: %s\n",strerror(errno));return shmid;
}//申请新的共享内存
int CreateShm()
{return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}//获取已存在的共享内存的id
int GetShm()
{return GetShareMemHelper(IPC_CREAT);
}#define FIFO_FILE "./myfifo"
#define MODE 0664enum
{FIFO_CREATE_ERR = 1,FIFO_DELETE_ERR,FIFO_OPEN_ERR
};class Init
{
public:Init(){// 创建管道int n = mkfifo(FIFO_FILE, MODE);if (n == -1){perror("mkfifo");exit(FIFO_CREATE_ERR);}}~Init(){//管道去链接,若引用计数为0则删除管道文件int m = unlink(FIFO_FILE);if (m == -1){perror("unlink");exit(FIFO_DELETE_ERR);}}
};#endif
- 自制日志类log:
#pragma once
#include <time.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
#include <cstdio>
#include <cstring>
#include <cassert>
#include <unistd.h>
#include <stdlib.h>//日志等级
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
//日志写入方式
#define Screen 1
#define Onefile 2
#define Classfile 3
#define LogFile "log.txt"using std :: string;
class log
{
public:log(){printMethod = Screen;path = "./log/";}void Enable(int method){printMethod = method;}string LeveltoString(int level){switch (level){case 0:return string("Info");break;case 1:return string("Debug");break;case 2:return string("Warning");break;case 3:return string("Error");break;case 4:return string("Fatal");break;default:break;}}void operator()(int level,char * format,...){//将时间格式化存入tm结构体中time_t t = time(nullptr);struct tm* ctime = localtime(&t);char leftbuffer[1024];snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%d-%d-%d %d:%d:%d]",LeveltoString(level).c_str(),ctime->tm_year+1900,ctime->tm_mon+1,ctime->tm_mday,ctime->tm_hour,ctime->tm_min,ctime->tm_sec);//解析可变参数va_list vls;va_start(vls,format);char rightbuffer[1024];vsnprintf(rightbuffer,sizeof(rightbuffer),format,vls);va_end(vls);//合并时间和可变参数char logtxt[2048];snprintf(logtxt,sizeof(logtxt),"%s %s\n",leftbuffer,rightbuffer);//执行日志记录printLog(level,string(logtxt));}//日志信息写出接口void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen://将日志信息打印到标准输出std::cout << logtxt << std::endl;break;case Onefile://将日志信息存入log.txtprintOneFile(LogFile, logtxt);break;case Classfile://将日志信息存入指定的分类日志文件printClassFile(level, logtxt);break;default:break;}}//日志信息写到log.txt中void printOneFile(const std::string &logname, const std::string &logtxt){//path-->日志保存路径 logname-->日志文件名std::string _logname = path + logname;//打开"log.txt"日志文件int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); if (fd < 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}//日志信息写到log.txt.level中void printClassFile(int level, const std::string &logtxt){//对日志文件名进行修改,根据日志等级分出多个日志文件std::string filename = LogFile;filename += ".";// "log.txt.Debug(Warning)(Fatal)"filename += LeveltoString(level); printOneFile(filename, logtxt);}~log(){}
private:int printMethod;std::string path;
};
- 读端进程示例:
#include "log.hpp"
#include "ShareMemBuild.hpp"extern log LOG;int main()
{//创建管道和共享内存Init pipeCreate;int shmid = CreateShm();//建立共享内存与进程虚拟地址空间之间的映射,并获取共享内存的虚拟地址char * shmaddr = (char *)shmat(shmid,NULL,0);//打开管道文件int fd = open(FIFO_FILE,O_RDONLY);if(fd == -1){LOG(Fatal, "error string: %s, error code: %d", strerror(errno), errno);exit(FIFO_OPEN_ERR);}while(true){//借助管道进行共享内存的读写控制,若写端没有给信号,则读端保持阻塞状态char c;int RSize = read(fd,&c,sizeof(c));if(RSize <=0) break;//直接访问共享内存,实现高效通信cout << "client say@ " << shmaddr << endl; sleep(1);}//进程与共享内存断开连接shmdt(shmaddr);//将共享内存标记为已销毁shmctl(shmid,IPC_RMID,nullptr);close(fd);return 0;
}
- 写端进程示例:
#include "log.hpp"
#include "ShareMemBuild.hpp"extern log LOG;int main()
{ //获取共享内存标识int shmid = GetShm();//建立共享内存与进程虚拟地址空间之间的映射,并获取共享内存的虚拟地址char * shmaddr = (char *)shmat(shmid,NULL,0);//打开管道文件int fd = open(FIFO_FILE,O_WRONLY);if(fd == -1){LOG(Fatal, "error string: %s, error code: %d", strerror(errno), errno);exit(FIFO_OPEN_ERR);}while(true){cout << "Please Enter@ ";//将信息写入共享内存fgets(shmaddr, 4096, stdin);//管道写入信号,解除读端的阻塞状态write(fd, "c", 1); }//进程与共享内存断开连接shmdt(shmaddr);close(fd);return 0;
}

- 多个进程直接通过各自的虚拟地址空间对同一个内存块进行访问使得共享内存通信具有很高的通信效率.管道通信过程中,数据至少要经过两次拷贝(用户读写缓冲区和内核读写缓冲区之间的拷贝),而共享内存通信不存在通信数据拷贝问题
- 共享内存,消息队列,信号量等通信内存资源(称为
ipc资源)统一由操作系统描述为各种数据结构统一进行管理,在Linux内核中,描述共享内存,消息队列,信号量的结构体形成继承体系:(C语言实现的继承体系)

相关文章:
System-V共享内存和基于管道通信实现的进程池
文章目录 一.进程间通信:进程间通信的本质: 二.Linux管道通信匿名管道:关于管道通信的要点:基于匿名管道构建进程池: 三.System-V共享内存共享内存和命名管道协同通信 参考Linux内核源码版本------linux-2.4.3 一.进程间通信: 操作系统中,为了保证安全性,进程之间具有严格的独…...
Python武器库开发-前端篇之CSS基本语法(三十)
前端篇之CSS基本语法(三十) CSS简介 CSS(层叠样式表)是一种用于描述网页外观和布局的样式表语言。它与 HTML 一起,帮助开发者对网页进行美化和布局。CSS通过定义网页元素的颜色、字体、大小、背景、边框等属性,使网页变得更加美…...
微信小程序实现类似Vue中的computed、watch功能
微信小程序实现类似Vue中的computed、watch功能 构建npm使用 构建npm 创建包管理器 进入小程序后,打开终端,点击顶部“视图” - “终端” 新建终端 使用 npm init -y初始化包管理器,生成一个package.json文件 安装 npm 包 npm install --…...
[JVM] 美团二面,说一下JVM数据区域
Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。这些区域有不同的用途。 文章目录 线程私有的数据区域1. 程序计数器2. Java 虚拟机栈3. 本地方法栈 线程共享的数据区域1. Java 堆2. 方法区3. 运行时常量池4. 直接内存 线程私有的数据区域 …...
【React】useReducer
让 React 管理多个相对关联的状态数据 import { useReducer } from "react" // 1. 定义reducer函数,根据不同的action返回不同的状态 function reducer(state, action) {switch (action.type) {case ADD:return state action.payloadcase SUB:return st…...
leetcode刷题详解二
160. 相交链表 本质上是走过自己的路,再走过对方的路,这是求两个链表相交的方法 ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {//本质上是走过自己的路,再走过对方的路if(headA NULL|| headB NULL){return NULL;}Lis…...
利用opencv/暗通道方法检测图像是否有雾-利用opencv/暗通道方法对深度学习目标检测算法结果进行二次识别提高准确率
目录 1 Python版本 2 C++版本 本来利用yolov5检测浓雾的,但是发现yolov5的检测结果会把一些正常天气检测成雾天,这种时候其实可以通过增加正常类,也就是将正常天气被误检成浓雾的图片当成一个正常类别去训练,但是不想标注图片,也不想重新训练算法了,因此想是不是可以用…...
Linux | 重定向 | 文件概念 | 查看文件 | 查看时间 | 查找文件 | zip
Linux | 重定向 | 文件概念 | 查看文件 | 查看时间 | 查找文件 | zip 文章目录 Linux | 重定向 | 文件概念 | 查看文件 | 查看时间 | 查找文件 | zip一、more1.1 输出重定向>和>>1.2 输入重定向< 二、 再谈一切皆文件三、less指令【重要】四、head指令五、tail指令…...
【广州华锐互动】利用VR体验环保低碳生活能带来哪些教育意义?
随着科技的不断发展,虚拟现实(VR)技术已经逐渐走进了我们的生活。从游戏娱乐到教育培训,VR技术的应用范围越来越广泛。而在这个追求绿色、环保的时代,VR技术也为我们带来了一种全新的环保低碳生活方式。让我们一起走进…...
python爬虫中 HTTP 到 HTTPS 的自动转换
前言 在当今互联网世界中,随着网络安全的重要性日益增加,越来越多的网站采用了 HTTPS 协议来保护用户数据的安全。然而,许多网站仍然支持 HTTP 协议,这就给我们的网络爬虫项目带来了一些挑战。为了应对这种情况,我们需…...
卷积神经网络(CNN)识别验证码
文章目录 一、前言二、前期工作1. 设置GPU(如果使用的是CPU可以忽略这步)2. 导入数据3. 查看数据4.标签数字化 二、构建一个tf.data.Dataset1.预处理函数2.加载数据3.配置数据 三、搭建网络模型四、编译五、训练六、模型评估七、保存和加载模型八、预测 …...
使用 PyODPS 采集神策事件数据
文章目录 一、前言二、数据采集、处理和入库2.1 获取神策 token2.2 请求神策数据2.3 数据处理-面向数组2.4 测试阿里云 DataFrame 入库2.5 调度设计与配置2.6 项目代码整合 三、小结四、花絮-避坑指南第一坑:阿里云仅深圳节点支持神策数据第二坑:神策 To…...
罗技M590鼠标usb优联连接不上
手里有一个罗技M590鼠标从18年4月一直用到现在,质量很好,除了滚轮有些松别的没毛病。最近一台笔记本电脑办公不太够用,又领了一个台式机,就想到M590支持双模连接,并且支持Flow,就把usb优联接收器从电池仓拿…...
天池 机器学习算法(一): 基于逻辑回归的分类预测
pytorch实战 课时7 神经网络 MSE的缺点:偏导值在输出概率值接近0或者接近1的时候非常小,这可能会造成模型刚开始训练时,偏导值几乎消失,模型速度非常慢。 交叉熵损失函数:平方损失则过于严格,需要使用更合…...
45岁后,3部位“越干净”,往往身体越健康,占一个也要恭喜!
众所周知,人的生命有长有短,而我们的身体健康状态,也同样会受到年龄的影响,就身体的年龄层次而言,往往需要我们用身体内部的干净程度来维持,换句话说就是:若是你的身体内部越干净,那…...
Windows安装Hadoop运行环境
1、下载Hadoop 2、解压Hadoop tar zxvf hadoop-3.1.1.tar.gz3、设置Hadoop环境变量 3.1.1、系统环境变量 # HADOOP_HOME D:\software\hadoop-3.1.13.1.2、Path 环境变量 %HADOOP_HOME%\bin %HADOOP_HOME%\sbin3.1.3、修改Hadoop文件JAVA_HOME 注 : 路径中不要出现空格 ,…...
软件测试 | MySQL 主键约束详解:保障数据完整性与性能优化
📢专注于分享软件测试干货内容,欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!📢交流讨论:欢迎加入我们一起学习!📢资源分享:耗时200小时精选的「软件测试」资…...
深入了解Linux中的scp命令及高级用法
Linux操作系统中,scp(Secure Copy Protocol)命令是一个用于在本地系统和远程系统之间安全复制文件的强大工具。通过基于SSH的加密通信,scp提供了安全的文件传输方式。在本文中,我们将深入介绍scp命令的基本语法以及一些…...
moviepy 视频剪切,拼接,音频处理
官网 使用matplotlib — moviepy-cn 文档 案例 from moviepy.editor import * from moviepy.video.fx import resize from PIL import Imagefile1r"D:\xy_fs_try\video_to_deal\spider_video\file\vedeo3.mp4" file2r"D:\xy_fs_try\video_to_deal\spider_video\…...
ubuntu搭建phpmyadmin+wordpress
Ubuntu搭建phpmyadmin wordpress Linux系统设置:Ubuntu 22配置apache2搭建phpmyadmin配置Nginx环境,搭建wordpress Linux系统设置:Ubuntu 22 配置apache2 安装apache2 sudo apt -y install apache2设置端口号为8080 sudo vim /etc/apache…...
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
LangFlow技术架构分析
🔧 LangFlow 的可视化技术栈 前端节点编辑器 底层框架:基于 (一个现代化的 React 节点绘图库) 功能: 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...
