当前位置: 首页 > article >正文

【进程间通信】————匿名管道、模拟实现进程池

目录1. 进程间通信1.1 进程间通信的目的1.2 进程间通信分类2. 管道3. 匿名管道3.1 pipe函数3.2 用 fork 来共享管道原理3.3 从文件描述符角度理解3.4 从内核角度理解3.5 父子进程管道读写测试3.6 管道特性3.7 4种通信情况3.8 管道的原子性4. 进程池4.1 需要的几个类ChannelChannelManagerTaskManagerProcessPool4.2 主逻辑4.2.1 创建进程池对象4.2.2 启动进程池Start4.2.3 派发任务Run4.2.4 回收结束进程池Stop4.3 执行结果4.4 代码还存在的bugbug代码打印查看每个子进程继承的写端4.5 解决方法方法一倒着关闭管道方法二只让父进程指向所有管道的写端1. 进程间通信1.1 进程间通信的目的数据传输⼀个进程需要将它的数据发送给另⼀个进程资源共享多个进程之间共享同样的资源。通知事件⼀个进程需要向另⼀个或⼀组进程发送消息通知它它们发⽣了某种事件如进 程终⽌时要通知⽗进程。进程控制有些进程希望完全控制另⼀个进程的执⾏如Debug进程此时控制进程希望能够 拦截另⼀个进程的所有陷⼊和异常并能够及时知道它的状态改变。1.2 进程间通信分类匿名管道pipe命名管道2. 管道什么是管道管道是Unix中最古⽼的进程间通信的形式。我们把从⼀个进程连接到另⼀个进程的⼀个数据流称为⼀个“管道”3. 匿名管道匿名管道本质上是由内核管理的临时文件它不占用磁盘空间可即时创建和释放3.1 pipe函数#includeunisdt.h int pipe(int fd[2]);pipe函数作用创建一个匿名管道 fd[0] 表示读端fd[1] 表示写端 成功返回0失败返回错误代码。实例代码下面的代码的逻辑就是创建一个管道、从键盘读取内容、将内容写入管道、从管道中读取刚写入的内容、将读取到的内容输出至屏幕上。C语言字符串处理接口通常会自动处理\0终止符。系统调用位于更底层不涉及字符串操作因此需要手动为\0预留空间#include stdio.h #include stdlib.h #include string.h #include unistd.h int main() { int fds[2]; char buffer[100]; char msg[100]; // 创建管道从键盘读取内容写道管道中然后读取管道中的内容 // 创建管道 if (pipe(fds) 0) { perror(pipe); exit(1); } while (fgets(buffer, 100, stdin)) // 从键盘读取内容 { // 写道管道中 int len strlen(buffer); if (write(fds[1], buffer, len) 0) { perror(write); exit(1); } // 清空缓冲区 memset(buffer, 0, sizeof(buffer)); // 读取管道中的内容 if (read(fds[0], msg, sizeof(msg) - 1) 0) { perror(read); exit(1); } // 输出到屏幕上 if (write(1, msg, strlen(msg)) 0) { perror(write); exit(1); } // 清空缓冲区 memset(msg, 0, sizeof(msg)); } return 0; }3.2 用 fork 来共享管道原理文件描述符 fd[0] 和 fd[1] 分别对应管道的读端和写端。父进程通过 fork() 创建子进程时子进程会继承父进程的文件描述符表。为了实现单向通信如父进程读、子进程写父子进程需要各自关闭不需要的文件描述符父进程关闭读端 fd[1]子进程关闭写端 fd[0]。3.3 从文件描述符角度理解3.4 从内核角度理解3.5 父子进程管道读写测试创建管道、创建子进程、关闭父子各自不需要的文件描述符、父读子写、关闭剩下的文件描述符、回收子进程waitpid获取退出信息退出码、退出信号#include iostream #include unistd.h #include sys/types.h #include cstring #include sys/wait.h using namespace std; void ChildWrite(int wfd) { char c A; int cnt 0; while (true) { write(wfd, c, 1); // 一次写一个字符 printf(child:%d\n, cnt); sleep(1); } } void FatherRead(int rfd) { char buffer[1024]; int cnt4; while (cnt--) { // 系统调用不关系\0,需要我们自己预留空间 ssize_t n read(rfd, buffer, sizeof(buffer) - 1); // n0,表述写端退出管道里没数据时会阻塞等待 if (n 0) { buffer[n] 0; // 加上\0 cout child say: buffer endl; sleep(1); } else if (n 0) { cout n: n endl; cout 写端关闭,我也关闭 endl; break; } else break; } } int main() { // 创建管道 int pipefd[2]; int n pipe(pipefd); if (n 0) { cout pipe error endl; return 1; } cout pipefd[0]: pipefd[0] endl; cout pipefd[1]: pipefd[1] endl; // 创建子进程 int subid fork(); if (subid 0) { cout fork error endl; return 1; } if (subid 0) { // 子进程 // 关闭不需要的文件描述符 // f-r,z-w close(pipefd[0]); ChildWrite(pipefd[1]); // 关闭剩下的文件描述符 close(pipefd[1]); } close(pipefd[1]); FatherRead(pipefd[0]); close(pipefd[0]); // 回收子进程 int status 0; pid_t ret waitpid(subid, status, 0); if (ret 0) { printf(exit code:%d,exit signal:%d\n, (status 8) 0xff, status 0x7f); sleep(5); } return 0; }写端退出退出信号为13pipefd[0]:3 pipefd[1]:4 child:0 child say:A child:1 child say:A child:2 child say:A child:3 child say:A child:4 exit code:0,exit signal:133.6 管道特性匿名管道只能用来进行具有血缘关系的进程之间的进程通信常用于父子。管道文件自带同步机制写的慢读端阻塞写的快写端阻塞。管道是面向字节流的写入和读取的都是二进制。管道是单向通信的属于半双工的一种特殊情况。任何一个时刻一个发一个收——半双工任何一个时刻可以同时发收——全双工管道文件的生命周期是随进程的3.7 4种通信情况写慢读快——读端就要阻塞进程阻塞等写写快读慢——管道满了的时候写就要阻塞等待等读写端关闭继续读——read就会读到返回值为0表示文件结尾读端关闭继续写——写端再写入没有任何意义OS不会做没有意义的事OS会杀掉写端进程发送异常信号13号信号3.8 管道的原子性管道原子性保证较小的写操作是不可分割的。只要写入字节数 ≤ PIPE_BUFLinux 通常是 4096 字节多个进程同时 write 也不会数据交错。超过 PIPE_BUF 后原子性失效数据可能被分段、穿插。管道大小通常是64kb4. 进程池4.1 需要的几个类ChannelChannel 管道写端 子进程 PID一个子进程对应一个 Channel父进程通过 Channel 控制子进程发任务、关闭、回收Channel 就是父进程管理子进程的 “专属通信通道 控制句柄”class Channel { public: Channel(int wfd, pid_t subid) : _wfd(wfd), _subid(subid) { _name channel- to_string(_wfd) - to_string(_subid); } } ~Channel(){} private: int _wfd; pid_t _subid; string _name; };ChannelManager用来管理ChannelChannel是先描述ChannelManager是再组织。class ChannelManager { public: ChannelManager() {} ~ChannelManager() {} private: vectorChannel channels; int _next 0; // 下标用户选择进程执行任务轮询 };TaskManagerTaskManager 任务的 “仓库 调度器”专门管有哪些任务、随机选任务、执行任务。typedef void (*task_t)(); // 函数指针 class TaskManager { public: TaskManager() { srand(time(nullptr)); } void Register(task_t t) { _tasks.push_back(t); } int Code() { // 随机选取任务 return rand() % _tasks.size(); } void Execute(int code) { // 执行对应任务 _tasks[code](); } int Size() { return _tasks.size(); } ~TaskManager() {} private: vectortask_t _tasks; };ProcessPoolProcessPool就是我们的进程池了通过ChannelManager找到每个管道写端 子进程 PID从而控制子进程。class ProcessPool { public: ProcessPool(int num) : _process_num(num) { // 事先插入几个任务 _tm.Register(PrintLog); _tm.Register(DownLoad); _tm.Register(UpLoad); } ~ProcessPool() {} private: ChannelManager _cm; int _process_num;// 进程池中的进程个数 TaskManager _tm; };4.2 主逻辑创建进程池对象启动进程池给进程池中的子进程派发任务收回结束进程池。#include ProcessPool.hpp #include Task.hpp int main() { // 创建进程池对象 ProcessPool pp(gdefaultnum); // 启动进程池 pp.Start(); pp.Debug(); coutendl; // 给进程池中的子进程派发任务 int cnt 10; while (cnt--) { pp.Run(); sleep(1); } coutendl; // 回收结束进程池 pp.Stop(); return 0; }4.2.1 创建进程池对象进程池中的进程个数设为gdefaultnum然后调用 TaskManager 中的 Register 方法事先插入几个任务。hpp文件就是将头文件和实现方法写在一起ProcessPool.hpp:ProcessPool类 const int gdefaultnum 5; // 构造函数 ProcessPool(int num) : _process_num(num) { _tm.Register(PrintLog); _tm.Register(DownLoad); _tm.Register(UpLoad); } //////////////////////////////////////////// void PrintLog() { cout 我是一个打印日志的任务 endl; } void DownLoad() { cout 我是一个下载的任务 endl; } void UpLoad() { cout 我是一个上传的任务 endl; } //////////////////////////////////////////// Task.hpp:TaskManager类 void Register(task_t t) { _tasks.push_back(t); }4.2.2 启动进程池Start启动进程池就是创建num个管道个子进程。然后父子各自关闭不需要的文件描述符。子进程进入 Work 函数后会一直读取管道中的数据等待父进程发布任务。父进程会将 Channle组织起来以便后续控制子进程。bool Start() { // 创建num个管道和子进程 for (int i 0; i _process_num; i) { // 创建管道 int pipefd[2]; int n pipe(pipefd); if (n 0) { cout pipe error endl; return false; } // 创建子进程 pid_t subid fork(); if (subid 0) { cout fork error endl; return false; } else if (subid 0) { // 子进程 // 关闭不需要的文件描述符 close(pipefd[1]); Work(pipefd[0]); close(pipefd[0]); exit(0); } // 父进程 close(pipefd[0]); // 将wfd和subid管理起来 _cm.Insert(pipefd[1], subid); } return true; }workcode是任务码子进程不断从管道中读取任务码然后根据任务码找到相对应的任务并执行。管道是具有同步机制的就算管道中没有数据读端也会阻塞等待但如果写端关闭了读端调用read就会返回0表示写端关闭了。void Work(int rfd) { // 子进程一直从管道中读取内容 while (true) { int code; ssize_t n read(rfd, code, sizeof(code)); if (n 0) { if (n ! sizeof(code)) // 读取不完整 continue; cout 子进程: getpid() 接收到一个任务码 code endl; _tm.Execute(code); } else if (n 0) { // 写端退出 cout 子进程 getpid() 已退出 endl; break; } else { cout 读取失败 endl; break; } } }4.2.3 派发任务Run派发任务的逻辑就是选取任务即随机选取一个任务码选择进程轮询选择保证进程池的负载均衡发送任务父进程将任务码写入管道管道对应的子进程接收到任务码后找到相对应的任务并执行ProcessPool.hpp-ProcessPool类 void Run() { // 选择任务 int code _tm.Code(); // 选择进程 auto c _cm.Select(); // 发送任务 c.Send(code); }随机选取一个任务TaskManager类 TaskManager() { srand(time(nullptr)); } int Code() { // 随机选取任务 return rand() % _tasks.size(); }通过_next依次返回此次执行任务的进程ChannelManager类 Channel Select() { // 返回进程 int n _next; _next; _next % channels.size(); return channels[n]; } private: int _next 0; // 下标用户选择进程执行任务轮询送法任务就是将任务码写入选中进程的对应管道Channel类 void Send(int code) { // 向管道内写数据 write(_wfd, code, sizeof(code)); } private: int _wfd;4.2.4 回收结束进程池Stop结束进程池需要做两件事1.关闭管道2.回收子进程的PCB关闭管道其实就是关闭写端写端关闭后子进程调用 read 时不再阻塞直接返回0然后跳出Work 函数然后会回到 Start 函数执行剩下的代码。剩下的代码就是关闭子进程的读端。ProcessPool类 void Stop() { // 关闭管道 _cm.StopSubProcess(); cout endl; // 回收子进程 _cm.WaitSubProcess(); cout endl; }ChannelManager类 void StopSubProcess() { // 关闭管道 for (auto channel : channels) { channel.Stop(); } } void WaitSubProcess() { // 回收子进程 for (auto channel : channels) { channel.Wait(); } }Channel类 void Stop() { close(_wfd); cout 管道 _wfd 已关闭 endl; } void Wait() { int ret waitpid(_subid, nullptr, 0); (void)ret; // 绕过编译器对未使用变量的语法检查 cout 子进程 _subid 已回收 endl; }4.3 执行结果先打印管道号和对应的子进程pid。然后随机派发任务查看是否是轮询派发。回收进程池查看管道是否关闭子进程是否退出和回收[wsjiZgw05f0yp422tzyebhkoaZ 2.ProcessPool]$ ./process_pool channel-4-24948 channel-5-24949 channel-6-24950 channel-7-24951 channel-8-24952 任务个数:3 子进程:24948接收到一个任务码0 我是一个打印日志的任务 子进程:24949接收到一个任务码2 我是一个上传的任务 子进程:24950接收到一个任务码1 我是一个下载的任务 子进程:24951接收到一个任务码1 我是一个下载的任务 子进程:24952接收到一个任务码1 我是一个下载的任务 子进程:24948接收到一个任务码1 我是一个下载的任务 子进程:24949接收到一个任务码2 我是一个上传的任务 子进程:24950接收到一个任务码0 我是一个打印日志的任务 子进程:24951接收到一个任务码1 我是一个下载的任务 子进程:24952接收到一个任务码0 我是一个打印日志的任务 管道4已关闭 管道5已关闭 管道6已关闭 管道7已关闭 管道8已关闭 子进程24952已退出 子进程24951已退出 子进程24950已退出 子进程24949已退出 子进程24948已退出 子进程24948已回收 子进程24949已回收 子进程24950已回收 子进程24951已回收 子进程24952已回收 [wsjiZgw05f0yp422tzyebhkoaZ 2.ProcessPool]$4.4 代码还存在的bug父进程逐个关闭管道写端并同步回收子进程但由于其他子进程已继承了未关闭的管道写端导致目标子进程的管道读端无法读到 EOFread返回 0一直阻塞最终所有子进程无法退出、父进程无法回收形成进程阻塞卡死 BUG。bug代码如果我们不是先关闭父进程的所有写端再回收子进程而是父进程关闭一个写端就回收一个子进进程就会造成阻塞第一个进程在read阻塞了ChannelManager类 void StopSubProcess() { // 关闭管道 for (auto channel : channels) { channel.Stop(); } } void WaitSubProcess() { // 回收子进程 for (auto channel : channels) { channel.Wait(); } } void StopAndWait() { // 关闭管道回收子进程 for (auto channel : channels) { channel.Stop(); channel.Wait(); } } ProcessPool类 void Stop() { // // 关闭管道 // _cm.StopSubProcess(); // cout endl; // // 回收子进程 // _cm.WaitSubProcess(); // cout endl; _cm.StopAndWait(); }结果channel-4-27225 channel-5-27226 channel-6-27227 channel-7-27228 channel-8-27229 任务个数:3 子进程:27225接收到一个任务码0 我是一个打印日志的任务 子进程:27226接收到一个任务码2 我是一个上传的任务 子进程:27227接收到一个任务码0 我是一个打印日志的任务 子进程:27228接收到一个任务码2 我是一个上传的任务 子进程:27229接收到一个任务码0 我是一个打印日志的任务 子进程:27225接收到一个任务码2 我是一个上传的任务 子进程:27226接收到一个任务码2 我是一个上传的任务 子进程:27227接收到一个任务码0 我是一个打印日志的任务 子进程:27228接收到一个任务码0 我是一个打印日志的任务 子进程:27229接收到一个任务码2 我是一个上传的任务 管道4已关闭打印查看每个子进程继承的写端else if (subid 0) { // 子进程 // 打印每个子进程继承的文件文件描述符表 cout #################### 第 i 1 个子进程继承的写端 endl; _cm.PrintChannel(); cout ############################ endl endl; // 关闭不需要的文件描述符 close(pipefd[1]); Work(pipefd[0]); close(pipefd[0]); exit(0); }####################第1个子进程继承的写端 ############################ ####################第2个子进程继承的写端 channel-4-28604 ############################ ####################第3个子进程继承的写端 channel-4-28604 channel-5-28611 ############################ ####################第4个子进程继承的写端 channel-4-28604 channel-5-28611 channel-6-28621 ############################ ####################第5个子进程继承的写端 channel-4-28604 channel-5-28611 channel-6-28621 channel-7-28631 ############################4.5 解决方法方法一倒着关闭管道void StopAndWait() { // 方法一倒着关闭 for (int i channels.size() - 1; i 0; i--) { channels[i].Stop(); channels[i].Wait(); } }运行结果管道8已关闭 子进程28775已退出 子进程28775已回收 管道7已关闭 子进程28763已退出 子进程28763已回收 管道6已关闭 子进程28740已退出 子进程28740已回收 管道5已关闭 子进程28729已退出 子进程28729已回收 管道4已关闭 子进程28726已退出 子进程28726已回收方法二只让父进程指向所有管道的写端子进程也会继承父进程的 ChannelManager 而 Channel 中就有记录从父进程继承的写端。所以我们调用 Channel 中的 Close() 关闭继承的写端因为父子进程之间修改数据会发生写时拷贝所以子进程关闭写端不会影响父进程。ChannelManager类 void CloseAll() { // 关闭子进程的继承的写端 for (auto channel : channels) { channel.Close(); cout 子进程 channel.Name() 关闭继承的写端 endl endl; } } Channel类 void Close() { close(_wfd); cout 管道 _wfd 已关闭 endl; }####################第1个子进程继承的写端 ############################ ####################第2个子进程继承的写端 channel-4-29691 ############################ 管道4已关闭 子进程channel-4-29691 关闭继承的写端 ####################第3个子进程继承的写端 channel-4-29691 channel-5-29694 ############################ 管道4已关闭 子进程channel-4-29691 关闭继承的写端 管道5已关闭 子进程channel-5-29694 关闭继承的写端 ####################第4个子进程继承的写端 channel-4-29691 channel-5-29694 channel-6-29705 ############################ 管道4已关闭 子进程channel-4-29691 关闭继承的写端 管道5已关闭 子进程channel-5-29694 关闭继承的写端 管道6已关闭 子进程channel-6-29705 关闭继承的写端 ####################第5个子进程继承的写端 channel-4-29691 channel-5-29694 channel-6-29705 channel-7-29715 ############################ 管道4已关闭 子进程channel-4-29691 关闭继承的写端 管道5已关闭 子进程channel-5-29694 关闭继承的写端 管道6已关闭 子进程channel-6-29705 关闭继承的写端 管道7已关闭 子进程channel-7-29715 关闭继承的写端

