- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
linux系统内核为上层应用程序提供了多种进程间通信(IPC)的手段,适用于不同的场景,有些解决进程间数据传递的问题,另一些则解决进程间的同步问题。对于同样一种IPC机制,又有不同的API供应用程序使用,目前有POSIX IPC以及System V IPC可以为应用程序提供服务。后续的系列文章将逐一介绍消息队列,共享内存,信号量,socket,fifo等进程间通信方法,本篇文章主要总结了 管道 相关系统调用的使用方式。文中代码可以在这个 代码仓库 中获取,代码中使用了我自己实现的一个 单元测试框架 ,对测试框架感兴趣的同学可以参考 上一篇文章 .
在linux环境进行日常开发时,管道是一种经常用到的进程间通信方法。在shell环境下,'|'就是连接两个进程的管道,它可以把一个进程的标准输出通过管道写到另一个进程的标准输入,利用管道以及重定向,各种命令行工具经过组合之后可以实现一个及其复杂的功能,这也是继承自UNIX的一种编程哲学.
除了在shell脚本中使用管道,另一种方式是通过系统调用去操作管道。使用 pipe 或者 pipe2 创建管道,得到两个文件描述符,分别是管道的读端和写端,有了文件描述符,进程就可以像读写普通文件一样对管道进行 read 和 write 操作,操作完成之后调用 close 关闭管道的两个文件描述符即可。可以看到,当完成创建之后,管道的使用和普通文件相比没有什么区别.
管道有两个特点: 1) 通常只能在有亲缘关系的进程之间进行通信; 2) 阅后即焚;有亲缘关系是指,通信的两个进程可以是父子进程或者兄弟进程,这里的父子和兄弟是一个广义的概念, 子进程可以是父进程调用了多次 fork 创建出来的,而不仅局限在只经过一次 fork ,总之,只要通信双方的进程拿到了管道的文件描述符就可以使用管道了。说”阅后即焚“是因为管道中的数据在被进程读取之后就会被管道清除掉。有一个形象的比喻说,管道就像某个进程家族各个成员之间传递情报的中转站,情报内容阅后即焚.
在使用管道时,需要注意管道中数据的流动方向,通常都是把管道作为一个单向的数据通道使用的。虽然通信双方可以都持有管道的读端和写端,然后使用同一个管道实现双向通信,但这种方式实际上很少使用。下面通过几段代码说明几种使用管道的方法:
管道虽然时进程间通信的一种手段,但一个进程自言自语也是可以的,内核并没有限制管道的两端必须由不同的进程操作。下面的代码展示了一个孤独的进程怎样通过管道自言自语,代码中使用了自己实现的测试框架 cutest 。执行之后它将从管道的另一头收到前一个时刻发给自己的消息.
CUTEST_CASE(basic_pipe, talking_to_myself) {
int pipefd[2];
pipe(pipefd);
const char *msg = "I'm talking to myself";
write(pipefd[1], msg, strlen(msg));
char buf[32];
read(pipefd[0], buf, 32);
printf("talking_to_myself: %s\n", buf);
close(pipefd[0]);
close(pipefd[1]);
}
自言自语始终是太过无聊,是时候让父子进程之间聊点什么了。因为 fork 之后的子进程会继承父进程的文件描述符, fork 之前父进程向管道写入的数据,子进程可以在管道的另一端读到.
CUTEST_CASE(basic_pipe, parent2child) {
int pipefd[2];
pipe(pipefd);
const char *msg = "parent write, child read";
write(pipefd[1], msg, strlen(msg));
if (fork() == 0) {
close(pipefd[1]);
char buf[64];
memset(buf, 0, 64);
read(pipefd[0], buf, 64);
printf("parent2child: %s\n", buf);
exit(0);
}
close(pipefd[0]);
close(pipefd[1]);
}
管道的方向是由通信双方操作的文件描述符决定的,子进程同样可以传递消息给父进程.
CUTEST_CASE(basic_pipe, child2parent) {
int pipefd[2];
pipe(pipefd);
if (fork() == 0) {
close(pipefd[0]);
const char *msg = "parent read, child write";
write(pipefd[1], msg, strlen(msg));
close(pipefd[1]);
exit(0);
}
close(pipefd[1]);
char buf[64];
memset(buf, 0, 64);
read(pipefd[0], buf, 64);
printf("child2parent: %s\n", buf);
close(pipefd[0]);
}
当有多个子进程时,只要它们持有了管道的文件描述符,就可以利用管道通信,把父进程写进管道的数据读取出来。当然,在具体的应用中需要考虑子进程的读取顺序等因素,下面的例子只是简单的创建了多个子进程,每个进程读取一个int类型的数据,开始阶段由父进程向管道写入数据,需要说明一点,三个子进程并没有将管道内的数据都读完,当所有引用了这个管道的文件描述符都关闭了之后,内核也会在适当的时机销毁自己维护的管道.
void fork_child_read(int id, int pipefd[2], const char *msg_pregix) {
if (fork() == 0) {
close(pipefd[1]);
int n;
read(pipefd[0], &n, sizeof(int));
printf("%s: child %d get data %d\n", msg_pregix, id, n);
close(pipefd[0]);
exit(0);
}
}
CUTEST_CASE(basic_pipe, parent2children) {
int pipefd[2];
pipe(pipefd);
for (int i = 1; i <= 10; i++)
write(pipefd[1], &i, sizeof(int));
const char *msg_prefix = "parent2children:";
fork_child_read(1, pipefd, msg_prefix);
fork_child_read(2, pipefd, msg_prefix);
fork_child_read(3, pipefd, msg_prefix);
close(pipefd[0]);
close(pipefd[1]);
}
考虑这样一种场景,一个任务需要由多个子进程进行处理,最终的计算结果需要由父进程汇总,下面的代码模拟了这样的场景,代码中创建了两个子进程向管道写入数据,父进程则一直尝试读取管道内的数据.
void fork_child_write(int pipefd[2], int data) {
if (fork() == 0) {
close(pipefd[0]);
write(pipefd[1], &data, sizeof(int));
close(pipefd[1]);
exit(0);
}
}
CUTEST_CASE(basic_pipe, children2parent) {
int pipefd[2];
pipe(pipefd);
int data[] = {512, 1024};
fork_child_write(pipefd, data[0]);
fork_child_write(pipefd, data[1]);
close(pipefd[1]);
int n;
while (read(pipefd[0], &n, sizeof(int)) == sizeof(int)) {
printf("children2parent: get data %d\n", n);
}
close(pipefd[0]);
}
如果有两个兄弟进程,进程A需要得到进程B的计算结果之后才能完成自己的任务,这时也可以用管道通信。代码中分别创建了两个进程对管道进行写和读操作,实际应用中经常还需要一种通知机制,让等待的进程知道它依赖的任务已经就绪了,这需要用到信号量,后续文章会介绍。下面代码的第二个进程在 read 操作时是阻塞的,会一直等到管道中数据可读,因为创建管道时没有指定 O_NONBLOCK 标志.
CUTEST_CASE(basic_pipe, two_children) {
int pipefd[2];
pipe(pipefd);
const char *msg = "pipe between two children";
if (fork() == 0) {
close(pipefd[0]);
write(pipefd[1], msg, strlen(msg));
close(pipefd[1]);
exit(0);
}
if (fork() == 0) {
close(pipefd[1]);
char buf[64];
memset(buf, 0, 64);
read(pipefd[0], buf, 64);
printf("two_children: %s\n", buf);
close(pipefd[0]);
exit(0);
}
close(pipefd[0]);
close(pipefd[1]);
}
前面的例子中提到了管道的阻塞和非阻塞,这里详细说明一下这个问题。对于一个阻塞的管道,如果进程在 read 时,系统中存在没有关闭的写端文件描述符,但此时管道是空的, read 操作就会阻塞在这里。可以这样理解,因为写端的存在, read 就固执地认为在未来的某个时刻一定会有人会向管道中写入数据,所以它就阻塞在这里。对于非阻塞的管道,在前面的条件下, read 会立即返回。上述的特性就要求我们在使用阻塞类型的管道时要 及时关闭不使用的文件描述符 ,因为进程 read 操作时在等待的写端文件描述符很可能是由当前进程打开的,当系统中管道的其他写端都关闭了的时候,当前进程的 read 就会出现自己等自己的问题,类似死锁.
CUTEST_CASE(basic_pipe, blocking_read) {
int pipefd[2];
pipe(pipefd);
if (fork() == 0) {
/* NOTE: remove the comment below if you don't want child process
* blocking while reading data from pipe. Otherwise you will see that
* there is still a "basic-pipe" process after you finish this test, and
* you have to kill it manually.*/
// close(pipefd[1]);
int num;
read(pipefd[0], &num, sizeof(int));
/* NOTE: since the write end of pipe is a valid file descriptor in
* current process, the print below should never execute.*/
printf("should NEVER goes here\n");
exit(0);
}
close(pipefd[0]);
close(pipefd[1]);
printf("blocking_read: parent process exit\n");
}
上述代码使用的是阻塞类型的管道, fork 出的进程没有关闭管道的写端,然后执行了 read 操作,当父进程退出之后,系统中仍存在这个管道的写端描述符,并且就在已经处于睡眠状态下的子进程中,这种情况下将不会再有人向管道中写入数据,子进程会一直睡眠。运行代码之后使用 ps 命令可以看到这个睡死过去的子进程.
以下是上述测试的执行结果,可以看到在程序退出之后仍然由一个"basic-pipe"进程,这是因为3.6节中的代码在子进程中没有及时关闭不使用的管道文件描述符。此时不得不手动把睡死的进程 kill 掉了.
[junan@arch1 test-all]$ make install
[junan@arch1 test-all]$ ./script/run_test.sh basic-pipe
blocking_read: parent process exit
two_children: pipe between two children
children2parent: get data 512
children2parent: get data 1024
parent2children:: child 1 get data 1
parent2children:: child 2 get data 2
parent2children:: child 3 get data 3
child2parent: parent read, child write
talking_to_myself: I'm talking to myself
cutest summary:
[basic_pipe] suit result: 7/7
[basic_pipe::blocking_read] case result: Pass
[basic_pipe::two_children] case result: Pass
[basic_pipe::children2parent] case result: Pass
[basic_pipe::parent2children] case result: Pass
[basic_pipe::child2parent] case result: Pass
[basic_pipe::parent2child] case result: Pass
[basic_pipe::talking_to_myself] case result: Pass
parent2child: parent write, child read
[junan@arch1 test-all]$ ps -e|grep basic-pipe
18866 pts/2 00:00:00 basic-pipe
[junan@arch1 test-all]$ kill -9 18866
[junan@arch1 test-all]$ ps -e|grep basic-pipe
[junan@arch1 test-all]$
以上的几段示例代码说明了管道的一些基本使用方法和注意事项,下面看一个使用管道和多进程生成质数的问题。我们的需求是这样的,给定一个整数 nmax ,生成[2, nmax]区间上的所有质数,并且要求生成质数的核心逻辑使用管道和多进程。第一次碰到这个问题是在xv6操作系统的lab中,也是为了说明 pipe 和 fork 的使用.
看到这里,不妨先稍微思考一下?一个简单的想法可能是这样的,首先有一个函数,其功能是判断输入的n是否是质数,接下来遍历[2, nmax]上的整数,并且用之前的函数把质数都过滤出来,但问题是如何用管道和多进程实现这个函数的过滤功能呢?OK, 思考结束,来看看管道加多进程版本的质数生成器算法思路:
这个“质数筛子”中的每个进程主要有三个任务,1)从pipe1读取第一个数据并打印出来,并且它一定是质数;2)用得到的质数过滤pipe1中的其他数据,并把过滤出来的数据写入pipe2;3) fork 自己的子进程,并把pipe2传递给它;具体的代码实现如下,当过滤之后没有数据时,就不会继续创建子进程了.
void generate_primes(int pipe1[2]) {
close(pipe1[1]);
int prime = 0;
int err = read(pipe1[0], &prime, sizeof(int));
if (err <= 0) {
close(pipe1[0]);
return;
}
printf("%d\n", prime);
int pipe2[2];
pipe(pipe2);
pid_t pid = fork();
if (pid == 0) {
generate_primes(pipe2);
} else {
int num = 0;
while ((err = read(pipe1[0], &num, sizeof(int))) > 0) {
if (num % prime) {
write(pipe2[1], &num, sizeof(int));
}
}
}
close(pipe1[0]);
close(pipe2[0]);
close(pipe2[1]);
exit(0);
}
CUTEST_SUIT(prime_numbers_pipe)
CUTEST_CASE(prime_numbers_pipe, prime_number_max30) {
int nmax = 30;
int pipe1[2];
pipe(pipe1);
for (int i = 2; i <= nmax; ++i)
write(pipe1[1], &i, sizeof(int));
if (fork() == 0) {
generate_primes(pipe1);
}
close(pipe1[0]);
close(pipe1[1]);
}
代码中生成的是2到30区间上的质数,执行结果如下:
[junan@arch1 test-all]$ ./script/run_test.sh prime-number-pipe
cutest summary:
[prime_numbers_pipe] suit result: 1/1
[prime_numbers_pipe::prime_number_max30] case result: Pass
2
3
5
7
11
13
17
19
23
29
[junan@arch1 test-all]$
管道是一种比较基础和常用的进程间通信方法,在使用过程中需要注意及时关闭不再使用的文件描述符的问题,否则可能使得进程一直睡眠。文中的代码示例可以在 我的代码仓库 中找到,有兴趣的可以自己clone下来实际跑跑看。后续会继续更新其他的IPC相关的文章,并在最后使用各种IPC方法实现一个小项目,有想法的欢迎在评论区冒泡.
最后此篇关于linux环境编程(2):使用pipe完成进程间通信的文章就讲到这里了,如果你想了解更多关于linux环境编程(2):使用pipe完成进程间通信的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
从 Redis 获取消息时,onDone:(){print('done')} 从未起作用。 import 'package:dartis/dartis.dart' as redis show PubS
昨天我玩了一些vim脚本,并设法通过循环来对当前输入的内容进行状态栏预测(请参见屏幕截图(灰色+黄色栏))。 问题是,我不记得我是怎么得到的,也找不到我用于该vim魔术的代码片段(我记得它很简单):它
我尝试加载 bash_completion在我的 bash (3.2.25) 中,它不起作用。没有消息等。我在我的 .bashrc 中使用了以下内容 if [ -f ~/.bash_completio
我正在尝试构建一个 bash 完成例程,它将建议命令行标志和合适的标志值。例如在下面 fstcompose 命令我想比赛套路先建议 compose_filter= 标志,然后建议来自 [alt_seq
当我尝试在重定向符号后完成路径时,bash 完成的行为就好像它仍在尝试在重定向之前完成命令的参数一样。 例如: dpkg -l > /med标签 通过在 /med 之后点击 Tab我希望它完成通往 /
我的类中有几个 CAKeyframeAnimation 对象。 他们都以 self 为代表。 在我的animationDidStop函数中,我如何知道调用来自哪里? 是否有任何变量可以传递给 CAKe
我有一个带有 NSDateFormatter 的 NSTextField。格式化程序接受“mm/dd/yy”。 可以自动补全日期吗?因此,用户可以输入“mm”,格式化程序将完成当前月份和年份。 最佳答
有一个解决方案可以使用以下方法完成 NSTextField : - (NSArray *)control:(NSControl *)control textView:(NSTextView *)tex
我正在阅读 Passport 的文档,我注意到 serialize()和 deserialize() done()被调用而不被返回。 但是,当使用 passport.use() 设置新策略时在回调函数
在 ubuntu 11.10 上的 Firefox 8.0 中,尽管 img.complete 为 false,但仍会调用 onload 函数 draw。我设法用 setTimeout hack 解决
假设我有两个与两个并行执行的计算相对应的 future 。我如何等到第一个 future 准备好?理想情况下,我正在寻找类似于Python asyncio's wait且参数为return_when=
我正在寻找一种 Java 7 数据结构,其行为类似于 java.util.Queue,并且还具有“最终项目已被删除”的概念。 例如,应可以表达如下概念: while(!endingQueue.isFi
这是一个简单的问题。 if ($('.dataTablePageList')) { 我想做的是执行一个 if 语句,该语句表示如果具有 dataTablesPageList 类的对象也具有 menu
我用replaceWith批量替换了许多div中的html。替换后,我使用 jTruncate 来截断文本。然而它不起作用,因为在执行时,replaceWith 还没有完成。 我尝试了回调技巧 ( H
有没有办法调用 javascript 表单 submit() 函数或 JQuery $.submit() 函数并确保它完成提交过程?具体来说,在一个表单中,我试图在一个 IFrame 中提交一个表单。
我有以下方法: function animatePortfolio(fadeElement) { fadeElement.children('article').each(function(i
我刚刚开始使用 AndEngine, 我正在像这样移动 Sprite : if(pValueY < 0 && !jumping) { jumping =
我正在使用 asynctask 来执行冗长的操作,例如数据库读取。我想开始一个新 Activity 并在所有异步任务完成后呈现其内容。实现这一目标的最佳方法是什么? 我知道 onPostExecute
我有一个脚本需要命令名称和该命令的参数作为参数。 所以我想编写一个完成函数来完成命令的名称并完成该命令的参数。 所以我可以这样完成命令的名称 if [[ "$COMP_CWORD" == 1 ]];
我的应用程序有一个相当奇怪的行为。我在 BOOT_COMPLETE 之后启动我的应用程序,因此在我启动设备后它是可见的。 GUI 响应迅速,一切正常,直到我调用 finish(),按下按钮时,什么都没
我是一名优秀的程序员,十分优秀!