【linux深入剖析】管道的四种情况以及五种特性

CSDN 2024-07-20 14:37:01 阅读 91


🍁你好,我是 RO-BERRY

📗 致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识

🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油


在这里插入图片描述


目录

1. 管道的四种情况2. 管道的五种特性3. 初识进程池4. 进程池模型


1. 管道的四种情况

正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端写入数据了)

在正常情况下,如果管道没有数据了,读端会阻塞等待,直到写端向管道中写入了数据,此时读端会从管道中读取数据。这种方式被称为阻塞式读取。当读取完成后,如果管道仍然有数据可读,则读端可以继续读取;如果没有数据了,则读端会再次阻塞等待,直到有新的数据写入。阻塞式读取是管道最常见的使用方式之一。

正常情况,如果管道被写满了,写端必须等待,直到有空间为止(读端读走数据了)。

在管道中,如果写入端写入数据速度过快,当管道缓冲区被写满时,写入端将会被阻塞,直到有读取端读取缓冲区中的数据,腾出空间之后才能继续写入数据。这种情况下,写入端会一直等待直到管道中有足够的空间,以便能够继续写入数据。这种阻塞等待的机制被称为“阻塞写入”。

写端关闭,读端一直读取,读端会读到read返回值为0,表示读到文件结尾。

当所有写入端(write-end)关闭时,管道就被认为是“完成”了。在这之后,所有读取端(read-end)将收到一个文件结束标志,即 read 函数将返回 0。这个机制保证了读取端读取数据时不会读取到已经关闭的写入端写入的数据。

读端关闭,写端一直写入,OS会直接杀掉写端进程,通过向目标进程发送SIGPIPE(13号)信号终止进程。

当一个管道的读端被关闭后,再往这个管道写入数据会触发SIGPIPE信号,而默认的行为是终止进程。这是因为管道的写入端会一直等待读取端读取数据,当读取端关闭后,写入端就无法将数据写入到管道中,会导致数据丢失。因此,如果你想要在管道读端关闭的情况下继续向管道写入数据,可以在写入数据时捕获SIGPIPE信号并进行处理。


2. 管道的五种特性

匿名管道只能在具有亲缘关系的进程之间使用,例如父进程和子进程之间。

如果需要在不具有亲缘关系的进程之间进行通信,则需要使用命名管道或其他进程间通信机制。

匿名管道默认提供了同步机制,即写入端写入数据后会等待读取端读取数据后才会继续执行。

这是因为管道有一个缓冲区,当写入端向缓冲区写入数据时,如果缓冲区已满,写入操作就会被阻塞,直到读取端从缓冲区中读取了数据后才会继续执行.

匿名管道是面向字节流的,字节流是由一系列字节组成的数据流,这些字节按照顺序传输,没有特定的结构或格式。

在匿名管道中,数据被视为一系列无结构的字节,管道不会对数据进行解析或处理,只是简单地传输字节流。这使得匿名管道非常灵活,可以用于任何类型的数据传输,但也需要在应用程序中实现数据格式和解析逻辑。

匿名管道是一种进程间通信机制,其生命周期与创建它的进程相关联。

当创建一个匿名管道时,操作系统会为其分配一些内存空间,并为其建立相应的读取和写入端口。当创建它的进程结束后,该管道也会被关闭并销毁。因此,匿名管道的生命周期是随着创建它的进程的生命周期而结束的。

管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

半双工是指,在通信的两个方向上,同一时刻只能有一个方向上的数据传输。与之相对的是全双工通信,即在通信的两个方向上,同时都可以进行数据传输。而半双工通信则需要在通信的两个方向上轮流进行数据传输,这样会降低通信的效率。但在某些情况下,半双工通信也可以满足需求,比如在一些低带宽的环境中,或者一些低成本的嵌入式系统中。

