进程间通信(1)——管道

1. 进程间通信简介

进程间通信(Inter-Process Communication,IPC)是指不同进程之间交换数据的机制。由于进程具有独立的地址空间,它们无法直接访问彼此的数据,因此需要IPC机制来实现信息共享、数据传递或同步操作。

1.1 进程间通信的目的

数据交换:不同进程之间共享数据,提高系统的协作能力。

资源共享:多个进程可以访问共享内存或文件,提高资源利用率。

事件通知:当一个进程发生某种事件时,能够通知其他进程进行响应。

进程同步:有些进程希望完全控制另⼀个进程的执行(如Debug进程),此时控制进程希望能够拦截另⼀个进程的所有陷⼊和异常,并能够及时知道它的状态改变。

负载均衡:多个进程可以分担任务,提高系统性能。

1.2 进程间通信的分类

System V IPC 和 POSIX IPC 与管道(Pipe)是同级别的进程间通信(IPC)机制,但它们的使用方式和适用场景不同。管道属于 IPC 机制的一种,而 System V IPC 和 POSIX IPC 提供了更丰富的通信手段,如消息队列、共享内存和信号量。

1.21 管道(Pipe)

管道是一种 基于数据流的 IPC 机制,主要用于进程间的 单向通信,分为:

(1)匿名管道(Anonymous Pipe):只能用于父子进程之间的通信,使用 pipe() 创建。

(2)命名管道(FIFO, Named Pipe):允许无亲缘关系的进程通信,使用 mkfifo() 创建。

特点:

  • 适用于简单的数据流通信
  • 有序、先进先出(FIFO) 方式传输数据。
  • 单向通信,如果需要双向通信,需要创建两个管道。

1.2.2 System V IPC

System V IPC 提供了三种主要的进程间通信方式

(1)消息队列(Message Queue):进程通过发送和接收消息进行通信,类似邮件系统。

(2)共享内存(Shared Memory):多个进程可以访问同一块内存区域,比管道和消息队列快,但需要同步机制。

(3)信号量(Semaphore):用于进程同步,通常用于控制对共享资源的访问

特点:

  • 需要手动管理 IPC 资源(创建、删除)。
  • 适用于 长期运行的进程,如数据库、后台服务。
  • 比管道更强大,但 API 更复杂。

1.2.3 POSIX IPC

POSIX IPC 是 System V IPC 的改进版,提供:

(1)POSIX 消息队列:类似 System V 消息队列,但支持非阻塞模式,管理更方便。

(2)POSIX 共享内存:基于文件系统,使用 shm_open() 进行管理。

(3)POSIX 信号量:比 System V 信号量更简单,支持命名信号量无名信号量

特点:

  • 资源可以自动释放,避免 System V IPC 的手动管理问题。
  • 更现代化,适用于 Linux、macOS 等系统。
  • API 更易用,推荐用于新开发的 Linux 应用

管道 vs. System V IPC vs. POSIX IPC

机制适用场景亲缘关系限制数据传输方式速度是否需要同步
匿名管道父子进程通信需要父子关系字节流不需要
命名管道任意进程通信无亲缘关系要求字节流不需要
System V 消息队列结构化数据传输无亲缘关系要求消息(队列方式)中等不需要
System V 共享内存进程间高效共享数据无亲缘关系要求共享内存最快需要
System V 信号量进程同步无亲缘关系要求计数器需要
POSIX 消息队列结构化数据传输无亲缘关系要求消息(队列方式)中等不需要
POSIX 共享内存进程间高效共享数据无亲缘关系要求共享内存最快需要
POSIX 信号量进程同步无亲缘关系要求计数器需要

总结

