本文来梳理下进程间通信的必备知识。
基础概念
并发进程之间的交互必须满足两个基本要求:同步和通信。
并发进程之间的两种关系:竞争与协作。(用互斥解决竞争关系,用同步解决协作关系)
进程同步:指两个以上进程基于某个条件来协调它们之间的活动。(说的简单点,就是为了共同的任务,需要进程排着队,一个个来处理)
进程互斥:指若干个进程要使用同一个共享资源时,在任何时刻只允许一个进程使用,其它的都得等待。
互斥是进程同步的特例。
进程间通信的方式:
- 信号通信机制:signal
- 信号量与P/V操作
- 共享内存通信机制
- 消息队列传递通信机制
- socket 单机/网络通信机制
信号
别名软中断,通常用来处理可以延时的任务,比如tcp/ip操作,SCSI协议操作等。
信号量
组成元素:一个变量、一个进程等待队列。
信号量加上P/V操作可用来解决进程间的同步与互斥问题,不过仅限传递信号,并没有传递数据的能力。
两大种类:system v版本的信号量只能用于进程,而POSIX版的可同时用于进程与线程。
参考 https://blog.youkuaiyun.com/lh2016rocky/article/details/70800958
管道pipe
我们所提的管道主要指无名管道,还有一种叫做named pipe
它有几大特点:
- 只能用于亲缘关系的进程间通信(父子or兄弟关系)
- 半双工通信,有固定的读端和写端
- 可被看成一种特殊的文件(实际为内核的一个缓冲区),用read和wirte函数读写。
int pipe(int pipefd[2]);
用pipe创建管道时会有两个文件描述符,其中 pipefd[0] 固定用来读,pipefd[1] 用来写。
创建管道流程:
管道几个注意点:
- 只有在管道的读端存在时,向管道写入数据才有意义。否则,向管道写入数据的进程将收到内核传来的SIGPIPE 信号(通常为Broken pipe 错误)。
- 向管道写入数据时,Linux 将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读取管道缓冲区中的数据,那么写操作将会一直阻塞。
命名管道特点:
- 可以使互不相关的两个进程实现彼此通信
代码参考 https://github.com/allen605/os_study/tree/master/pipe_folder
消息
socket
POSIX标准所规定的 socket api是基于BSD版的socket api。(BSD是一个unix系统的分支,由伯克利分校开发与维护)
扩展:线程的互斥与同步
linux系统遵循POSIX标准,所以本文只考虑使用POSIX的api。
因为线程之间可以共享进程的数据(不像IPC那么复杂),所以只需关注同步问题即可。
互斥量:是一个用来保证线程在关键区正常执行的变量。
pthread_mutex_t pthread_mutex_lock(pthread_mutex_t *mutex);
检查互斥量mutex是否上锁,如果已经上锁,则会进入阻塞态。
条件变量:是一个特殊的线程结构体变量,它可以允许一个线程等待某一个事件,另一个线程在事件发生时向它发送信号。
//等待某一事件
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
//发出信号
int pthread_cond_signal(pthread_cond_t *cond);
条件变量需要和互斥量配合使用。当调用wait时,会进入阻塞态并自动解锁,直到收到信号后进入运行态并自动上锁,继续往下运行。
代码参考 pthread_cond_mutex_os.c https://github.com/allen605/os_study
POSIX信号量:
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem); //not blocked
int sem_post(sem_t *sem); //increments (unlocks) the semaphore.
int sem_getvalue(sem_t *sem, int *sval);
其和传统的P/V操作类似,sem_wait() 即P操作,用来申请共享资源;sem_post即V操作,用来释放资源。
信号量参考 https://blog.youkuaiyun.com/lh2016rocky/article/details/70800958
<ing>