遵义城乡住房建设厅网站,网站排名不稳定怎么办,纬天建筑工程信息网,网站开发 工程师 类型目录
进程间通信目的
进程间通信标准 管道 匿名管道
管道实现进程间通信
管道的特点 进程池
ProcessPool.cc
Task.hpp
习题 进程间通信目的 数据传输#xff1a;一个进程需要将它的数据发送给另一个进程 资源共享#xff1a;多个进程之间共享同样的资源。 通知事件…目录
进程间通信目的
进程间通信标准 管道 匿名管道
管道实现进程间通信
管道的特点 进程池
ProcessPool.cc
Task.hpp
习题 进程间通信目的 数据传输一个进程需要将它的数据发送给另一个进程 资源共享多个进程之间共享同样的资源。 通知事件一个进程需要向另一个或一组进程发送消息通知它它们发生了某种事件如进程终止时要通知父进程。 进程控制有些进程希望完全控制另一个进程的执行如Debug进程此时控制进程希望能够拦截另一个进程的所有陷入和异常并能够及时知道它的状态改变
进程间通信不是目的是手段。 技术背景
1.进程是具有独立性的虚拟地址空间页表保证进程允许的独立性进程内核数据结构进程的代码和数据
2.通信成本会比较高
进程间通信的本质理解
1.进程间通信的前提首先需要让不同的进程看到同一块“内存”特定的结构组织的
2.这个所谓的“内存”不属于任何一个进程强调的是共享
进程间通信标准
1.Linux原生能提供——管道匿名命名
2.SystemV——多进程——单机通信
SystemV有共享内存消息队列信号量
3.posix——多线程——网络通信 这些标准在使用者看来都是接口上具有一定的规律。 管道 管道是Unix中最古老的进程间通信的形式。 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
只要是管道就一定有出口和入口但是出入口数量有n种可能单向传输内容管道是用来传资源的。计算机的设计者设计了一种单向通信模式——管道。 管道通信背后是进程之间通过管道进行通信 每个进程都有一个自己的文件描述符表进程内部有files*指针指向自己的文件描述符表文件描述符表都有自己的数组下标012是标准输入标准输出标准错误流linux下一切皆文件没个文件都有自己的struct_file结构struct_file不仅包含inode属性也包含文件对应的内核缓冲区这个缓冲区在内核中对应的结构叫address_space 1. 我们以读和写的方式打开同一个文件给父进程返回的时候会返回俩个文件描述符 2.fork创建子进程
由于进程具有独立性子进程会有自己的task_struct和文件描述符表之后父进程相关的数据会拷贝给子进程文件描述符表也会拷贝给子进程。
这样不同的进程看到同一份资源内存这里这是创建了子进程我们的目的是子进程不会拷贝文件相关数据结构只会拷贝进程相关数据结构。 管道的底层原理就是以文件的形式设计
3.双方进程各自关闭自己不需要的文件描述符。
由于管道是单向通信我们目前规定父进程写入子进程读取。 这就叫管道 管道就是文件俩个进程通过文件来进行通信 当我们在上面操作的文件中写入了数据没必要把这些数据刷新到磁盘中进程间通信要保证纯内存级别的通信磁盘是外设。 匿名管道 #include unistd.h 功能:创建一无名管道 原型 int pipe(int fd[2]);
输出型参数期望通过调用它得到被打开的文件fd创建成功时返回值为0失败返回值为-1。
谁调用pipe就让谁以读写方式打开文件不需要指定文件名这个文件是纯内存级别的如struct_file就不在磁盘中。文件属于内核一个进程把文件给操作系统然后另一个进程通过操作系统去读取。 参数 fd文件描述符数组,其中fd[0]表示读端, fd[1]表示写端 返回值:成功返回0失败返回错误代码 用代码进行测试 进行调试 如果要不想调试 在-DDEBUG前面加# 此时结果不能打印 我们接下来要实现让父进程写入子进程读取。 创建子进程并进行读写通道的建立 read的返回值类型是ssize_t这是long int
snprintf把特定的字符串内容显示到字符串中 管道实现进程间通信
#includeiostream
#includecstdio
#includecstring
#includeunistd.h
#includeassert.h
#includestring
#includesys/types.h
#includesys/wait.h
using namespace std;
int main()
{int pipefd[2]{0};//默认设置为0int npipe(pipefd);//完成了管道的创建,默认以读写方式打开//断言在DEBUG模式下有效release无效//如果没有(void)n n就只被定义而未被使用可能会在 Realse模式下会出现大量的警告所以我们加了(void)n
assert(n!-1);
(void)n;
//查看读和写对应的文件描述符
#ifdef DEBUGcout pipefd[0]: pipefd[0] endl; // 3 读端cout pipefd[1]: pipefd[1] endl; // 4 写端
#endif//2.创建子进程pid_t idfork();assert(id!-1);if(id0){//子进程//3.构建单向通信的信道// 3.1关闭子进程不需要的fdclose(pipefd[1]);//子进程关闭写端char buffer[1024];while(true){ssize_t s read(pipefd[0],buffer,sizeof(buffer)-1);//从读端进行读将内容读取到buffer中大小为buffer-1if(s0)//读取成功{buffer[s]0;//由于是系统层面的读取没有\0所以我们在这添加\0coutchild get a message[getpid()]Father# bufferendl;//打印来自父进程的文件内容}}exit(0);}//父进程//3.构建单向通信的信道//3.1关闭父进程不需要的fdclose(pipefd[0]);//父进程关闭读端string message我是父进程我正在给你发消息;int count0;//计数器统计发送消息的条数char send_buffer[1024];//要发送的缓冲区while(true){//3.2 构建一个变化的字符串snprintf(send_buffer,sizeof(send_buffer),%s[%d]:%d,message.c_str(),getpid(),count);//把消息和统计次数格式化到缓冲区当中//3.3 写入write(pipefd[1],send_buffer,strlen(send_buffer));//把缓冲区的内容写入到写端这里的strlen后面不需要1//因为管道也是文件不需要给文件描述符对应的文件写入\0因为没意义//3.4 故意sleepsleep(1);}//等待子进程pid_t retwaitpid(id,nullptr,0);assert(ret0);(void)ret;close(pipefd[1]);//结束之后关闭写端,子进程不需要写因为子进程程序中有exit(0)当推出后文件描述符会被关掉此时文件不一定被释放。文件是父进程打开的当父进程结束时文件会被释放return 0;}我们在程序里写端缓冲区有一个buffer读端缓冲区也有一个buffer 不用全局buffer是因为有写时拷贝的存在父子进程要确保自己数据的私有性再怎么定义全局变量也无法进行通信。
管道的特点 1.管道是用来进行具有“学院”关系的进程进行进程间通信——常用于父子通信
管道是一个文件显示器也是一个文件父子同时往显示器写入的时候如果没有先后顺序俩个同时写入这种情况是由于缺乏访问控制。
管道文件在进行读取的时候子进程会等待父进程如这里休眠10s在休眠的这段时间子进程一直都在等父进程所以显示器未打印数据这叫具有访问控制 2.管道具有通过让进程间协同提供了访问控制
不控制父进程的写入速度让子子进程20s后再读 程序一执行管道就被写满 之后子进程进行读取一次性把管道里的内容读取完 子进程读取完之后父进程又开始写直到写满 3.管道提供的时面向流式的服务——面向字节流一般需要定制协议 4.管道是基于文件的文件的生命周期时随进程的管道的生命周期是随进程的如果父子进程在使用管道通信通信双方退出管道会被自动释放
如果读的时候写入的一方的fd没有关闭如果有数据就读没有数据就等如果写入的一方fd关闭读取的一方read会返回0表示读到了文件的结尾
写了10次关闭写端 写入端关闭子进程 退出
5.管道是单向通信的就是半双工通信的一种特殊情况要么一者在接收要么一者在发送。 全双工俩人吵架俩人同时在说同时在听。 a.写快读慢写满就不能再写了
b.写慢读快管道没有数据的时候就必须等待
c.写关读到了0读到文件结尾即把所有文件读取完表示读到了文件结尾
d.读关写继续写OS终止写进程
当要写入的数据量不大于PIPE_BUF时linux将保证写入的原子性只有做和不做俩种选择。 当要写入的数据量大于PIPE_BUF时linux将不再保证写入的原子性。 进程池 每个子进程和父进程形成一个管道0是fd为01是fd为1当用户要父进程完成任务时父进程可指派子进程进行完成可随机选子进程或按照某种特定顺序让子进程执行。
我们在这里让父进程每次写4字节子进程读四字节
ProcessPool.cc
#include iostream
#include vector
#include cstdlib
#include ctime
#include cassert
#include unistd.h
#include sys/wait.h
#include sys/types.h
#include Task.hpp#define PROCESS_NUM 5//进程个数
using namespace std;
int waitCommand(int waitFd,bool quit)
{//uint32_t是4个字节uint32_t command0;ssize_t sread(waitFd,command,sizeof(command));//从waitFd里读取内容到command大小为4个字节if(s0)//此时把文件描述符关了{quittrue;return -1;}assert(ssizeof(uint32_t));return command;//返回读到的命令
}void sendAndWakeup(pid_t who, int fd, uint32_t command)
{//给who进程通过fd发送command命令write(fd, command, sizeof(command));cout main process: call process who execute desc[command] through fd endl;
}
int main()
{load();vectorpairpid_t,int slots;//pair的first是pid,second是进程相关信息pipefd//slots是一个表表里存的是子进程的id和pipefd父进程可以通过该表选择让哪个子进程执行任务//创建多个进程for(int i0;iPROCESS_NUM;i){int pipefd[2]{0};//创建管道int npipe(pipefd);assert(n0);pid_t idfork();(void)n;assert(id!-1);//子进程让他进行读取if(id0){//子进程//关闭子进程写端close(pipefd[1]);while(true){bool quitfalse;//一开始先不退出后面要等待命令int commandwaitCommand(pipefd[0],quit);//子进程等命令,在读取文件描述符那里等命令如果对方不发命令我们就阻塞if(quit)break;//执行对应的命令if(command0commandhandlerSize())//如果范围之类{callbacks[command]();//直接调用对应的方法}else{cout非法commandcommandendl;}}exit(1);}//父进程close(pipefd[0]);slots.push_back(pairpid_t,int(id,pipefd[1]));//子进程ID写端描述符//把所有的子进程信息放到vector里形成一张子进程表}//整个for循环就是在选择父进程在选择子进程并且准备给子进程发指令//父进程派发任务,用单机版的负载均衡防止出现一个子进程做多个任务均衡一下子进程工作量//我们用随机数派发srand((unsigned long)time(nullptr)^getpid()^2323232323L);//让数据源更随机while(true){cout########################################endl;cout# 1.show functions 2.send command #endl;cout########################################endl;coutPlease Select;int select;int command;//命令cinselect;if(select1)//显示可以被执行的任务{showHandler();}else if(select2)//发送命令{//选择任务coutEnter Your Command;cincommand;//输入命令//选择进程int choicerand()%slots.size();//布置任务,把任务给指定的进程sendAndWakeup(slots[choice].first,slots[choice].second,command);//发送命令并且唤醒子进程}else{}}//关闭fd,结束所有进程for(const auto slot:slots){close(slot.second);}//回收所有的子进程信息for(const auto slot:slots){waitpid(slot.first,nullptr,0);}return 0;
}
Task.hpp #pragma once
#include iostream
#include string
#include vector
#include unordered_map
#include unistd.h
#include functionaltypedef std::functionvoid() func;//定义一个函数类型返回值类型void参数()std::vectorfunc callbacks;//回调
std::unordered_mapint, std::string desc;void readMySQL()
{std::cout sub process[ getpid() ] 执行访问数据库的任务\n std::endl;
}void execuleUrl()
{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.insert({callbacks.size(), readMySQL: 读取数据库});//相当于构建键值对0号命令对应readmysqlcallbacks.push_back(readMySQL);//下标为0插入ReadMysqldesc.insert({callbacks.size(), execuleUrl: 进行url解析});callbacks.push_back(execuleUrl);desc.insert({callbacks.size(), cal: 进行加密计算});callbacks.push_back(cal);desc.insert({callbacks.size(), save: 进行数据的文件保存});callbacks.push_back(save);
}void showHandler()//显示当前所有的方法
{for(const auto iter : desc ){std::cout iter.first \t iter.second std::endl;}
}int handlerSize()//共有多少任务的处理方法
{return callbacks.size();
}
习题
1.以下描述正确的有B
A.进程之间可以直接通过地址访问进行相互通信
B.进程之间不可以直接通过地址访问进行相互通信
C.所有的进程间通信都是通过内核中的缓冲区实现的
D.以上都是错误的
A错误 进程之间具有独立性拥有自己的虚拟地址空间因此无法通过各自的虚拟地址进行通信A的地址经过B的页表映射不一定映射在什么位置
B正确
C错误 除了内核中的缓冲区之外还有文件以及网络通信的方式可以实现 2.以下选项属于进程间通信的是A,B,D[多选]
A.管道
B.套接字
C.内存
D.消息队列
典型进程间通信方式管道共享内存消息队列信号量。 除此之外还有网络通信以及文件等多种方式
C选项这里的内存太过宽泛并没有特指某种技术错误。
3.下列关于管道(Pipe)通信的叙述中正确的是(C)
A.一个管道可以实现双向数据传输
B.管道的容量仅受磁盘容量大小限制
C.进程对管道进行读操作和写操作都可能被阻塞
D.一个管道只能有一个读进程或一个写进程对其操作
A错误 管道是半双工通信是可以选择方向的单向通信
B错误 管道的本质是内核中的缓冲区通过内核缓冲区实现通信命名管道的文件虽然可见于文件系统但是只是标识符并非通信介质
C正确 管道自带同步没有数据读阻塞缓冲区写满写阻塞与互斥
D错误 多个进程只要能够访问同一管道就可以实现通信不限于读写个数
4.以下关于管道的描述中正确的是 [多选]B,D
A错误匿名管道只能用于具有亲缘关系的进程间通信命名管道可以用于同一主机上的任意进程间通信
B正确
C错误匿名管道需要在创建子进程之前创建因为只有这样才能复制到管道的操作句柄与具有亲缘关系的进程实现访问同一个管道通信
D正确
5.以下关于管道的描述中错误的是 [多选]B,C
A.可以通过int pipe(int pipefd[2])接口创建匿名管道其中pipefd[0]用于从管道中读取数据
B.可以通过int pipe(int pipefd[2])接口创建匿名管道其中pipefd[0]用于向管道中写入数据
C.若在所有进程中将管道的写端关闭则从管道中读取数据时会返回-1
D.管道的本质是内核中的一块缓冲区
管道本质是内核中的一块缓冲区多个进程通过访问同一块缓冲区实现通信。使用int pipe(int pipefd[2])接口创建匿名管道pipefd[0]用于从管道读取数据pipefd[1]用于向管道写入数据。管道特性半双工通信自带同步与互斥生命周期随进程提供字节流传输服务。在同步的提现中若管道所有写段关闭则从管道中读取完所有数据后继续read会返回0不再阻塞若所有读端关闭则继续write写入会触发异常导致进程退出
根据以上管道理解分析A正确B错误C错误D正确
因为题目为选择错误选项因此选择B和C选项