这几天读完了UNP v2,对进程间通信与同步的方式有所了解,现对主要的知识点总结如下:
根据出现的历史,先有的管道,FIFO,信号,然后是systemV IPC,再是后来的Poxis IPC,systemV IPC是内核持续性的,而Poxis根据实现不同有的是内核有的是文件系统持续性。
管道:分为管道和FIFO, 管道一般用于父子进程,不能跨进程传输,FIFO可以在文件系统上建立对象,属于一种文件类型(用p标识),可跨进程通信。他们的内容是进程持久性的,也就是说当进程结束时,管道中的内容都丢失了。管道一般都是单工的,只能单向通信,管道通信的前提是管道的两端都被打开,不像消息队列等打开一端就可以发送消息。管道传输的也是字节流,必要时需要制定应用层传输协议来防治粘包。
消息队列:可以看做是一个消息链表,在某个进程往一个队列中写入消息之前,并不需要另外某个进程在该队列上等待消息的到达。具有内核持续性,也就说一个进程写入消息后可以中止,可让另一个进程在以后某个时候读出该消息。存放的是有边界的消息记录,每个消息含有优先级,POXIS消息队列可以进行notify,在消息到达后通过信号或线程进行消息通知。SystemV消息队列可以指定优先级,优先级高的被优先投递,并支持接收指定优先级的消息。在编写一对多模型的时候不同于管道需要创建多个连接,消息队列只需要创建一个连接,各个进程都可以从其中标识地取出属于自己的消息。缺点就是不能结合select/poll使用,必要时需要用管道转接。新编写的应用程序应该首先考虑使用POXIS消息队列。
互斥锁与条件变量: 可以实现生产者与消费者模型,可以动态分配也可以静态分配,当动态分配并指定共享时可以用在进程间同步,存在的问题是当拥有互斥锁的进程或线程异常终止时可能导致临界区数据不一致。当某个进程阻塞在一个条件变量上的时候,如果调用pthread_exit/pthread_cancel取消线程时,会使该线程再次获得该条件变量的互斥锁,并且线程退出时并不会释放该锁,这样就会造成死锁,解决的办法是使用pthread_cleanup_push/pthread_cleanup_pop来实现清理处理程序的安装和删除。
读写锁: 可以提高并发度,根据实现的不同,可以分为读优先,写优先。读写锁默认也是线程间的锁,如果需要在进程间锁也需要指定PTHREAD_PROCESS_SHARED 属性。
记录锁: 可以提供细粒度的锁,并且程序终止时会完成已有锁的清理工作。由内核维护记录锁,分为建议性锁和强制性锁,强制性锁可以阻止非协作进程读一个已被锁住的文件,但这并不能保证数据的一致性。
信号量: 分为有名信号量,基于内存的信号量,systemV信号量,Poxis有名信号量通过一个文件名打开,标识一个内核中文件系统的结构,所以是随内核持久的,可以在进程间或线程间使用,而基于内存的信号量则具有随进程的持续性。
相对于互斥锁有三点不同:
1. 互斥锁必须由给他上锁的线程解锁,信号量没有这种限制;
2. 信号量有一个与之关联的值,它由挂出操作加一,由等待操作减一,那么任何线程都可以挂出一个信号,即使当时没有线程在等待该信号量的值为正数也没关系,但是如果某个线程调用pthread_cond_signal,但是没有任何线程阻塞在pthread_cond_wait调用中,那么相应条件变量的信号将丢失。
3. 各种同步技巧中能够从信号处理程序中安全调用的唯一函数是sem_post函数。互斥锁是为上锁而优化的,条件变量是为等待而优化的,信号量即可用于上锁又可用于等待,因而可能导致更多的开销和更高的复杂性。
SystemV信号量则是计数信号量集,他维护一组信号量,他允许增长或减少信号量的值不只是1,所以相对posix信号量较为复杂。
共享内存区:共享内存区是IPC形式中最快的,一旦这样的内存区映射到共享他的进程的地址空间,这些进程间数据的传递就不需要涉及内核(指不需要通过执行任何进入内核的系统调用),然而一般需要某种形式的同步。而管道,FIFO,消息队列都需要经由内核传递,所以数据需要四次复制。
所有的共享方式都各有利弊吧,例如基于内核的有的需要内存拷贝,共享内存快却需要加锁来保障数据的一致性,所以我们应该按需使用。此外还有信号和socket,由于涉及内容太多,此处不再赘述。