管道(Pipe)和 System V / POSIX IPC 都是 IPC 机制的一种,但它们适用于不同场景:

  • 管道适用于简单的进程间数据流通信,特别是父子进程之间的通信。
  • System V 和 POSIX IPC 适用于更复杂的进程通信需求,比如:
    • 消息队列 适合结构化数据传输(比管道更灵活)。
    • 共享内存 适合高效数据共享(比管道快)。
    • 信号量 适合进程同步(配合共享内存使用)。
  • POSIX IPC 是 System V IPC 的改进版,API 更简洁,资源管理更方便,推荐在现代 Linux 开发中使用。

2. 管道

管道是进程间通信(IPC)的一种方式,它允许一个进程将数据传输给另一个进程。管道在类Unix操作系统中尤其重要。管道有两种类型:匿名管道和命名管道。

  1. 匿名管道:匿名管道通常用于父子进程或兄弟进程之间的通信,它没有名称,因此只能在创建它的进程间使用。数据只能在一个方向上传递,从管道的一端写入数据,另一端读取数据。匿名管道的生命周期与父进程相关联。

  2. 命名管道(FIFO):命名管道有一个特定的名称,因此可以在不同的进程之间进行通信,而不仅限于父子进程。命名管道在文件系统中表现为一个特殊的文件,可以通过路径访问。这使得它可以跨进程、跨终端进行通信。

管道的工作原理是通过内核缓冲区进行数据传输,内核会确保进程间的数据不会相互干扰。管道是半双工的(数据只能单向流动),但通过创建两个管道可以实现全双工通信。

管道的优点是简单、快速,缺点是只能在有限的场景中使用,如通信双方需要具有亲缘关系(匿名管道)或访问权限(命名管道)。

3. 匿名管道

#include <unistd.h>
功能:创建⼀⽆名管道
原型
int pipe(int fd[2]);
参数
fd:⽂件描述符数组,其中fd[0]表⽰读端, fd[1]表⽰写端
返回值:成功返回0,失败返回错误代码

3.1 多视角理解管道 

用fork来共享管道原理

#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>void ChildWrite(int wfd)
{char buffer[1024];int cnt = 0;while(true){snprintf(buffer, sizeof(buffer), "I am child, pid: %d, cnt: %d", getpid(), cnt++);write(wfd, buffer, strlen(buffer));sleep(1);} 
}
void FatherRead(int rfd)
{char buffer[1024];while(true){buffer[0] = 0; int n = read(rfd, buffer, sizeof(buffer) - 1);if(n > 0){buffer[n] = 0;std::cout << "child say" << buffer << std::endl;}}
}int main()
{int fds[2] = {0};int n = pipe(fds);if(n < 0){std::cerr << "pipe error" << std::endl;}std::cout << "fds[0]:" << fds[0] << std::endl;std::cout << "fds[1]:" << fds[1] << std::endl;//创建子进程pid_t id = fork();if(id == 0){close(fds[0]);ChildWrite(fds[1]);close(fds[1]);exit(0);}close(fds[1]);FatherRead(fds[0]);close(fds[0]);wait(NULL);return 0;
}

站在文件描述符角度-深度理解管道

站在内核角度-管道本质

3.2 管道的同步机制

(1)写慢,读块

读端阻塞进程(等写)

#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>void ChildWrite(int wfd)
{char buffer[1024];int cnt = 0;while(true){snprintf(buffer, sizeof(buffer), "I am child, pid: %d, cnt: %d", getpid(), cnt++);write(wfd, buffer, strlen(buffer));} 
}
void FatherRead(int rfd)
{char buffer[1024];while(true){buffer[0] = 0; int n = read(rfd, buffer, sizeof(buffer) - 1);if(n > 0){buffer[n] = 0;std::cout << "child say" << buffer << std::endl;}sleep(1);}
}int main()
{int fds[2] = {0};int n = pipe(fds);if(n < 0){std::cerr << "pipe error" << std::endl;}std::cout << "fds[0]:" << fds[0] << std::endl;std::cout << "fds[1]:" << fds[1] << std::endl;//创建子进程pid_t id = fork();if(id == 0){close(fds[0]);ChildWrite(fds[1]);close(fds[1]);exit(0);}close(fds[1]);FatherRead(fds[0]);close(fds[0]);wait(NULL);return 0;
}

