操作系统知识点梳理(二)
进程/线程间通信方式
继续(一)之后,梳理进程/线程间通信方式主要知识点,参考资料:
- https://mp.weixin.qq.com/s/mblyh6XrLj1bCwL0Evs-Vg
- https://blog.csdn.net/weixin_40418080/article/details/125491961
进程间通信
- 管道
- 管道本质上就是内核中的一个缓存,当进程创建一个管道后,Linux会返回两个文件描述符,一个是写入端的描述符,一个是输出端的描述符,可以通过这两个描述符往管道写入或者读取数据,如果想要实现两个进程通过管道来通信,则需要让创建的管道的进程fork子进程,这样子进程就拥有了父进程的文件描述符,这样子进程之间也就有了对同一管道的操作
- 缺点
- 半双工通信,一条管道只能一个进程写,一个进程读
- 一个进程写完后,另一个进程才能读,反之同理
- 消息队列
- 消息队列是保存在内核中的消息链表,在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型,所以每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。
- 优点:消息队列适合进程间频繁地交换数据,两个进程之间的通信就像平时发邮件一样,你来一封,我回一封,可以频繁沟通
- 缺点:
- 消息队列不适合大数据的传输
- 通信不及时,消息队列通信过程中,存在用户态和内核态之间的数据拷贝开销
- 共享内存
- 共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另一个进程马上就能看到,都不需要拷贝来拷贝去,大大提高了进程间通信的速度
- 缺点:多个进程同时修改一个共享内存,很有可能冲突
- 信号量
- 为了防止多进程竞争共享资源,而造成数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被一个进程访问,信号量实现了这一保护机制
- 信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据
- 控制信号量的方式有两种原子操作
- 一个是P操作:这个操作会把信号量减去-1,相减后如果信号量<0,则表明资源已被占用,进程需阻塞等待;相减后如果信号量>=0,则表明还有资源可使用,进程可正常继续执行
- 一个是V操作:这个操作会把信号量加上1,相加后如果信号量<=0,则表明当前有阻塞中的进程,于是会将改进程唤醒运行;相加后如果信号量>0,则表明当前没有阻塞的进程
- 信号量初始化
- 初始化为1代表着互斥信号量
- 初始化为0代表着同步信号量
- 信号
- 对于异常情况下的工作模式,就需要用【信号】的方式来通知进程
- 信号是进程间通信机制中唯一的异步通信机制,因为可以在任何时候发送信号给某一进程,一旦有信号产生,有以下几种用户进程对信号的处理方式
- 执行默认操作
- 捕捉信号
- 忽略信号
- Socket
- 跨网络与不同主机上的进程之间通信
- 基于TCP协议通信方式
- 基于UDP协议通信方式
- 本地进程间通信方式
线程间通信
- 等待–通知
- 等待–通知机制相关方法:
- notify():通知一个对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程取得了对象的锁
- notifyAll():通知所有等待在该对象上的线程
- wait():调用该方法的线程进程WAITING状态,只有等待另外线程的通知或被中断才会返回,调用wait()方法后,会释放对象的锁
- wait(long timeout):超过等待一段时间,没有通知就超时返回,参数时间是毫秒
- 注意事项
- 等待–通知机制使用的是同一个对象的锁,如果两个进程使用的是不同的对象锁,那它们之间是不能用等待–通知机制通信的
- 使用wait()、notify()和notifyAll()时需要先对调用对象加锁
- 调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列
- notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifyAll()的线程释放锁之后,等待线程才有机会从wait()返回
- notify()方法将等待队列中的一个等待线程从等待队列中移动到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移动到同步队列,被移动的线程状态由WAITING变为BLOCKED
- 从wait()方法返回的前提是获得了调用对象的锁
- wait()方法的核心原理
- 当线程调用了locko(某个同步锁对象)的wait()方法后,JVM会将当前线程加入locko监视器的WaitSet(等待集),等待被其他线程唤醒。
- 当前线程会释放locko对象监视器的Owner权限,让其他线程可以抢夺locko对象的监视器
- 让当前线程等待,其状态变成WAITING
- notify()方法的核心原理
- 当线程调用了locko(某个同步锁对象)的notify()方法后,JVM会唤醒locko监视器WaitSet中的第一条等待线程
- 当线程调用了locko的notifyAll()方法后,JVM会唤醒locko监视器WaitSet中的所有等待线程
- 等待线程被唤醒后,会从监视器的WaitSet移动到EntryList,线程具备了排队抢夺监视器Owner权利的资格,其状态从WAITING变成BOLOKED
- 共享内存
- 同步–synchronized
- 关键字synchronized可以修饰方法或同步块,它主要确保多个线程在同一时刻,只能有一个线程处于方法或同步块中,它保证了线程对变量访问的可见性和排他性
- 信号量–volatile
- 关键字volatile可以用来修饰字段(成员变量),就是告诉程序对该变量的访问需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性
- volatile能够保证内存的可见性,如果在一个线程里改变了这个变量的值(该变量是由volatile关键字修饰的),那么其它线程对这种改变是立马可见的。
- 循环队列:生产者消费者模型
- 管道流
- ReentrantLock/Condition 消息队列方式