相关文章:

【进程间通信】————匿名管道、模拟实现进程池

目录 1. 进程间通信 1.1 进程间通信的目的 1.2 进程间通信分类 2. 管道 3. 匿名管道 3.1 pipe函数 3.2 用 fork 来共享管道原理 3.3 从文件描述符角度理解 3.4 从内核角度理解 3.5 父子进程管道读写测试 3.6 管道特性 3.7 4种通信情况 3.8 管道的原子性 4. 进程…...

云服务器配置远程桌面

租赁云服务器通常没有图形化界面,因为想跑仿真看场景所以希望通过远程桌面的方式链接过去,那就需要服务器有图形化界面 1.安装图形化界面 ssh建立连接后 sudo apt update 极简版 sudo apt install --no-install-recommends task-gnome-desktop 简化…...

C++:模板精讲

泛型编程 当我们实现一个交换函数&#xff0c;想要实现不同类型的交换&#xff0c;可以使用函数重载&#xff1a; #include<iostream>using namespace std;void Swap(int& left, int& right) {int temp left;left right;right temp; } void Swap(char& …...

015-016 类中方法中的this,解决类中this指向问题

类中方法中的this<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-wid…...

Weka回归分析实战:从数据预处理到模型部署

1. 项目概述&#xff1a;Weka中的回归机器学习实战指南在数据科学领域&#xff0c;回归分析是预测连续型变量的经典方法。Weka作为一款开源的机器学习工作台&#xff0c;以其友好的图形界面和丰富的算法库&#xff0c;成为许多从业者快速验证模型的首选工具。不同于Python/R需要…...