(2)写快, 读慢

缓冲区满了的时候,写端要阻塞等待读端

void ChildWrite(int wfd)
{char buffer[1024];int cnt = 0;while(true){snprintf(buffer, sizeof(buffer), "I am child, pid: %d, cnt: %d", getpid(), cnt++);write(wfd, buffer, strlen(buffer));sleep(1);} 
}
void FatherRead(int rfd)
{char buffer[1024];while(true){buffer[0] = 0; int n = read(rfd, buffer, sizeof(buffer) - 1);if(n > 0){buffer[n] = 0;std::cout << "child say" << buffer << std::endl;}}
}

 (3)写关, 继续读

read读到返回值为0, 表示文件结尾

void ChildWrite(int wfd)
{char buffer[1024];int cnt = 0;for (int i = 0; i < 5; ++i){snprintf(buffer, sizeof(buffer), "I am child, pid: %d, cnt: %d", getpid(), cnt++);write(wfd, buffer, strlen(buffer));sleep(1);}// 关闭写端close(wfd);
}
void FatherRead(int rfd)
{char buffer[1024];while(true){buffer[0] = 0; int n = read(rfd, buffer, sizeof(buffer) - 1);if(n > 0){buffer[n] = 0;std::cout << "child say" << buffer << std::endl;}else if (n == 0){std::cout << "Reached end of file (write end closed)" << std::endl;break;}else{std::cerr << "read error" << std::endl;break;}}
}

(4)读关,继续写

没有任何意义,OS不做没有意义的事,会杀掉进程

void ChildWrite(int wfd)
{char buffer[1024];int cnt = 0;while(true){snprintf(buffer, sizeof(buffer), "I am child, pid: %d, cnt: %d", getpid(), cnt++);write(wfd, buffer, strlen(buffer));sleep(1);}// 关闭写端close(wfd);
}
void FatherRead(int rfd)
{char buffer[1024];for(int i = 0; i < 3; i++){buffer[0] = 0; int n = read(rfd, buffer, sizeof(buffer) - 1);if(n > 0){buffer[n] = 0;std::cout << "child say" << buffer << std::endl;}}close(rfd);
}

总结:

管道(Pipe)在进程间通信(IPC)中是一种常见的机制,其读写规则如下:

当没有数据可读时:
O_NONBLOCK 关闭:read 调用会阻塞,即进程暂停执行,直到有数据可读。
O_NONBLOCK 开启:read 调用返回 -1,errno 值为 EAGAIN

当管道满时:
O_NONBLOCK 关闭:write 调用会阻塞,直到有进程读取数据腾出空间。
O_NONBLOCK 开启:write 调用返回 -1,errno 值为 EAGAIN

如果所有管道写端对应的文件描述符被关闭,则 read 返回 0,表示读到文件结尾(EOF)。

如果所有管道读端对应的文件描述符被关闭,则 write 操作会产生 SIGPIPE 信号,进而可能导致 write 进程退出。

当写入的数据量不大于 PIPE_BUF 时,Linux 保证写入的原子性,即写入的数据不会与其他进程的写入操作交错。

当写入的数据量大于 PIPE_BUF 时,Linux 不再保证写入的原子性,可能会发生数据交错。

3.3 基于匿名管道-进程池 

管道的容量

ProcessPool.hpp创建进程池处理任务

