前言
在多进程系统中,诸进程可以并发执行,并且以各自独立得速度向前推进,但是我们知道在操作系统当中供进程使用得资源是十分有限的,就算不有限如果遇到了各进程都需要使用的空间资源,那么这个资源该分给谁呢?该如何使用呢?
这就说明进程之间是存在竞争系统资源和进程协作的关系来维护这种状态。进程之间的协作关系是由于进程之间存在共享数据,为了保存数据的完整性,需要正确处理进程协作的问题,这就引入到我们的进程同步问题,同步就分为:进程互斥、进程同步、进程间通信。为了理解进程共享与线程的互斥概念,我们必须得先了解临界资源和临界区的概念。
一、临界资源和临界区
1.1临界资源
虽然多个进程可以共享系统中的各种资源,但其中许多资源一次只能为一个进程所使用,并且一个进程在使用的同时我们另外一个进程不能进行访问,我们把一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如打印机等。此外,如果变量、数据等都可以被若干进程共享,也属于临界资源。
1.2临界区
一组进程对临界资源的访问,这一组的进程的每一个进程都包含一个对于该临界资源的程序段,从逻辑上分离开来就是(访问临界资源的那段代码)临界区。我们可以进入访问临界资源的代码分为4个区:进入区:为了进入临界区使用临界资源,在进入区要检查可否进入临界区,如果可以进入临界区,则应设置正在访问临界区的标志,以阻止其他进程同时进入临界区。临界区:进程中访问临界资源的那段代码,又称临界段。退出区:将正在访问临界区的标志清除。剩余区:代码中的其余部分。
二、信号量实现进程同步
其实在实现对于临界资源的访问控制机制中操作系统有提供两种同步机构,锁和信号量,分别应用于线程和进程当中,两者都是一个物理实体,采用一个标识来代表某种资源的状态在,在这种标志下实现对于临界资源正确访问控制。
那么信号量是如何实现访问控制的呢?我们下面来讲解P,V操作。
2.1P、V操作讲解
我们这里主要使用的函数API
int semop(int semid, struct sembuf *sops, unsigned nsops);
//参数一:就是我们创建信号量的标识ID;
//参数二:就是一个结构,里面包含我所需要的信息;
//参数三:信号量的个数;
结构体如何使用?又有哪些信息呢?
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;
这个man手册上的一个demo,因为我们这里只有一个信号量就不需要定义为一个数组,只要声明一个结构体变量即可。
// P操作函数部分,需要封装为一个函数
void PGET(int semID)
{
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = -1; //拿锁要减一
sops.sem_flg = SEM_UNDO; //需要等待,如果用SEM_NOWAIT没有意义
semop(semID,&sops,1);
}
// V操作函数部分,需要封装为一个函数
void Vpush_back(int semID)
{
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = 1; //改变即可
sops.sem_flg = SEM_UNDO;
semop(semID,&sops,1);
}
我们的P,V操作差不多就这么多,完整的多进程访问临界资源代码见:进程间通信.
三、线程同步讲解
我们再讨论进程的时候,需要同步和互斥,线程也一样需要,我们知道线程对于资源的消耗会更加地少,所以再实际的编程当中我们应用多线程的方式比较广泛,但是线程是相互共享内存的,这也就需要我们对于临界资源进行访问控制,对线程也加上一些标志来达到访问控制的目的。
本文你篇幅较长,线程锁详见:链接: 线程的同步讲解.