信号量的一些内容
信号量S是个整数变量,除了初始化以外,只能通过两个标准原子操作:wait()(P操作)和signal()(V操作)来访问。
wait()的定义:
1 wait(s) { 2 while (S<=0) 3 ; //no-operation 4 S--; 5 }
signal()的定义:
1 signal(S) { 2 S++; 3 }
因为这两个是原子操作,因此对信号量整型值的修改必须不可分地执行。即当一个进程修改信号量时,不能有其他进程同时修改同一信号量的值。
1. 信号量用法
信号量通常分为计数信号量和二进制信号量。计数信号量的值域不受限制,为整数;二进制信号
| 信号量 | 别名 | 值域 |
| 计数信号量 | 所有整数 | |
| 二进制信号量 | 互斥锁(mutex) | 0和1 |
可以使用二进制信号量处理多进程的临界问题。设n个进程共享一个信号量mutex,并初始化为1。每个进程的结构:
do { wait(mutex); // 临界区(critical section) signal(mutex); // 剩余区(remainder section) }while(TRUE);
计数信号量可以用来控制访问具有若干个实例的某种资源。该信号量初始化为可用资源的数量。当每个进程需要使用资源时,需要对该信号量执行wait()(P操作)操作,减少信号量的计数。当进程释放资源时,需要对该信号量执行signal()操作,增加信号量的计数。信号量的计数为0时意味着所有资源都被使用,之后所有需要使用资源的进程将会被阻塞,知道计数大于0。
也可以使用信号量解决同步问题。例如,有两个并发进程P1有语句S1,P2有语句S2,假设要求在S1执行完后才执行S2,令P1和P2共享一个共同信号量synch,将其初始化为0,
P1中插入语句:
S1;
signal(synch);
P2中插入语句:
wait(synch);
S2;
2. 实现
以上所定义的信号量也称为自旋锁。自旋锁通常用于多处理器系统中,这样一个线程在一个处理器自旋时,另一个线程可以在另一个处理器上在其临界区执行。
- 其优点是,进程在等待锁时不进行上下文切换(上下文切换可能需要花费相当长的时间),如果锁的占用时间短,那么自旋锁就有用了;
- 其缺点是,忙等待(busy waiting)。当一个进程位于其临界区内时,任何其他试图进入临界区的进程都必须在其进入代码中连续地循环。这种连续循环在实际多道程序系统中是个问题,因为这里只有一个处理器为多个进程所共享。忙等待浪费了CPU时钟,而这本可以有效地为其他进程所使用。
为了克服忙等,可以修改信号量操作wait()和signal()的定义。当一个进程执行wait()操作时, 发现信号量不为正(+),则它必须等待。然而,该进程不是忙等而是阻塞自己,阻塞操作将一个进程放入到与信号量相关的等待队列中,并将该进程的状态切换成等待状态。接着,控制转到CPU调度程序,以选择另一个进程来执行。
一个被阻塞在等待信号量S上的进程, 可以在其他进程执行signal()操作之后被重新执行。该进程的重新执行使通过wakeup()操作来进行的,该操作将进程从等待状态切换到就绪状态。接着,该进程被放入到就待续队列中,CPU调度算法不同,CPU有可能会也可能不会从正在运行的进程切换到刚刚就绪的进程。
为了实现此种定义的信号量,信号量的定义如下:
typedef strucet { int value; struct process *list; } semaphore;
每个信号量都有一个整型值和一个进程链表。 当一个进程必须等待信号量时,就加入到进程链表上。 操作signal() 慧聪等待进程链表中取一个进程以唤醒。
信号量操作wait()如下定义:
wait(semaphore *S) { S->value--; if (S-> value < 0) { add this process to S->list; block(); } }
信号量操作signal如下定义:
signal(semaphore *S) { S->value++; if (S->value <= 0) { remove a process P from S->list; wakeup(P); } }
操作block()挂起调用它的进程。操作wakeup(P)重新启动阻塞进程P的执行。这两个操作都是由操作系统作为基本系统调用来提供的。
注意:在具有忙等的信号量的经典定义下, 信号量的值不可能为负。
浙公网安备 33010602011771号