边缘节点的PHP应用部署、数据同步、算力调度标准化方案=hyperf最

针对边缘节点场景&#xff0c;按三个维度给你整理最实用的 Hyperf 方案&#xff1a;---一、应用部署标准化 容器化 & 打包 …...

ARM智能卡接口(SCI)架构与通信协议详解

1. ARM智能卡接口(SCI)核心架构解析 智能卡接口(Smart Card Interface, SCI)作为嵌入式系统中实现安全通信的关键模块&#xff0c;其硬件架构设计直接决定了系统与智能卡之间的通信效率和可靠性。ARM架构下的SCI模块采用分层设计理念&#xff0c;主要由物理层、协议层和应用层组…...

别再手动算了!用Matlab的dec2hex/dec2bin函数搞定进制转换(附硬件寄存器操作实例)

别再手动算了&#xff01;用Matlab的dec2hex/dec2bin函数搞定进制转换&#xff08;附硬件寄存器操作实例&#xff09; 在嵌入式开发和数字电路设计中&#xff0c;进制转换是工程师们每天都要面对的"家常便饭"。想象一下这样的场景&#xff1a;你正在调试一块FPGA板卡…...

evolver部署教程:构建自动优化AI系统

在运行进化算法或自动优化类 AI 系统时&#xff0c;计算资源与运行稳定性会直接影响结果质量。尤其是在需要长时间迭代、批量实验或多轮计算的场景中&#xff0c;一些具备稳定资源与弹性能力的环境&#xff08;如莱卡云服务器这类部署方式&#xff09;通常更有利于实验持续推进…...