举个例子,假设你在使用计算机时需要同时进行数据的上传和下载操作。如果你只有一条管道,那么只能在一个方向上传输数据,另一个方向则需要等待。这会极大地降低你的工作效率。因此,为了实现双向通信,需要建立两个管道,分别用于数据的上传和下载操作。这样,你就可以同时进行上传和下载操作,提高工作效率。

在这里插入图片描述


3. 初识进程

我们在系统当中的很多资源,在创建的时候都是会有成本的,这个成本无外乎就是时间以及空间的成本,我们创建一个进程本身就会有成本,我们需要花空间以及时间去创建它,创建都是系统层面的,我们都是直接或者间接使用了系统调用去创建。

我们创建都是为了完成某些任务,但是我们如果在任务来的时候才去创建,就比较耽误时间,如果我们提前将进程创建好,当任务到来的时候,我们直接派发给进程完成任务,就省略了这中间创建进程的时间,我们将这一批提前创建好的进程叫做进程池

学习了C++,其中的new和malloc,它的本质就是通过库函数或者关键字来申请内存,申请内存是在地址空间上进行的申请,但是这是OS来帮我们去做的事情,那么如果有100MB的数据,我们是一次申请10MB分十次去申请还是一次申请100MB更为高效呢?

我们在调用new和malloc的时候,它本质就是让OS去开辟空间,然后把空间给我们,这个过程也注定了malloc和new需要去调用系统层面的调用帮我们来申请空间,也就是说,我们一次申请100MB,那么这个过程只需要使用一次的系统调用,我们分十次申请,每次申请10MB呢,这就是需要使用十次的系统调用帮我们申请空间。

我们要清楚一件事情—调用系统调用是有成本的!!

进程池是一种常见的进程管理机制,它可以提高进程的利用效率和系统的稳定性。在 Linux 系统中,进程池主要应用于网络编程中,通过预先创建多个子进程,并将它们加入到任务队列中,等待主进程向队列中添加任务并分配给子进程处理。这样,就可以有效地避免频繁地创建和销毁进程,提高了系统的稳定性和效率。

进程池主要包含以下几个部分:

创建子进程池:在主进程中预先创建多个子进程,以备随时处理任务。创建任务队列:当有新的任务需要处理时,主进程将任务加入到任务队列中。分配任务:主进程从任务队列中取出一个任务,并将其分配给其中一个空闲的子进程来处理。处理任务:子进程从任务队列中取出一个任务,并执行相应的操作。退出子进程:当子进程完成任务后,不要直接退出,而是将自己重新加入到子进程池中,等待下一次分配任务。

与之对应的还有一个概念那就是内存池

内存池是一种预先分配一定数量的内存空间,用于减少动态内存分配和释放带来的开销。 它可以提高内存分配和释放的效率,避免由于频繁申请和释放内存而导致的内存碎片问题,进而提高程序的运行速度和稳定性。

内存池的实现方式有多种,其中一种常见的方式是使用链表数据结构来管理已经分配出去但是目前空闲的内存块,每次需要分配内存时,从链表中取出一个内存块,并将其标记为已占用;而释放内存时,只是将其标记为未占用,并将其插入到链表中。这种方式可以避免频繁地调用系统函数进行动态内存分配和释放,从而提高程序效率。


4. 进程池模型

对于一个进程池,我们需要维护一个进程队列,如果进程在忙就等待,如果进程空闲,那么就给空闲进程发任务让进程去处理。

在这里插入图片描述

总任务:

🐥定义一个池子,在里面放上固定数量的进程,有需求来了,就拿一个池中的进程来处理任务🐥等到处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务🐥如果有很多任务需要执行,池中的进程数量不够,任务就要等待之前的进程执行任务完毕归来,拿到空闲进程才能继续执行。🐥也就是说,进池中进程的数量是固定的,那么同一时间最多有固定数量的进程在运行🐥这样不会增加操作系统的调度难度,还节省了开关进程的时间,也一定程度上能够实现并发效果。




声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。