进程间通信的目的
进程是一个独立的资源分配单元,不同进程(这里所说的进程通常指的是用户进程)之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源(例如打开的文件描述符)。但是,进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信( IPC)
进程间通信的目的:
(1)数据传输:一个进程需要将它的数据发送给另一个进程。
(2)通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程);
(3)资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供互斥和同步机制。
(4)进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
进程间通信的方式与比较
(1)管道
管道又分为3种:
1)普通管道(PIPE):有2种限制(单工,即单向传输,且只可以在父子或兄弟进程之间使用)
2)流管道(s_pipe):为半双工,只可以在父子或兄弟之间传递,可以双向传输。
3)命名管道(name_pipe)(FIFO):可以在许多不相关之间的进程通信。
(2)信号量
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是前一节的共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:
1) 测试控制该资源的信号量。
2) 若此信号量的值为正,则允许进行使用该资源。进程将信号量减1。
3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。
4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。
(3)消息队列
消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
https://www.jianshu.com/p/36a7775b04ec
https://www.cnblogs.com/ruiati/p/6649868.html
(4)信号
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
(5)共享内存
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
(6)套接字(socket)
套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
线程间通信方式:
共享内存
进程与线程的区别
根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位。
在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)。
内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
健壮性:进程有独立的地址空间,进程崩溃后在保护模式下不会对其他进程产生影响。
而线程只是进程中不同的执行路线,有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程发生异常崩溃或死掉就等于整个进程死掉,因此多进程的程序比多线程的程序健壮。但进程切换时,耗费资源巨大,效率差些。
同步主要通过互斥锁和条件变量
线程同步: https://blog.csdn.net/guoxiang3538/article/details/79376191
https://baike.baidu.com/item/线程同步/4855164?fr=aladdin
临界区,互斥对象,信号量,事件对象
并发,并行/同步异步 https://blog.csdn.net/guoxiang3538/article/details/79376191
进程和程序:
程序:通常为二进制程序,放置在存储媒介中(硬盘/光盘/软盘/磁盘),以物理文件形式存在。
进程:程序被触发后,执行者的权限与属性,程序的代码与所需数据等都会被加载在内存中,操作系统给与这个内存中的单元一个标识符,可以说进程是一个正在运行中的程序。
子进程可以获得父进程的环境变量
Fork and exec:
linux的程序调用通常称为fork and exec过程,进程会借用父进程以复制的方式产生一个一摸一样的子进程,然后被复制出来的子进程再以exec的方式执行实际要执行的进程,最终成为一个子进程
内核态/用户态/系统调用/常见系统调用
内存空间用于存储程序和数据,所有程序必须再内存中才可以运行。用于容纳操作系统的内存空间叫做系统空间。容纳应用程序的内存空间叫用户空间。操作系统又称为内核。
程序要访问设备必须通过内核。
内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。
用户态:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取。
为什么会有用户态和内核态:由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级 -- 用户态和内核态。
系统调用:fork read write mkdir chdir open close exit
用户态到内核太的切换:
(1)系统调用
这是用户态进程主动要求切换到内核态的一种方式。用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。例如fork()就是执行了一个创建新进程的系统调用。系统调用的机制和新是使用了操作系统为用户特别开放的一个中断来实现,如Linux的int 80h中断。
(2)异常
当cpu在执行运行在用户态下的程序时,发生了一些没有预知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关进程中,也就是切换到了内核态,如缺页异常。
(3)外围设备的中断
当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令而转到与中断信号对应的处理程序去执行,如果前面执行的指令时用户态下的程序,那么转换的过程自然就会是 由用户态到内核态的切换。如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后边的操作等。
这三种方式是系统在运行时由用户态切换到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。从触发方式上看,切换方式都不一样,但从最终实际完成由用户态到内核态的切换操作来看,步骤有事一样的,都相当于执行了一个中断响应的过程。系统调用实际上最终是中断机制实现的,而异常和中断的处理机制基本一致。
参考博客: https://blog.csdn.net/qq_39823627/article/details/78736650
fork过程:
fork()是内核程序创建进程的一种方式(其他还有vfork()和clone()方法),由fork()创建的新进程被称为子进程(child process)。需要特别注意的是:该函数被调用一次,但返回两次。两次分别返回父进程和子进程。子进程的返回的是0(内核代码中设定的为0),而父进程返回的是子进程的进程PID。父进程和子进程共享代码段,但是分别拥有自己的数据段和堆栈段。一个进程的子进程可以不止一个。对子进程来说,之所以fork返回0给它,是因为它随时可以调用getpid()来获取自己的PID;也可以调用getppid()来获取父进程的PID。
fork之后,操作系统会拷贝出一个与父进程完全相同的子进程,这两个进程共享代码段空间,但是数据段是互相独立的,子进程数据空间中的内容是父进程的完整拷贝,指令指针也完全相同,子进程拥有父进程当前运行到的位置(两进程的程序计数器pc值相同,也就是说,子进程是从fork返回处开始执行的)。如果fork不成功,父进程会返回错误。
消息队列
消息队列可以看作是一个存放消息的容器,当我们需要使用消息的时候可以取出消息供自己使用。消息队列是分布式系统中重要的组件,使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性。目前使用较多的消息队列有ActiveMQ,RabbitMQ,Kafka,RocketMQ
为什么使用消息队列:
1.通过异步处理提高系统性能(削峰、减少响应所需时间);
在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。
消息队列具有很好的削峰作用的功能——即通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。
因为用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败。因此使用消息队列进行异步处理之后,需要适当修改业务流程进行配合,比如用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功,以免交易纠纷。这就类似我们平时手机订火车票和电影票。
2 降低系统耦合性。
如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。
另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。