扩展拖垮VSCode?禁用这3类高危插件,启动速度提升3.2倍,实测有效

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;扩展拖垮VSCode&#xff1f;禁用这3类高危插件&#xff0c;启动速度提升3.2倍&#xff0c;实测有效 VSCode 启动缓慢常被误认为是硬件或系统问题&#xff0c;但真实瓶颈往往藏在插件生态中。我们对 127…...

如果openKylin 2.0 SP2主机的IPv4地址改变,如何让GitLab正常运行

作者&#xff1a;沈传越 明德融创工作室&#xff08;Minter Fusion Studio, MFS&#xff09; 出品 本文介绍的所有步骤均经过测试复现。 本文针对GitLab管理员使用。如果对于相关的专业词汇不太清楚。可以参考以下文章&#xff1a; 《如何在openKylin下安装并配置GitLab&…...

VSCode量子插件配置踩坑实录:92%开发者忽略的3项核心环境校验与自动修复方案

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;VSCode量子插件配置踩坑实录&#xff1a;92%开发者忽略的3项核心环境校验与自动修复方案 VSCode 量子开发插件&#xff08;如 Q# Extension、Quantum Development Kit&#xff09;在启用时频繁报错&…...

DimOS:AI原生机器人操作系统入门与实践指南

1. 项目概述&#xff1a;重新定义机器人操作系统如果你在过去几年里折腾过机器人开发&#xff0c;大概率绕不开ROS&#xff08;Robot Operating System&#xff09;。从ROS 1到ROS 2&#xff0c;它确实为机器人软件模块化、通信标准化立下了汗马功劳。但说实话&#xff0c;有多…...

