(区别于linux内核所用的自旋锁和互斥锁,本文中讨论的锁用于普通编程)
当两个或多个并发线程的执行次序造成了意想不到的错误结果时,“竞态条件”就是会产生。防止“竞态条件”的一个方法是使用同步机制,对访问“共享资源”的代码中关键段实施“品行访问”控制。常用的OS同步机制有:互斥体(mutex)、“多读取者/单写入者”锁(reader/writer locks)、信号量(semaphores)和条件变量(condition variable)。
1.互斥体(mutex,Mutual Exclusion)锁
当共享资源被多个线程并发访问时,为了确保这些资源的完整性,我们可以使用互斥体(mutex)锁。互斥体可用来串行执行多个线程,这需要在代码中确定关键 (critical section)——即,一次只能由一个线程执行的代码。“互斥体”语义像双括号那样具有“对称体”:也就是就说,如果一个线程拥有互斥体,那么,它还和负责释放这个互斥体。这种简单的语义有助于互斥体的高效实现——如通过硬件自旋锁(spinlock)来实现。
常见的互斥体有两种:
非递归互斥体(nonrecursive mutex):如果当前拥有互斥体的线程在没有首先释放它的情况,试图再次获得它,就会导致死锁或失败。
递归互斥体(recursive mutex):拥有互斥体的线程可能多次获得它而不会产生自死锁,只要这个线程最终以相同次数释放这个互斥体即可。
2.Readers/Writer锁
Readers/Writer(多读取者/单写入者)锁可以通过以下方式之一访问共享资源:
多个线程并发读取资源,但不修改它;
一次只一个线程修改资源,此时其它线程都不能对其进行读/写访问。
Readers/Writer锁可以用来保护“读操作比写操作频繁”的资源,从而提高并发应用程序的性能。在实现Readers/Writer锁时,要么给“读取者”以优先权,要么给“写入者”以优先权。
Readers/Writer 锁和互斥体有某些共性,例如:获得锁的线程也必须释放这个锁,如查一个“写入者”希望获得这个锁,那么,这个线程必须等待其它所有“拥有这个锁”的线程释放它;然后,这个“写入者”线程单独占有这个锁。便但和互斥体不同的是,多个线程可以同时获得一个Readers/Writer锁执行“读”操作。
3.信号量锁
从概念上说,信号量(semaphore)是可以原子(automically)递增和背叛的非负整数。如果一个线程试图递减一个信号量,但这个信号量的值已经为0,则线程将会阻塞。另一个线程“发出(post)”这个信号(semaphore),使用信号量大于0之后,被阻塞的线程才会被释放。
信号量维护状态信息,对信号计数值(count)和被阻塞线程的数量进行记录。它们一般是通过“休止锁(sleep lock)”来实现的;休止锁用来触发环境切换,以允许其他线程执行。和互斥体不同的是,释放信号量的线程不必是最初获得这个信号量的线程。这使得信号量适用于更广泛的执行环境,如信号处理程序或中断处理程序。
4.条件变量
和互斥体、Readers/Writer锁、信号量锁不同,条件变量(condtion variable)提供了不同风格的同步方式。在前三种机制中,当“占有锁的线程”在关键段中执行代码时,其他线程会等待。下此相反,使用条件变量,线程可以调整和调度自己的处理过程。
例如,某一数据被其他线程共享;条件变量可能使用处于等待状态,直至“涉及这个数据”的一个条件表达式达到某一状态。当一个“合作线程(cooperation thread)”显示共享数据的状态已经改变时,阻塞在条件上的线程会被唤醒;然后,被唤醒的线程重新计算它的条件表达式,如果共享数据达到预期状态,则恢复处理。再复杂的条件表达式也可以通过条件变量来等待;所以,较之前面所说的同步机制,条件变量允许更复杂的调度决策。