进程同步
进程同时处理同一串数据, 会造成不确定性,比如有多个进程同时对一个文件进行读写,那么读文件的进程无法确定自己读到的数据是否是它本来想要的数据,还是被修改的数据,除此以外,当先读后写时,由于缓冲区没有写入数据,读进程无数据可读,就会因此被阻塞(使用管道通信)。
这种两个或多个进程读写某些共享数据,而最后的结果取决于进程运行的精准时序,称为数据竞争,而这种多个程序可以并发执行,但是由于系统资源有限,程序的执行不是一贯到底的,以不可预知的速度向前推进,这又被称为异步性。
这种受访问顺序影响的数据是没有意义的(程序的运行不能有二义性),所以为了能够使得进程有能够有一定的顺序来访问数据,从而引入了同步的概念。
所谓进程同步就是指协调这些完成某个共同任务的并发线程,在某些位置上指定线程的先后执行次序、传递信号或消息
本文将主要讲解如何使用信号量实现进程同步。
信号量基本概念
信号量相当于一个信号灯,在程序实现中往往是一个非负整数。在实际生活中,如火车进站前会看到的信号灯,若灯亮说明火车可以进站,否则不能进站,这里的信号灯就可以看作是信号量,火车看作是进程,能否进站即能否访问资源。
在进程进入一个关键代码段之前,进程必须获取一个信号量;一旦该关键代码段执行完毕了,那么该线程必须释放信号量。其它想进入该关键代码段的进程必须等待直到第一个进程释放信号量。
信号量
- 作用:控制多进程共享资源的访问(资源有限并且不共享)
- 本质:任一时刻只能有一个进程访问临界区(代码),数据更新的代码。
PV操作
PV操作即是针对信号量进行的相应操作,PV操作由P操作原语和V操作原语组成(原语是不可中断的过程)。
当进程执行P操作,若信号量大于零(有共享资源),则信号量减一,进程继续执行;若信号量为零,则进程等待。
当进程执行V操作,若信号量大于零(有共享资源),则信号量加一;若信号量为零,则唤醒等待进程。如下图所示:
关于信号量的函数
使用信号量的相关函数时需要添加头文件#include <semapore.h>
,链接库为pthread
。
接下来我们具体学习关于信号量的相关函数,根据信号量是否命名分为命名信号量(基于文件实现)和匿名信号量(基于内存)。
命名信号量相关函数
操作 | 函数定义 |
---|---|
创建 | sem_t *sem_open(const char *name, int oflag, mode_t mode,unsigned int value) |
删除 | int sem_unlink(const char *name) |
打开 | sem_t *sem_open(const char *name, int oflag) |
关闭 | int sem_close(sem_t *sem) |
挂出 | int sem_post(sem_t *sem) |
等待 | int sem_wait(sem_t *sem) |
尝试等待 | int sem_trywait(sem_t *sem) |
获取信号量的值 | int sem_getvalue(sem_t *sem, int *sval) |
匿名信号量相关函数
操作 | 函数 |
---|---|
初始化 | int sem_init (sem_t *sem , int pshared, unsigned int value) |
销毁 | int sem_destroy(sem_t *sem) |
挂出 | int sem_post(sem_t *sem) |
等待 | int sem_wait(sem_t *sem) |
尝试等待 | int sem_trywait(sem_t *sem) |
获取信号量的值 | int sem_getvalue(sem_t *sem, int *sval) |
信号量的使用
命名信号量
我们首先创建两个进程,具体代码如下:
#include <iostream>
#include <unistd.h>
using namespace std;
int main(){
fork();
for(int i=0; i<5; i++){
cout << getpid() << ":before" << endl;
sleep(1)