告别STC-ISP!手把手教你写一个通吃STC89/12/15系列单片机的延时函数库

告别STC-ISP&#xff01;手把手教你打造跨代STC51单片机的延时函数库 当你在深夜调试STC89C52RC时&#xff0c;突然接到需求要移植代码到STC15W4K32S4上&#xff0c;却发现原本精准的延时函数完全失效——这种场景对51单片机开发者来说再熟悉不过。不同指令集架构带来的时钟周期…...

量子模拟中的N-可表示性问题与相关纯化方法

1. 量子模拟中的N-可表示性问题在量子化学计算中&#xff0c;约化密度矩阵&#xff08;Reduced Density Matrix, RDM&#xff09;是描述多电子系统量子态的核心工具。特别是二电子约化密度矩阵&#xff08;2-RDM&#xff09;&#xff0c;它包含了计算系统能量和各类物理性质所需…...

未来3年,这3个AI赛道已经定了

我最近一直在想一件事。Anthropic上个月的年化收入超过了OpenAI。很多人看到这条新闻&#xff0c;觉得不过是个财报数字。但我觉得这是一个信号——一个新产业正式成型的信号。这个产业叫AI编程。先说为什么是Anthropic&#xff0c;不是OpenAIOpenAI体量更大&#xff0c;名气更…...

