进程的同步与互斥
顺序程序与并发程序特征
顺序程序 |
并发程序 |
顺序性 |
共享性 |
封闭性:(运行环境的封闭性) |
并发性 |
确定性 |
随机性 |
可再现性 |
进程互斥
由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥.
系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。
在进程中涉及到互斥资源的程序段叫临界区.
互斥示例
说明:如果此时票数X=1,A进程与B进程同时抢到临界资源X,都执行X--,则会出现X<0的情况,这种情况肯定是不合理的,票数不可能小于0;
进程同步
进程同步指的是多个进程需要相互配合共同完成一项任务。
同步示例
说明:只有司机P1与售票员P2相互协作,才能完成,司机开车与售票员售票的过程
死锁的产生与解除
死锁是指多个进程之间相互等待对方的资源,而在得到对方资源之前又不释放自己的资源,这样,造成循环等待的一种现象。如果所有进程都在等待一个不可能发生的事,则进程就死锁了。
死锁产生的必要条件
1)互斥条件
进程对资源进行排它性的使用,即在一段时间内某资源仅为一个进程所占用。
2)请求和保持条件
当进程因请求资源而阻塞时,对已获得的资源保持不放。
3)不可剥夺条件
进程已获得的资源在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件
各个进程组成封闭的环形链,每个进程都等待下一个进程所占用的资源
防止死锁办法
资源一次性分配(破坏请求和保持条件)
可剥夺资源(破坏不可剥夺条件)
资源有序分配法(破坏循环等待条件)
死锁避免
预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得较满意的系统性能。
由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
信号量
信号量和P、V原语由Dijkstra(迪杰斯特拉)提出
互斥:P、V在同一个进程中
同步:P、V在不同进程中
信号量值含义
S>0:S表示可用资源的个数
S=0:表示无可用资源,无等待进程
S<0:|S|表示等待队列中进程个数
PV操作
struct semaphore { int value; pointer_PCB queue; } //P原语 P(s) { --s.value; if (s.value < 0) //表示没有空闲资源 { 将当前进程设置为阻塞状态; 将当前进程的PCB插入相应的阻塞队列s.queue末尾; } } //V原语 V(s) { ++s.value; if (s.value <= 0) //表示有进程处于阻塞状态 { 唤醒阻塞队列s.queue中等待的一个进程,将其置为就绪态; 将其插入就绪队列; } }
信号量API
Linux为信号量维护数据结构
struct semid_ds { struct ipc_perm sem_perm; /* Ownership and permissions */ time_t sem_otime; /* Last semop time */ time_t sem_ctime; /* Last change time */ unsigned long sem_nsems; /* No. of semaphores in set */ };
信号量集函数
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(key_t key, int nsems, int semflg); int semctl(int semid, int semnum, int cmd, ...); int semop(int semid, struct sembuf *sops, unsigned nsops);
semget函数
功能:用来创建和访问一个信号量集
原型
int semget(key_t key, int nsems, int semflg);
参数
key: 信号集键(key)
nsems:信号集中信号量的个数
semflg: 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志一致
返回值:
成功返回一个非负整数,即该信号集的标识码;失败返回-1
//实践 int main() { int semid = semget(0x12345670,1,0666|IPC_CREAT); if (semid == -1) { if (errno == EEXIST) { err_exit("EEXIST"); } else { err_exit("semget error"); } } else { cout << "semget OK" << endl; } return 0; }
shmctl函数
功能:用于控制信号量集
原型
int semctl(int semid, int semnum, int cmd, ...);
参数
semid:由semget返回的信号集标识码
semnum:信号集中信号量的序号
cmd:将要采取的动作(取值如下)
最后一个参数根据命令不同而不同
返回值:
成功返回0;失败返回-1
Man-page semctl() performs the control operation specified by cmd on the System V semaphore set identified by semid, or on the semnum-th semaphore of that set. (The semaphores in a set are numbered starting at 0.) This function has three or four arguments, depending on cmd. When there are four, the fourth has the type union semun. The calling program must define this union as follows: union semun { int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */ };
//实践:SETVAL union mySemUn { int val; // Value for SETVAL// struct semid_ds *buf; // Buffer for IPC_STAT, IPC_SET// unsigned short *array; // Array for GETALL, SETALL// struct seminfo *__buf; // Buffer for IPC_INFO (Linux-specific)// }; int main() { int semid = semget(0x12345670,1,0666|IPC_CREAT); if (semid == -1) { err_exit("semget error"); } union mySemUn setValue; setValue.val = 15764; if (semctl(semid,0,SETVAL,setValue) != 0) { err_exit("semctl SETVAL error"); } int returnValue = semctl(semid,0,GETVAL,0); cout << "returnValue = " << returnValue << endl; return 0; }
semop函数
功能:用来操纵一个信号量集
原型
int semop(int semid, struct sembuf *sops, unsigned nsops);
参数
semid:是该信号量的标识码,也就是semget函数的返回值
sops:是个指向一个结构数组的指针
nsops:信号量的个数
返回值:
成功返回0;失败返回-1
//Man-page示例代码 struct sembuf sops[2]; int semid; /* Code to set semid omitted */ sops[0].sem_num = 0; /* Operate on semaphore 0 */ sops[0].sem_op = 0; /* Wait for value to equal 0 */ sops[0].sem_flg = 0; sops[1].sem_num = 0; /* Operate on semaphore 0 */ sops[1].sem_op = 1; /* Increment value by one */ sops[1].sem_flg = 0; if (semop(semid, sops, 2) == -1) { perror("semop"); exit(EXIT_FAILURE); }
sembuf结构体参考如下:
struct sembuf { unsigned short sem_num; /* semaphore number */ short sem_op; /* semaphore operation */ short sem_flg; /* operation flags */ };
sem_num是信号量的编号(从0开始)。
sem_op是信号量一次PV操作时加减的数值,一般只会用到两个值,一个是“-1”,也就是P操作,等待信号量变得可用;另一个是“+1”,也就是V操作,发出信号量已经变得可用;
sem_flag的两个取值是IPC_NOWAIT或SEM_UNDO