如有可能可使用非阻塞锁 (PDF 191KB)
摘要
通过执行由辅助线程实施提供的同步基元,线程可在共享资源上实现同步。 这些基元例如互斥体 (mutex) 和旗语 (semaphore) 等只允许单条线程持有锁,其它线程则依据自身的超时机制自旋或阻塞。 阻塞线程将导致成本昂贵的环境切换操作,而旋转等待则会浪费 CPU 执行资源(除非等待时间非常短)。 另一方面,非阻塞系统调用则允许未成功获得锁的竞争线程原路返回,继续执行有意义的工作,进而避免浪费执行资源。
本文是“英特尔多线程应用开发指南”系列的一部分,该系列介绍了针对英特尔® 平台开发高效多线程应用的指导原则。
背景
大多数线程实施,包括 Windows* 和 POSIX* 线程 API,均可提供阻塞和非阻塞两种线程同步基元。 默认情况下通常使用阻塞基元。 成功争得锁之后,线程便获得锁的控制权,进入关键代码段执行代码。 但是,如果没有争得锁,系统便会执行环境切换,线程将被置于等待队列中。 环境切换的成本非常高,需尽量避免,具体原因如下:
- 环境切换开销特别高,基于内核线程的线程实施尤为如此。
- 应用中跟随同步调用之后的有用工作必须等线程获得锁后才能够执行。
使用非阻塞系统调用有助减少性能损失。 在这种情况下,应用线程如果没能成功锁定关键代码段,便会继续执行代码。 这不但可以消除环境切换开销,同时也可避免线程在等待获得锁定权的过程中自旋。 事实上,线程在重新尝试争夺锁定权之前会一直执行有用工作。
建议
使用非阻塞线程调用来避免生成环境切换开销。 非阻塞同步调用通常以关键字 try开始。 例如,Windows 线程实施提供的阻塞和非阻塞版本关键代码段同步基元如下所示:
如果线程在争夺锁的过程中成功获得关键代码段的所有权, TryEnterCriticalSection调用将返回“ True”Boolean 值。 否则,它将返回“False”,线程便可以继续执行应用代码。
void EnterCriticalSection (LPCRITICAL_SECTION cs);
bool TryEnterCriticalSection (LPCRITICAL_SECTION cs);
非阻塞系统调用的典型使用示例如下:
CRITICAL_SECTION cs; void threadfoo() { while(TryEnterCriticalSection(&cs) == FALSE) { // some useful work } // Critical Section of Code LeaveCriticalSection (&cs); } // other work }
同样地,POSIX 线程提供非阻塞版本的互斥体 (mutex)、旗语(semaphore) 和条件变量同步基元。 阻塞和非阻塞版本的互斥体同步基元如下所示:
int pthread_mutex_lock (pthread_mutex_t *mutex);
int pthread_mutex_try_lock (pthread_mutex_t *mutex);
在 Windows* 线程实施中,还可以为线程锁定基元设定超时时间。 Win32* API 提供了WaitForSingleObject和 WaitForMultipleObjects系统调用,用于在内核对象上实现同步。 执行这些调用的线程将一直等待直至相应的内核对象可用,或者用户指定的时间间隔已过。 一旦超时间隔已过,线程便可继续执行有用工作。
DWORD WaitForSingleObject (HANDLE hHandle, DWORD dwMilliseconds);
在上面的代码中,hHandle 是内核对象的句柄 ( hHandle); dwMilliseconds是超时间隔,如果该间隔过后内核对象仍不可用函数便会自动返回。 “INFINITE”值表示线程将无限期地等待下去。 下方列出了使用该 API 调用的代码片断。
void threadfoo () { DWORD ret_value; HANDLE hHandle; // Some work ret_value = WaitForSingleObject (hHandle,0); if (ret_value == WAIT_TIME_OUT) { // Thread could not gain ownership of the kernel // object within the time interval; // Some useful work } else if (ret_value == WAIT_OBJECT_0) { // Critical Section of Code } else { // Handle Wait Failure} // Some work }
同样地, WaitForMultipleObjectsAPI 调用允许线程等待多个内核对象进入可用状态。
使用非阻塞系统调用如 TryEnterCriticalSection时,在释放共享对象前应查看同步调用的返回值,确保请求已得到满足。
使用指南
Aaron Cohen 和 Mike Woodring。《Win 32 多线程编程》, O'Reilly Media;第 1 版, 1997 年。
Jim Beveridge 和 Robert Wiener,Win32 多线程应用 — 完整线程指南, Addison-Wesley Professional, 1996 年。
Bil Lewis 和 Daniel J Berg,基于 Pthreads 的多线程编程, Prentice Hall PTR;第 136 版, 1997 年。