3个颠覆性功能让Pearcleaner成为Mac系统清理必备神器

3个颠覆性功能让Pearcleaner成为Mac系统清理必备神器 【免费下载链接】Pearcleaner A free, source-available and fair-code licensed mac app cleaner 项目地址: https://gitcode.com/gh_mirrors/pe/Pearcleaner 你是否想过&#xff0c;为什么Mac电脑用久了会越来越慢…...

Linux服务器磁盘爆满?手把手教你用parted命令在线扩容/home分区(CentOS 8/9实战)

Linux服务器磁盘爆满&#xff1f;手把手教你用parted命令在线扩容/home分区&#xff08;CentOS 8/9实战&#xff09; 凌晨三点&#xff0c;监控系统突然发出刺耳的警报声——生产环境的/home分区使用率突破95%。作为运维工程师&#xff0c;这种场景再熟悉不过&#xff1a;应用日…...

差分放大器在高速信号链中的关键作用与设计实践

1. 差分放大器在高速信号链中的核心作用在现代无线通信和高速数据采集系统中&#xff0c;差分放大器扮演着信号调理的关键角色。这类器件通过独特的平衡架构&#xff0c;能够有效抑制共模噪声并显著降低偶次谐波失真。以THS4509为例&#xff0c;其1900MHz的带宽和6600V/s的压摆…...

