线程/进程通信方式

本文详细介绍了进程间通信的多种方式,如管道、命名管道、信号量、消息队列、信号、共享内存和套接字,以及各自优缺点。接着讨论线程间的通信机制,包括 volatile 和 synchronized 关键字、ReentrantLock 结合 Condition、wait/notify 机制、管道通信、ThreadLocal 和 Thread.join。最后,文章阐述了 Java 中线程阻塞唤醒的四种方式,以及并行与并发的概念,进程和线程的区别,并探讨了协程的高效性和异步特性。

进程间的通信方式

  1. 管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有血缘关系的进程间使用。进程的血缘关系通常指父子进程关系
  2. 命名管道(FIFO):命名管道也是半双工的通信方式,但是它允许无亲缘关系进程间通信
  3. 信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段
  4. 消息队列(message queue):消息队列是由消息组成的链表,存放在内核中 并由消息队列标识符标识。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点
  5. 信号(signal):信号是一种比较复杂的通信方式,用于通知接收进程某一事件已经发生
  6. 共享内存(shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问,共享内存是最快的IPC方式,它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信
  7. 套接字(socket):套接口也是一种进程间的通信机制,与其他通信机制不同的是它可以用于不同机器间的进程通信

通信方式的优缺点:
1.无名管道简单方便.但局限于单向通信的工作方式.并且只能在创建它的进程及其子孙进程之间实现管道的共享:有名管道虽然可以提供给任意关系的进程使用.但是由于其长期存在于系统之中,使用不当容易出错。

2.消息缓冲可以不再局限于父子进程.而允许任意进程通过共享消息队列来实现进程间通信.并由系统调用函数来实现消息发送和接收之间的同步.从而使得用户在使用消息缓冲进行通信时不再需要考虑同步问题.使用方便,但是信息的复制需要额外消耗CPU的时间.不适宜于信息量大或操作频繁的场合。

3.共享内存针对消息缓冲的缺点改而利用内存缓冲区直接交换信息,无须复制,快捷、信息量大是其优点。但是共享内存的通信方式是通过将共享的内存缓冲区直接附加到进程的虚拟地址空间中来实现的.因此,这些进程之间的读写操作的同步问题操作系统无法实现。必须由各进程利用其他同步工具解决。另外,由于内存实体存在于计算机系统中.所以只能由处于同一个计算机系统中的诸进程共享。不方便网络通信。

线程间的通信机制

  线程间通信由于多线程共享地址空间和数据空间,所以多个线程间的通信是一个线程的数据可以直接提供给其他线程使用,而不必通过操作系统,所以线程间通信和同步的方式主要有锁、信号、信号量

  • volatile 和 synchronized 关键字
      volatile 可以修饰字段,告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回主内存,它能保证所有线程对变量访问的可见性。
      synchronized 可以修饰方法或以同步块的形式使用,它主要确保多个线程在同一个时刻只能有一个线程处于方法或同步块中,保证了线程对变量访问的可见性和排他性。
  • 使用 ReentrantLock 结合 Condition
  • 使用Object类的wait/notify机制
       等待通知机制是指一个线程 A 调用了对象 O 的 wait 方法进入等待状态,而另一个线程 B 调用了对象 O 的 notify 或 notifyAll 方法,线程 A 收到通知后从 wait 方法返回,进而执行后序操作。两个线程通过对象 O 完成交互,对象上的 wait 和 notify/notifyAll 就如同开关信号,用来完成等待方和通知方之间的交互工作。
  • 管道通信
      管道 IO 流和普通文件IO 流或网络 IO 流的不同之处在于它主要用于线程之间的数据传输,传输的媒介为内存。管道流主要用PipedInputStream和PipedOutputStream进行通信来实现两个线程之间的二进制数据的传。
  • ThreadLocal
      ThreadLoacl 是线程变量,内部以 ThreadLocal 为键,任意对象为值的存储结构实现。该结构绑定在每个线程上,存储的值在每个线程中都是一个唯一副本,每个线程可以通过 ThreadLocal 对象访问自己唯一的值。
      这种存储结构叫 ThreadLocalMap ,是 ThreadLocal 的一个静态内部类,是一个弱引用集合,它的存值、取值实现类似于 HashMap,使用 set 设置值,使用 get 获取值。使用弱引用的目的是为了节约资源,如果执行过程中发生了 GC,ThreadLocal 是 null 但由于 ThreadLocalMap 生命周期和线程一样,不会被回收,这时候就会导致 ThreadLocalMap 的 key 不存在而 value 还在的内存泄漏问题,解决办法是使用完 ThreadLocal 后执行remove操作。
  • Thread.join
      如果一个线程执行了某个线程的 join 方法,这个线程就会阻塞等待执行了 join 方法的线程终止之后才返回,这里涉及了等待/通知机制。join 方法的底层是通过 wait 方法实现的,当线程终止时会调用自身的 notifyAll 方法,通知所有等待在该线程对象上的线程。
  • while轮询
      多线程同时执行,会牺牲部分CPU性能。在这种方式下,线程A不断地改变条件,线程ThreadB不停地通过while语句检测这个条件是否成立 ,从而实现了线程间的通信。但是这种方式会浪费CPU资源,是因为JVM调度器将CPU交给线程B执行时,它没做啥“有用”的工作,只是在不断地测试 某个条件是否成立。

java线程阻塞唤醒的四种方式

suspend与resume
  Java废弃 suspend() 去挂起线程的原因,是因为 suspend() 在导致线程暂停的同时,并不会去释放任何锁资源。其他线程都无法访问被它占用的锁。直到对应的线程执行 resume() 方法后,被挂起的线程才能继续,从而其它被阻塞在这个锁的线程才可以继续执行。
  但是,如果 resume() 操作出现在 suspend() 之前执行,那么线程将一直处于挂起状态,同时一直占用锁,这就产生了死锁。而且,对于被挂起的线程,它的线程状态居然还是 Runnable。

wait与notify
  wait与notify必须配合synchronized使用,因为调用之前必须持有锁,wait会立即释放锁,notify则是同步块执行完了才释放

await与singal
  Condition类提供,而Condition对象由new ReentLock().newCondition()获得,与wait和notify相同,因为使用Lock锁后无法使用wait方法

park与unpark
  LockSupport是一个非常方便实用的线程阻塞工具,它可以在线程任意位置让线程阻塞。和Thread.suspenf()相比,它弥补了由于resume()在前发生,导致线程无法继续执行的情况。和Object.wait()相比,它不需要先获得某个对象的锁,也不会抛出IException异常。可以唤醒指定线程。

总结
1、unpark会直接指定要解除阻塞的线程,而notify需要知道有一个确定的线程在wait,如果有多个线程在阻塞,则不能确定知道哪个会被解除阻塞
2、wait和notify有先后顺序,即必须先wait,再notify才能解除,而park和unpark则没有,可以先给权限,再阻塞,阻塞会直接返回
3、wait时线程如果被interrupt,会报错InterruptedException,而park时则会正常结束
4、wait/notify面向对象,而LockSupport面向线程

1、wait与await区别:

  • wait与notify必须配合synchronized使用,因为调用之前必须持有锁,wait会立即释放锁,notify则是同步块执行完了才释放
  • 因为Lock没有使用synchronized机制,故无法使用wait方法区操作多线程,所以使用了Condition的await来操作

2、Lock实现主要是基于AQS,而AQS实现则是基于LockSupport,所以说LockSupport更底层,所以使用park效率会高一些

并行与并发

并行:指多个任务同时执行
并发:指在一个时间段内,多个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行(即多个任务在同一处理机上交替执行)

对单核CPU,因为一个CPU一次只能执行一条指令,是无法做到并行,只能做到并发。

进程和线程的区别

  进程是系统进行资源分配和调度的一个独立单位。
  线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程有自己的堆栈和局部变量,线程拥有自己的栈空间(如程序计数器、一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

区别
  进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,拥有独立的执行序列,但线程之间没有单独的地址空间。在进程切换时,耗费资源较大,效率相比于线程切换要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

进程拥有的资源

进程资源:
1、虚拟地址空间
2、一个全局唯一的进程ID (PID)
3、一个可执行映像(image),也就是该进程的程序文件在内存中的表示
4、一个或多个线程
5、一个位于内核空间中的名为EPROCESS(executive process block,进程执行块)的数据结构,用以记录该进程的关键信息,包括进程的创建时间、映像文件名称等
6、一个位于内核空间中的对象句柄表,用以记录和索引该进程所创建/打开的内核对象。操作系统根据该表格将用户模式下的句柄翻译为指向内核对象的指针
7、一个位于描述内存目录表起始位置的基地址,简称页目录基地址(DirBase),当CPU切换到该进程/任务时,会将该地址加载到CR3寄存器,这样当前进程的虚拟地址才会被翻译为正确的物理地址
8、一个位于用户空间中的进程环境块(Process Environment Block, PEB)
9、一个访问权限令牌(access token),用于表示该进程的用户、安全组以及优先级别

线程之间共享的资源

共享的资源有:
1、堆
2、全局变量
3、静态变量。存于堆中开辟的.bss和.data段,是共享的
4、进程代码段
5、进程打开的文件描述符
6、进程的当前目录
7、进程用户ID与进程组ID

独享的资源
1、栈
2、程序计数器

协程

  协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
  协程在子程序内部是可中断的,然后转而执行别的子程序,在适当的时候再返回来接着执行。

协程与线程区别

  • 一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU
  • 线程进程都是同步机制,而协程则是异步
  • 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态
  • 协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力

协程是如何更少占用资源的
  协程切换完全在用户空间进行,线程切换涉及用户态和内核态切换,需要在内核空间完成

  • 协程不依赖操作系统和其提供的线程
  • 协程之间的切换完全在用户态执行,在用户态没有时钟中断,系统调用等机制,因此效率高。协程切换只涉及基本的CPU上下文切换(寄存器保存 CPU运行任务所需要的信息),协程切换非常简单,就是把当前协程的 CPU 寄存器状态保存起来,然后将需要切换进来的协程的 CPU 寄存器状态加载的 CPU 寄存器上
  • 系统内核调度的对象是线程,线程的调度只有拥有最高权限的内核空间才可以完成,所以线程的切换涉及到用户空间和内核空间的切换,现代操作系统一般都采用抢占式调度,上下文切换一般发生在时钟中断和系统调用返回前,调度器计算当前线程的时间片,如果需要切换就从运行队列中选出一个目标线程,保存当前线程的环境,并且恢复目标线程的运行环境。

协程占用内存少

  • 线程除了和协程相同基本的 CPU 上下文,还有线程私有的栈和寄存器等,上下文比协程多一些

协程的特点在于是一个线程执行,那和多线程比,协程有何优势?

  • 极高的执行效率:因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显
  • 不需要多线程的锁机制:因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多

补充:
进程的常用方法:

1、join():阻塞主进程,主进程会等待子进程结束后结束主进程
2、os.getpid():获取子进程的ID
3、os.getppid():获取子进程的父进程的ID
4、current_process().name:查看当前进程的名字
5.is_alive:查看进程是否还活着
6.terminate(): 直接终止子进程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值