IPC

996Worker
996Worker
发布于 2021-07-13 / 437 阅读
0
0

IPC

Q: Please talk about IPC.

A:

1. 常见通信方式

  1. 管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

    父进程初始化管道,管道有两个描述符,进口入口。


父进程fork出来子进程,它永扬拥有指向同一个管道的两个描述符。


父进程删掉读,子进程删掉写,这样父给子单向发信息。

匿名管道主要利用了,创建子进程的时候会把父进程的文件描述符表拷贝一份这个特征,通过这个特征,父子进程就看到了一个公共的资源—管道,并同时拥有对该管道腹泻的权利,那么一方读,一方写,就可以完成进程之间的通信了。
所谓的匿名管道就是说,没有名字。。。你根本不知道这个管道文件存放在哪,也不知道这个管道文件的文件名,唯一跟这个跟管道文件有联系的只有父子进程中的文件描述符。那么根据匿名管道的实现机制,很容易就能看出他的优缺点。

  1. 命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
    简单的来说,我们刚才使用的匿名管道是因为不知道文件名和存放路径,所以只能通过继承文件描述符表来获得跟匿名管道建立联系的方式,如果我们知道路径和管道文件名呢?那不就可以完成非亲缘关系的进程间通信了么

    • 函数原型 int mkfifo(const char *pathname,mode_t mode)

    • 其中第一个参数pathname就是路径了,mode 就是创建的管道的访问权限umask

    • 头文件:#include <sys.stat.h> #include <sys/types.h>

    • 返回值,成功返回0,失败返回-1

      管道的n个特征

      • 管道是依赖于文件系统的,创建好管道之后,一定要关闭不使用的读写端
      • 只有父子进程才可以使用管道通信,也就是所谓的有血缘关系的进程进行进程间通信。(匿名管道独有)
      • 管道是基于数据流的,面向字节流!(后面会提到消息队列是面向数据块的,对比来看会好懂一些)
      • 管道只能称之为单向数据通信,连半双工都算不上
      • 同步与互斥问题不需要考虑了,管道已经考虑了
      • 当父子进程退出的时候,管道的生命周期就结束了,也就是说管道的生命周期就是进程
        因为管道是基于文件系统的,所以会有I/O系统调用,性能就低了。
  2. 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

一存,一发,一消费 -- 转发器:生产者先将消息投递一个叫做「队列」的容器中,然后再从这个容器中取出消息,最后再转发给消费者,仅此而已。


上面这个图便是消息队列最原始的模型,它包含了两个关键词:消息和队列。

1、消息:就是要传输的数据,可以是最简单的文本字符串,也可以是自定义的复杂格式(只要能按预定格式解析出来即可)。
2、队列:大家应该再熟悉不过了,是一种先进先出数据结构。它是存放消息的容器,消息从队尾入队,从队头出队,入队即发消息的过程,出队即收消息的过程。

3.1 队列模型

最初的消息队列就是上一节讲的原始模型,它是一个严格意义上的队列(Queue)。消息按照什么顺序写进去,就按照什么顺序读出来。不过,队列没有 “读” 这个操作,读就是出队,从队头中 “删除” 这个消息。


这便是队列模型:它允许多个生产者往同一个队列发送消息。但是,如果有多个消费者,实际上是竞争的关系,也就是一条消息只能被其中一个消费者接收到,读完即被删除。

3.2 发布-订阅模型

如果需要将一份消息数据分发给多个消费者,并且每个消费者都要求收到全量的消息。很显然,队列模型无法满足这个需求。

一个可行的方案是:为每个消费者创建一个单独的队列,让生产者发送多份。这种做法比较笨,而且同一份数据会被复制多份,也很浪费空间。

为了解决这个问题,就演化出了另外一种消息模型:发布-订阅模型。


在发布-订阅模型中,存放消息的容器变成了 “主题”,订阅者在接收消息之前需要先 “订阅主题”。最终,每个订阅者都可以收到同一个主题的全量消息。

仔细对比下它和 “队列模式” 的异同:生产者就是发布者,队列就是主题,消费者就是订阅者,无本质区别。唯一的不同点在于:一份消息数据是否可以被多次消费。

3.3 总结上述模型

最后做个小结,上面两种模型说白了就是:单播和广播的区别。而且,当发布-订阅模型中只有 1 个订阅者时,它和队列模型就一样了,因此在功能上是完全兼容队列模型的。

3.4 MQ应用

目前,MQ 的应用场景非常多,大家能倒背如流的是:系统解耦、异步通信和流量削峰。除此之外,还有延迟通知、最终一致性保证、顺序消息、流式处理等等。

那到底是先有消息模型,还是先有应用场景呢?答案肯定是:先有应用场景(也就是先有问题),再有消息模型,因为消息模型只是解决方案的抽象而已。

我们试着重新理解下消息队列的模型。它其实解决的是:生产者和消费者的通信问题。那它对比 RPC 有什么联系和区别呢?

通过对比,能很明显地看出两点差异:

1、引入 MQ 后,由之前的一次 RPC 变成了现在的两次 RPC,而且生产者只跟队列耦合,它根本无需知道消费者的存在。
2、多了一个中间节点「队列」进行消息转储,相当于将同步变成了异步。

举一个实际例子,比如说电商业务中最常见的「订单支付」场景:在订单支付成功后,需要更新订单状态、更新用户积分、通知商家有新订单、更新推荐系统中的用户画像等等。

引入 MQ 后,订单支付现在只需要关注它最重要的流程:更新订单状态即可。其他不重要的事情全部交给 MQ 来通知。这便是 MQ 解决的最核心的问题:系统解耦。

  1. 共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

*在jdk1.4中提供的类MappedByteBuffer为我们实现共享内存提供了较好的方法。该缓冲区实际上是一个磁盘文件的内存映像。二者的变化将保持同步,即内存数据发生变化会立刻反映到磁盘文件中,这样会有效的保证共享内存的实现。 *

**共享内存的使用有如下几个特点: **

  • 可以被多个进程打开访问;
  • 读写操作的进程在执行读写操作时其他进程不能进行写操作;
  • 多个进程可以交替对某一共享内存执行写操作;
  • 一个进程执行了内存的写操作后,不影响其他进程对该内存的访问。同时其他进程对更新后的内存具有可见性。
  • 在进程执行写操作时如果异常退出,对其他进程写操作禁止应自动解除。
  • 相对共享文件,数据访问的方便性和效率有
  1. 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
    典型的例子就是锁。

  2. 套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

  3. 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生


评论