NREL风速数据API参数详解:从wkt坐标到interval间隔,新手避坑指南

NREL风速数据API参数详解&#xff1a;从wkt坐标到interval间隔&#xff0c;新手避坑指南 当你在可再生能源或气象研究领域初次接触NREL风速数据API时&#xff0c;是否曾被那一长串参数列表搞得晕头转向&#xff1f;本文将化身你的私人参数解码器&#xff0c;带你深入理解每个配…...

神经形态硬件在强化学习机器人控制中的低功耗实践

1. 项目概述&#xff1a;神经形态硬件上的强化学习机器人控制去年在实验室调试Astrobee机器人时&#xff0c;我遇到了一个棘手的问题&#xff1a;传统GPU方案虽然能实现精确控制&#xff0c;但功耗高达200W&#xff0c;根本无法满足太空任务对能源的苛刻要求。这促使我开始探索…...

Pytest及相关测试工具实战指南

一个完整的例子&#xff0c;手把手教你从零开始使用Pytest&#xff0c;Pytest-cov&#xff0c;Pylint&#xff0c;flake8。 例子&#xff1a;银行账户系统 编写测试 -> 检查覆盖率 -> 做静态分析 -> 代码风格检查 第一部分&#xff1a;Pytest入门 - 从零到熟…...