#ifndef __PROCESS__POOL__HPP__
#define __PROCESS__POOL__HPP__#include <iostream>
#include <cstdlib>
#include <vector>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#include "Task.hpp"class Channel
{
public:Channel(int fd, pid_t id) : _wfd(fd), _subid(id){_name = "channel" + std::to_string(_wfd) + "-" + std::to_string(_subid);}void Send(int code){int n = write(_wfd, &code, sizeof(code));//void(n);}~Channel() {}int getWfd() { return _wfd; }pid_t getSubid() { return _subid; }std::string getName() { return _name; }void Close(){close(_wfd);}void Wait(){pid_t rid =  waitpid(_subid, nullptr, 0);(void)rid;}private:int _wfd;pid_t _subid;std::string _name;
};
class ChannelManager
{
public:ChannelManager() :_next(0){}~ChannelManager() {}void InsertChannel(int wfd, pid_t subid){_channels.emplace_back(wfd, subid);}Channel &Select(){auto &c = _channels[_next];_next++;_next %= _channels.size();return c;}void Printchannel(){for(auto &channel : _channels){std::cout << channel.getName() << std::endl;}}void Closechannel(){for(int i = _channels.size() - 1; i >= 0; i--){_channels[i].Close();std::cout << "关闭:" << _channels[i].getName() << std::endl;_channels[i].Wait();std::cout << "回收:" << _channels[i].getName() << std::endl;}}
private:std::vector<Channel> _channels;int _next;
};const int gdefaultnum = 5;class ProcessPool
{
public:ProcessPool(int num) : _process_num(num){_tm.Register(PrintLog);_tm.Register(Download);_tm.Register(Upload);}~ProcessPool() {}void Work(int rfd){while(true){int code = 0;ssize_t n = read(rfd, &code, sizeof(code));if(n > 0){if(n != sizeof(code)) continue;std::cout << "子进程[" << getpid() << "]收到一个任务码: " << code << std::endl;}else if(n == 0){std::cout << "子进程退出" << std::endl;break;}else {std::cout << "读取错误" << std::endl;break;}}}bool Create(){for (int i = 0; i < _process_num; i++){ // 1.创建管道int pipefd[2] = {0};int n = pipe(pipefd);if (n < 0)return false;// 2. 创建见子进程pid_t subid = fork();if (subid < 0)return false;else if (subid == 0){// 3. 关闭写进程// 子进程close(pipefd[1]);Work(pipefd[0]);close(pipefd[0]);exit(0);}else{// 3. 关闭读进程// 父进程close(pipefd[0]);_cm.InsertChannel(pipefd[1], subid);}}return true;}void Debug(){_cm.Printchannel();}void Run(){//1. 选择一个任务int taskcode = _tm.Code();//2. 选择一个信道,负载均衡的选择一个子进程,完成任务Channel &c = _cm.Select();std::cout << "选择了一个子进程:" << c.getName() << std::endl;//2. 发送任务c.Send(taskcode);std::cout << "发送了一个任务码:" << taskcode << std::endl;}void Stop(){_cm.Closechannel();}
private:ChannelManager _cm;int _process_num;TaskManager _tm;
};#endif

Task.hpp 创建进程池处理任务

#pragma once#include <iostream>
#include <vector>
#include <ctime>typedef void (*task_t)();debug/
void PrintLog()
{std::cout << "我是一个打印日志的任务" << std::endl;
}void Download()
{std::cout << "我是一个下载的任务" << std::endl;
}void Upload()
{std::cout << "我是一个上传的任务" << std::endl;
}
//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){if(code >= 0 && code < _tasks.size()){_tasks[code]();}}~TaskManager(){}
private:std::vector<task_t> _tasks;
};

 Main.cc

#include "ProcessPool.hpp"int main()
{ProcessPool pp(gdefaultnum);pp.Create();//pp.Debug();//int task = 0;int cnt = 10; while(cnt--){std::cout << cnt << std::endl;pp.Run();sleep(1);}pp.Stop();return 0;
}

 3.4 管道特点 

(1)管道 只能用于具有共同祖先的进程(即具有亲缘关系的进程)之间的通信。通常,一个管道由一个进程创建,然后该进程调用 fork,此后父、子进程之间就可以通过该管道进行通信。

(2)管道提供 流式服务,数据按顺序传输,适用于字节流通信。

(3)管道的 生命周期随进程结束,通常进程退出后,管道会被释放。

(4)内核会对管道操作进行同步与互斥,保证数据读写的正确性。例如,多个进程写入时,写入不大于 PIPE_BUF 的数据会保持原子性。

(5)管道是 半双工的,即数据只能单向流动。如果需要双向通信,需要建立两个管道,分别用于两个方向的数据传输。

4. 命名管道

4.1 mkfifo

mkfifo 用于创建 命名管道(FIFO,First In First Out)。与普通管道(匿名管道)不同,命名管道可以用于 不具有亲缘关系的进程 之间的通信,因为它存在于文件系统中,可以由多个进程打开进行读写。

4.2 实现进程间通信

//comm.cpp
#pragma once#include <iostream>
#include <string>#define PATH "."
#define FIFONAME "fifo"#define PATH "."
#define FILENAME "fifo"#define ERR_EXIT(m)         \do                      \{                       \perror(m);          \exit(EXIT_FAILURE); \} while (0)class NameFifo
{
public:NameFifo(const std::string &path, const std::string &name) : _path(path), _name(name){_fifoname = _path + "/" + _name;int n = mkfifo(_fifoname.c_str(), 0666);if(n == - 1){std::cerr << "mkfifo error" << std::endl;}std::cout << "mkfifo success" << std::endl;}~NameFifo(){int n = unlink(_fifoname.c_str());if(n == 0){std::cout << "remove FIFO_FILE success" << std::endl;}else{std::cerr << "remove FIFO_FILE failed" << std::endl;}}
private:std::string _path;std::string _name;std::string _fifoname;
};
class Fileoper
{
public:Fileoper(const std::string &path, const std::string &name) : _path(path), _name(name), _fd(-1){_fifoname = _path + "/" + _name;}void OpenForRead(){//打开,write方法中没有执行open的时候,就要在open内部进行阻塞_fd = open(_fifoname.c_str(), O_RDONLY);if(_fd < 0){std::cerr << "open error" << std:: endl;return;}std::cout << "open success" << std::endl;}void OpenForWrite(){_fd = open(_fifoname.c_str(), O_WRONLY);if(_fd < 0){std::cerr << "open fifo cerr" << std::endl;return;}std::cout << "open fifo success" << std::endl;}void Write(){std::string message;int cnt = 1;pid_t id = getpid();while(true){std::cout << "Please Enter# ";std::getline(std::cin, message);message += (", message number: " + std::to_string(cnt++) + ", [" + std::to_string(id) + "]");write(_fd, message.c_str(), message.size()); }}void Read(){while(true){char buffer[1024];int number = read(_fd, buffer, sizeof(buffer) - 1);if(number > 0){buffer[number] = 0;std::cout << "Client say# " << buffer << std::endl; }else if(number == 0){std::cout << "client quit" << number <<std::endl;break;}else{std::cerr << "client error" << std::endl;break;}}}void Close(){if(_fd > 0) close(_fd);}~Fileoper(){}
private:std::string _path;std::string _name;std::string _fifoname;int _fd;
};
//server.cc
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "comm.hpp"int main()
{//创建管道文件NameFifo fifo(PATH, FIFONAME);//文件操作Fileoper readfile(PATH, FIFONAME);readfile.OpenForRead();readfile.Read();readfile.Close();return 0;
}
//client.cc
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "comm.hpp"int main()
{Fileoper writefifo(PATH, FIFONAME);writefifo.OpenForWrite();writefifo.Write();writefifo.Close();return 0;
}
//Makefile
.PHONY:all
all:client serverclient::client.ccg++ -o $@ $^ -std=c++11
server:server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm client server

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

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

相关文章

台达PLC转太网转换的教程案例(台达DVP系列)

产品介绍 台达DVP-PLC自投身工业自动化市场以来&#xff0c;始终致力于创新发展&#xff0c;不断推陈出新。其产品紧密贴合市场需求与行业工艺&#xff0c;凭借卓越性能与丰富功能&#xff0c;深受用户青睐。不仅推出了高效的程序与编辑工具&#xff0c;显著提升了主机执行速度…

ArcGIS10. 8简介与安装,附下载地址

目录 ArcGIS10.8 1. 概述 2. 组成与功能 3. 10.8 特性 下载链接 安装步骤 1. 安装准备 2. 具体步骤 3.补丁 其他版本安装 ArcGIS10.8 1. 概述 ArcGIS 10.8 是由美国 Esri 公司精心研发的一款功能强大的地理信息系统&#xff08;GIS&#xff09;平台。其核心功能在于…

R语言高效数据处理-自定义格式EXCEL数据输出

注&#xff1a;以下代码均为实际数据处理中的笔记摘录&#xff0c;所以很零散&#xff0c; 将就看吧&#xff0c;这一篇只是代表着我还在&#xff0c;所以可能用处不大&#xff0c;这一段时间都很煎熬&#xff01; 在实际数据处理中为了提升效率&#xff0c;将Excel报表交付给…

从零构建大语言模型全栈开发指南:第一部分:数学与理论基础-1.1.2核心数学基础:线性代数、概率论与梯度优化

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 1.1.2 核心数学基础&#xff1a;线性代数、概率论与梯度优化1. 线性代数&#xff1a;大语言模型的“骨架”1.1 核心概念与应用场景表1&#xff1a;线性代数核心运算与模型应…

科研项目验收管理系统

摘 要 使用旧方法对科研项目信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在科研项目信息的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。这次开发的科研项目验收管…

游戏成瘾与学习动力激发策略研究——了解存在主义心理学(通俗版)

存在主义心理学是20世纪中叶兴起的重要心理学流派,融合了哲学存在主义思想,强调人的主观体验、自由选择与责任承担,旨在帮助个体在不确定的世界中创造意义。 研究人如何在不确定的世界中活出意义的心理学,核心思想可以概括为以下四点: 存在主义心理学的主要内容 “存在先于…

Dropshare for Mac v6.1 文件共享工具 支持M、Intel芯片

Dropshare 是 Mac 用来上传图片、视频、截图和各种文件的工具。这款软件利用了SCP over SSH传输协议来将 Mac 本机的文件快速上传到自设的远程服务器。 应用介绍 Dropshare 是 Mac 用来上传图片、视频、截图和各种文件的工具。这款软件利用了SCP over SSH传输协议来将 Mac 本…

关于redis中的分布式锁

目录 分布式锁的基础实现 引入过期时间 引入校验id 引入lua脚本 引入看门狗 redlock算法 分布式锁的基础实现 多个线程并发执行的时候&#xff0c;执行的先后顺序是不确定的&#xff0c;需要保证程序在任意执行顺序下&#xff0c;执行逻辑都是ok的。 在分布式系统中&am…

利用AI让数据可视化

1. 从问卷星上下载一份答题结果。 序号用户ID提交答卷时间所用时间来源来源详情来自IP总分1、《中华人民共和国电子商务法》正式实施的时间是&#xff08;&#xff09;。2、&#xff08;&#xff09;可以判断企业在行业中所处的地位。3、&#xff08;&#xff09;是指店铺内有…

PairRE: Knowledge Graph Embeddings via Paired Relation Vectors(论文笔记)

CCF等级&#xff1a;A 发布时间&#xff1a;2020年11月 代码位置 25年3月24日交 目录 一、简介 二、原理 1.整体 2.关系模式 3.优化模型 三、实验性能 四、结论和未来工作 一、简介 将RotatE进行生级&#xff0c;RotatE只对头实体h进行计算&#xff0c;PairRE对头尾…

解决git init 命令不显示.git

首先在自己的项目代码右击 打开git bash here 输入git init 之后自己的项目没有.git文件&#xff0c;有可能是因为.git文件隐藏了&#xff0c;下面是解决办法

汇编移位指令

rol, ror 循环左移/右移 该指令影响CF。因为左移/右移时将最高位/最低位移动到CF中&#xff0c;同时移动到最低位&#xff0c;其他位依次左移/右移。 shl, shr 逻辑左移/右移 该指令影响CF。因为左移/右移时将最高位/最低位移动到CF中&#xff0c;其他位依次左移/右移&…

Python个人学习笔记(18):模块(异常处理、traceback、日志记录)

七、异常处理 语法错误不属于异常&#xff0c;处理的是程序运行时的一些意外情况 代码&#xff1a; a int(input(>>>&#xff1a;)) b int(input(>>>&#xff1a;)) print(a / b) # 在运行的时候由于数据不对&#xff0c;导致出错 # 此时程序会中断 prin…

AnyTouch:跨多个视觉触觉传感器学习统一的静态动态表征

25年3月来自人大、武汉科技大学和北邮的论文“AnyTouch: Learning Unified Static-dynamic Representation Across Multiple Visuo-tactile Sensors”。 视觉触觉传感器旨在模拟人类的触觉感知&#xff0c;使机器人能够精确地理解和操纵物体。随着时间的推移&#xff0c;许多精…

【数据分享】1999—2023年地级市固定资产投资和对外经济贸易数据(Shp/Excel格式)

在之前的文章中&#xff0c;我们分享过基于2000-2024年《中国城市统计年鉴》整理的1999-2023年地级市的人口相关数据、染物排放和环境治理相关数据、房地产投资情况和商品房销售面积相关指标数据、社会消费品零售总额和年末金融机构存贷款余额、各类用地面积、地方一般公共预算…

(位运算 水题?407周赛题?o 使两个整数相等的位更改次数)leetcode 3226

思路 &#xff1a;灵茶山艾府 怎么判断n能构成k直接异或取1的数量就行 关键在于如何判断n无法构成k 按照灵茶山大佬的方案一就是让k是n的子集也就是n与k的交集等于k 不等于k就不是n的子集 &#xff08;当k是n的子集时 n能构成k&#xff09; 与运算取交集&#xff0c;或运算取…

使用DDR4控制器实现多通道数据读写(四)

在创建完DDR4的仿真模型后&#xff0c;我们为了实现异步时钟的读写&#xff0c;板卡中在PL端提供了一组差分时钟&#xff0c;可以用它通过vivado中的Clock Wizard IP核生成多个时钟&#xff0c;在这里生成两个输出时钟&#xff0c;分别作为用户的读写时钟&#xff0c;这样就可以…

Linux 文件操作-标准IO函数4-fseek设置文件偏移量、ftell获取当前偏移量、rewind使文件偏移量(为0)定位到开头

目录 1.fseek设置文件偏移量 2.ftell获取当前偏移量 3.rewind使文件偏移量&#xff08;为0&#xff09;定位到开头 4.程序验证 1.fseek设置文件偏移量 函数原型&#xff1a; /* 功能&#xff1a;设置文件位置指针的偏移量 参数&#xff1a; stream&#xff1a;文件指针 of…

JavaEE的知识记录

内容很多可以通过目录进行查找对应的内容。 目录 一、注解 元注解 RequestMapping 路由映射注解 RequestParam绑定请求参数到可控制器方法的参数 请求参数绑定可控制方法参数&#xff1a; 参数绑定重命名 RequestBody请求正文注解 ResponseBody响应体正文注解 PathVar…

带旋转的目标标注工具-X-AnyLabeling

在之前的文章中&#xff0c; 分别介绍过3款标注工具&#xff1a; 目标检测&#xff0c;语义分割标注工具–labelimg labelme智能标注工具 T-Rex Label 对于2D目标检测标注&#xff0c; 上面的工具只能标注不带旋转的检测框。但是如果我们要进行带旋转方向的检测&#xff08;O…