现代操作系统的核心任务之一就是实现进线程的并发,因此必须采用一定的方法来消除进线程间的“竞态条件”(racecondition),
通过长时间的研究,人们逐步的提出了一系列的同步原语,利用它们可实现这一艰难的任务。
事实上,没有硬件和操作系统的支持,单纯使用应用程序来实现进程间的互斥与共享是非常困难的。最早提出解决方案的是荷兰
数学家T.Dekker,而后又有一系列的算法出现,其中最经典的是Peterson的算法:
Peterson's solution for achievingmutual exclusion.
#define FALSE 0
#define TRUE 1
#define N 2 /* number ofprocesses */
int turn; /* whose turn isit? */
int interested[N]; /* all values initially 0(FALSE)*/
void enter_region(int process) /* process is 0 or 1 */
{
int other; /*number of the other process */
other = 1 – process; /*the opposite of process */
interested[process] = TRUE; /*show that you are interested */
turn = process; /*set flag */
while (turn == process && interested[other] == TRUE) /* null statement */;
}
void leave_region(int process) /* process: who is leaving */
{
interested[process] = FALSE; /* indicate departure from critical region */
}
怎么样,尽管这基本上已经是最简单的算法了,但理解起来还是需要费些功夫吧。
但是,如果有硬件的帮助,这一问题很容易解决。比如,下列的算法使用了TSL指令。
【注】:TSL即Test and Set 指令,很多计算机系统都提供了类似的指令。它的用法如下:
TSL RX, LOCK
RX将被赋为LOCK之前的值,而如果LOCK此时为0,则被置1。
enter_region:
TSL REGISTER, LOCK |copy LOCK to register and set LOCK to 1
CMP REGISTER, #0 |was LOCK zero?
JNE ENTER_REGION |if it was non zero, LOCK was set, so loop
RET |return to caller;criticalregion entereleave_region:
MOVELOCK,#0 |store a 0 in LOCK
RET |return to caller
怎么样,简单多了吧?
下面,我们来看一下操作系统或运行时环境经常会提供的一些进程同步原语。
1 Semaphore(信号灯)
Semaphore是由伟大的荷兰数学家,GOTO语句的掘墓人Dijkstra于1965年提出的。简单说来,它由一个SharedCounter
和加诸其上的P(Down)/V(Up)操作构成的。
P操作:
如果SharedCounter>0,则Decreaseit;
否则,block()
V操作:
SharedCounter ++
如果wait的队列不为空,则唤醒其中一个进程。
相比较于上面的TSL指令,信号灯显得更高层一些,它将对SharedCounter的操作和Sleep/wakeup绑定执行,使用起来更加方
便。而SharedCounter用来表示资源的数量(当SharedCounter初始化为1时,Semaphore就退化为了Mutex),可以完成比互斥
操作更加复杂的要求。事实上Semaphore常被来实现producer-consumer问题,如下所示:
#define N 100 /*number of slots in thebuffer */
typedef int semaphore; /*semaphores are a specialkind of int */
semaphore mutex = 1; /*controls access to criticalregion */
semaphore empty = N; /*counts empty buffer slots */
semaphore full = 0; /*counts full buffer slots*/
void producer(void)
{
int item;
while (TRUE){ /* TRUE is theconstant 1 */
item = produce_item(); /* generate something toput in buffer */
down(&empty); /* decrement emptycount */
down(&mutex); /* enter criticalregion */
insert_item(item); /* put new item inbuffer */
up(&mutex); /* leavecritical region */
up(&full); /* incrementcount of full slots */
}
}
void consumer(void)
{
int item;
while (TRUE){ /* infinite loop */
down(&full); /* decrementfull count */
down(&mutex); /* enter criticalregion */
item = remove_item(); /* take item frombuffer */
up(&mutex); /* leavecritical region */
up(&empty); /* incrementcount of empty slots
consume_item(item);
}
}
请注意,在上述代码中除信号灯外还使用了一个Mutex,这是因为:
(1)Semaphore表示的是资源数量,用于生产者和消费者进程的同步;
(2)而“产品队列”作为一种共享资源,对其的写操作也需要使用mutex进行互斥。
2. Conditional Variables(CV)
Semaphore在使用过程中还是有些不便,比如,在上述的代码中,如果错误的使Semaphore在拥有Mutex(Lock)
的情况下睡眠,则会引起进程死锁。
而CV使用起来就安全很多,CV可执行三种操作:
(1)wait ---- block直到其他进程notify;
(2)signal(notify)-----notify a waiting进程;
(3)borodcast(notifyall)-----notify all waiting 进程。
CV与某个Lock绑定在一起,进程要在CV上等待时,首先必须要拥有这个Lock,而wait操作会使进程释放该Lock。
而进程被唤醒时,会首先试图重新获取Lock,如失败则会继续block,CV的示例代码如下:
pthread_mutex_t mylock;
pthread-cond_t myCV;
int counter = 0;
/* ------------thread A--------------* /
pthread_mutex_lock(&myLock);
while (counter < 10) {
pthread_cond_wait(&myCV,&myLock);
}
pthread_mutex_unlock(&myLock);
/*-------------- thread B--------------*/
pthread_mutex_lock(&myLock);
counter++;
while (counter >= 10) {
pthread_cond_signal(&myCV);
}
pthread_mutex_unlock(&myLock);
3 monitor(管程)
monitor是更高层的同步工具,它由一个object(内部隐藏了资源)和其中若干procedure(用于访问资源)组成。任何时刻
仅有一个活动进程可以执行monitor内的procedure。这样,可以方便的实现资源的互斥访问。
monitor通常要借助CV来实现,要执行procedure则必须拥有该object的Lock,否则procedure就会wait在CV上;而active的procedure
结束后,会唤醒等待在该CV上的进程。
一般来说,monitor需要编程语言的支持,而很多现代的编程语言也确实支持了这一特性。比如JAVA,其synchronize关键字就是
用来支持Monitor的。Monitor乍听起来跟现代的OO语言的“封装”概念很类似,但其目的是为了完成进程同步。事实上,monitor的
概念最早在1973年就被提出了。
对同步原语的讨论以接近尾声,但需要强调的是,进程同步原语只是操作系统(运行时环境)提供的用于进程同步的工具,我们可以
利用他们简化我们的实现;但绝不是说,只要使用了他们就一定能写出正确的多进(线)程程序。这一点经常被人们误解,比如,JAVA多线程
新手往往以为只要使用了synchronize关键字就万事大吉了,事情原非如此,编写正确的多进(线)程程序远比此复杂的多。
参考文献:
AndrewS. Tanenbaum ,Operating Systems Design and Implementation(Third Edition);
MattWelsh, Harvard university ,CS61:System programming and Machine organization(fall 2009) Lecture 20 “Semaphores Conditional Variables and Monitor”