PyTorch实现Transformer英法机器翻译系统

1. 从零构建Transformer模型&#xff1a;实现英法机器翻译系统 2017年&#xff0c;Transformer架构的提出彻底改变了序列到序列任务的处理方式。作为一名长期从事NLP开发的工程师&#xff0c;我将带您完整实现一个基于PyTorch的英法翻译Transformer模型。不同于简单调用现成库&…...

从零实现5大机器学习基础算法:Python代码与数学推导

1. 从零实现机器学习基础算法的必要性在机器学习领域&#xff0c;调用现成的库&#xff08;如scikit-learn&#xff09;固然方便&#xff0c;但真正理解算法本质的开发者都会选择自己动手实现一遍。这就像学习烹饪时&#xff0c;从切菜开始准备食材比直接使用预制菜更能掌握料理…...

从‘像素’到‘3D模型’:手把手拆解David Marr视觉四层描述,理解CV任务本质

从像素到三维世界&#xff1a;用David Marr视觉理论重构计算机视觉认知框架 1982年出版的《视觉计算理论》中&#xff0c;David Marr提出的视觉处理层次模型&#xff0c;至今仍是理解计算机视觉任务本质的黄金标准。这位将神经科学、心理学与计算机科学交叉融合的天才学者&…...

数字孪生AI赋能智慧社区:从概念到落地的全景指南

数字孪生AI赋能智慧社区&#xff1a;从概念到落地的全景指南 引言 在数字化转型浪潮下&#xff0c;智慧社区正从简单的设备联网迈向虚实融合的智能新阶段。数字孪生&#xff08;Digital Twin&#xff09;与人工智能&#xff08;AI&#xff09;的结合&#xff0c;为社区治理、…...

AI Agent Harness日志体系:可追溯性设计

AI Agent Harness日志体系全解密:从零搭建全链路可追溯能力,让每一次Agent决策都有迹可循 关键词 AI Agent、Harness日志体系、可追溯性、全链路追踪、分布式日志、决策审计、故障根因分析 摘要 随着AI Agent从单场景原型落地到企业级多Agent协作生产系统,「决策黑盒」「…...

数字孪生AI赋能智慧商圈:从概念到落地的全解析

数字孪生AI赋能智慧商圈&#xff1a;从概念到落地的全解析 引言 在数字化转型浪潮下&#xff0c;传统的商业空间正经历一场深刻的智能化变革。数字孪生与人工智能的结合&#xff0c;为“智慧商圈”的构建提供了全新的技术范式。它不再仅仅是简单的线上地图或监控大屏&#xf…...

不用C、不用Verilog!用Ada点亮LED,这才是Zynq的“另一种打开方式”

当你还在用C语言写GPIO、用Verilog连LED的时候&#xff0c;有人已经开始用一门“冷门但强大”的语言——Ada&#xff0c;在Zynq上点灯了。1.1 设置 EMIO 允许PS控制 LED在 Zedboard 上&#xff0c;LED 只能通过可编程逻辑 (PL)&#xff08;FPGA&#xff09;端进行控制&#xff…...

港科夜闻|香港科大于THE亚洲大学排名2026位列第12位,彰显顶尖亚洲大学地位

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、在最新公布的2026年泰晤士高等教育&#xff08;THE&#xff09;亚洲大学排名中&#xff0c;香港科技大学位列亚洲第十二位&#xff0c;充分展现香港科大在蓬勃发展的亚洲高等教育界中站稳领先位置。作为一所扎根亚洲、放…...