《Win32多线程程序设计》之线程同步

来源:互联网 发布:淘宝店铺模板有什么用 编辑:程序博客网 时间:2024/05/17 06:09

线程等待
等待是线程的必要之恶。
比较low的等待方法是:1.sleep(), 2.循环GetExitCodeThread()。这两种方法耗cpu,且使代码效率低下。

用WaitForSingleObject,等待核心对象变为激发状态

DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
DWORD WaitForMultipleObjects(DWORD nCount,CONST HANDLE *lpHandles,BOOL bWaitAll,DWORD dwMilliseconds);

bWaitAll 如果此为 TRUE ,表示所有的 handles 都必须激发,此函数才得以返回。否则此函数将在任何一个handle 激发时就返回。

返回值:
超时:WAIT_TIMEOUT
bWaitAll:为TRUE, 返回WAIT_OBJECT_0。为FALSE,将返回值减去 WAIT_OBJECT_0,即handle下标。
失败:WAIT_FAILED。

handles元素个数MAX = MAXIMUM_WAIT_OBJECTS

同步机制

1. 同步vs异步
让我先对同步( synchronous)与异步( asynchronous)做个说明。
当程序1调用程序2时,程序1 停下不动,直到程序2完成回到程序1来,程序1才继续下去,这就是所谓的“ synchronous”。
如果程序1调用程序2后,径自继续自己的下一个动作,那么两者之间就是所谓的“ asynchronous”。
Win32 API中的 SendMessage() 就是同步行为,而 PostMessage() 就是异步行为。

Win32中关于进程和线程的协调工作是由同步机制 (synchronous mechanism)来完成的

2.Critical Sections(关键区域、临界区域)
Win32 之中最容易使用的一个同步机制就是 critical sections。
可共享资源(如一块内存、一个数据结构、一个文件),其具有使用排他性特点。同一时间内只能被一个线程处理
每个需要保护的资源 明一个CRITICAL_SECTION类型的变量。
使用方法:初始化->进入锁定->共享资源操作代码->解除锁定->销毁

InitializeCriticalSection()EnterCriticalSection()LeaveCriticalSection()DeleteCriticalSection()

使用时注意:
- 不要长时间锁住一份资源,不要有sleep或wait…API,停很久会挂着
- 考虑资源被使用的频率如何
- 如果critical section内的代码当掉,则整个程序都会挂死
- 注意对于多个cs的死锁问题

3.互斥器
Mutex与critical section用途类似,一个时间内只能有一个线程拥有mutex。但它牺牲速度以增加弹性。

优缺点
- 比cs花费几乎100倍时间,因cs不需要进入操作系统核心,只在用户态操作
- Mutex可跨进程(指定名称,系统全局,引用计数),cs只能在同一进程
- Mutex可指定等待时间,cs不行

处理被舍弃的mutex需明确两个问题:
1) 如何知道mutex被舍弃?线程结束前没有调用ReleaseMutex(),则在wait…API返回WAIT_ ABANDONED_0 。
2) 如何处理?分析比较困难,需查看线程代码。

任何时候只要你想锁住超过一个以上的同步对象,你就有死锁的潜在病因。如果总是在相同时间把所有对象都锁住,问题可去矣。

为什么有一个最初拥有者?
CreateMutex() 的第二个参数 bInitialOwner,允许你指定现行线程(current thread)是否立刻拥有即将产生出来的 mutex。乍见之下这个参数或许只是提供一种方便性,但事实上它阻止了一种 race condition 的发生。
与 critical section 不同, mutexes 可以跨进程使用,以及跨线程使用。Mutex 可以根据其名称而被开启。所以,另一个进程可以完全不需要和产生mutex 的 进 程 打 声 招 呼 , 就 根 据 名 称 开 启 一 个 mutex 。 如 果 没 有bInitialOwner,你就必须写下这样的代码:

HANDLE hMutex = CreateMutex(NULL, FALSE, "Sample Name");int result = WaitForSingleObject(hMutex, INFINITE);

但是这样的安排可能会产生 race condition 。如果在 CreateMutex 完成之后,发生了一个 context switch ,执行权被切换到另一个线程,那么其他进程就有可能在 mutex 的产生者调用 WaitForSingleObject() 之前,锁住这个mutex 对象。

4.信号量【关于这段需反复阅读如下文字,不好懂啊!】
为什么你需要一个 semaphore?
它是解决各种 producer/consum er 问题的关键要素。这种问题会存有一个缓冲区,可能在同一时间内被读出数据或被写入数据。
举例说明:Steve想在加州租一辆车。租车店柜台后面坐了好几位租车代理人。 Steve 告诉租车代理人说他想要一部敞篷车,接待他的那位代理人往窗外一看,有三辆敞篷车可以用,于是开始写派车单。不幸的是,就那么巧,有另三个人也同时要一辆敞篷车,而他们的租车代理人也正在做 Steve 的代理人的相同动作。现在,有四个人想租三辆车,而必然有某个人要被淘汰出局。

租车公司这边的问题是,他们不可能即时写下派车单并且马上给租车人钥匙。整个租车程序过长,长到足够让另一位代理人把同一辆车租给另一个人。这种情况我们已经在多线程的情况下一再地看到了。如果有许多个线程正在处理相同的资源,那么必须有某些机制被用来阻止线程干扰其他线程。

解决之道是:首先,所有的敞篷车都被视为相同(是啊,什么时候你租车还选颜色的?),在钥匙被交到客户手上之前,唯一需要知道的就是现在有几辆车可以用。我们可以用 semaphore 来维护这个数字,并保证不论是增加或减少其值,都是在一个不可分割的动作内完成。当 semaphore 的数值降为 0时,不论什么人要租车,就得等待了。

理论可以证明, mutex 是 semaphore 的一种退化。如果你产生一个semaphore 并令最大值为 1,那就是一个 mutex。也因此, mutex 又常被称为binary semaphore。semaphores 不像 mutexes,它并没有所谓的“wait abandoned”状态可以被其他线程侦测到。

在许多系统中, semaphores 常被使用,因为 mutexes 可能并不存在。
在Win32 中 semaphores 被使用的情况就少得多,因为 mutex 存在的缘故。

semaphore 的现值代表的意义是目前可用的资源数。如果semaphore 的现值为 1,表示还有一个锁定动作可以成功。如果现值为 5,就表示还有五个锁定动作可以成功。

每当一个锁定动作成功,semaphore 的现值就会减1。你可以使用任何一种Wait…() 函数(例如 WaitForSingleObject())要求锁定一个 semaphore。因此,如果 semaphore 的现值不为0,Wait…() 函数会立刻返回。这和 mutex 很像,如果没有任何线程拥有 mutex, Wait…() 会立刻返回。

如果锁定成功,你也不会收到 semaphore 的拥有权。因为可以有一个以上的线程同时锁定一个semaphore,所以谈 semaphore 的拥有权并没有太多帮助。在semaphore身上并没有所谓“独占锁定”这种事情。也因为没有拥有权的观念,一个线程可以反复调用 Wait…() 函数以产生新的锁定。这和 mutex绝不相同:拥有 mutex 的线程不论再调用多少次 Wait…() 函数,也不会被阻塞住

一旦 semaphore 的现值降到 0,就表示资源已经耗尽。此时,任何线程如果调用 Wait…() 函数,必然要等待,直到某个锁定被解除为止。

解除锁定:ReleaseSemaphore()
当你调用WaitForSingleObject() 并获得一个 semaphore 锁定之后,你就需要调用ReleaseSemaphore()。 Semaphore**常常被用来保护固定大小的环状缓冲区(ring buffer)。程序如果要读取环状缓冲区的内容,必须等待semaphore。线程将数据写入环状缓冲区,写入的数据可能不只一笔,在这种情况下解除锁定时的 semaphore 增额应该等于写入的数据笔数。**

ReleaseSemaphore() 对于 semaphore 所造成的现值的增加,绝对不会超过CreateSemaphore() 时所指定的 lMaxim umCount。

与 mu tex 不同的是,调用 ReleaseSem aphore() 的那个线程,并不一定就得是调用 Wait…() 的那个线 程 。任何线程都可以在任何时间调用ReleaseSemaphore(),解除被任何线程锁定的 semaphore。【?此处已看晕!因没有拥有权的概念?】

5.事件
Event object有什么用途?
Event 对象之所以有大用途,正是因为它们的状态完全在你掌控之下。Mutexes 和 sem aphores 就 不 一 样 了 , 它 们 的 状 态 会 因 为 诸 如WaitForSingleObject() 之类的函数调用而变化。所以, 你可以精确告诉一个event 对象做什么事,以及什么时候去做。
Event 对象被运用在多种类型的高级 I/O 操作中。

假设“receiver”线程检查队列中是否有字符,这时候发生 context switch,切换到“sender”线程,它对一个 event 对象进行 pulse 操作,这时候又发生 context switch ,回到 receiver 线程,调用WaitForSingleObject(),等待 event 对象。由于这个动作发生在 sender 线程激发 event 之后,所以 event 会遗失,于是 receiver 永远不会醒来,程序进入死锁状态。这正是 sem aphore 之所以被创造用以解决问题的地方。

6.Interlocked Variables
同步机制的最简单类型是使用 interlocked 函数,对着标准的 32 位变量进行操作。这些函数并没有提供“等待”机能,它们只是保证对某个特定变量的存取操作是“一个一个接顺序来”。

用于计数器、引用计数等

所谓的 interlocked 函数,共有两个:

InterlockedIncrement()InterlockedDecrement()

这两个函数都只能够和 0 做比较,不能和任何其他数值比较
Interlocked…() 函数的传回值代表计数器和 0 的比较结果。


**

同步机制摘要

**

Critical Section
Critical section(临界区)用来实现“排他性占有”。适用范围是单一进程的各线程之间。它是:
- 一个局部性对象,不是一个核心对象。
- 快速而有效率。
- 不能够同时有一个以上的 critical section 被等待。
- 无法侦测是否已被某个线程放弃。

Mutex
Mutex 是一个核心对象,可以在不同的线程之间实现“排他性占有”,甚至即使那些线程分属不同进程。它是:
- 一个核心对象。
- 如果拥有 mutex 的那个线程结束,则会产生一个“abandoned”错误信息。
- 可以使用 Wait…() 等待一个 mutex。
- 可以具名,因此可以被其他进程开启。
- 只能被拥有它的那个线程释放(released)。

Semaphore
Semaphore 被用来追踪有限的资源。它是:
- 一个核心对象。
- 没有拥有者。
- 可以具名,因此可以被其他进程开启。
- 可以被任何一个线程释放(released)。

Event Object
Event object 通常使用于 overlapped I/O,或用来设计某些自定义的同步对象。它是:
- 一个核心对象。
- 完全在程序掌控之下。
- 适用于设计新的同步对象。
- “要求苏醒”的请求并不会被储存起来,可能会遗失掉。
- 可以具名,因此可以被其他进程开启。

Interlocked Variable
如果 Interlocked…() 函数被使用于所谓的 spin-lock,那么它们只是一种同步机制。所谓 spin-lock 是一种 busy loop,被预期在极短时间内执行,所以有最小的额外负担(overhead)。系统核心偶尔会使用它们。除此之外, interlocked variables 主要用于引用计数。它们:
- 允许对 4 字节的数值有些基本的同步操作,不需动用到 critical section 或 mutex 之类。
- 在 SMP (Symmetric Multi-Processors)操作系统中亦可有效运作。

虽然摘录了这么多笔记,但是真正理解的估计只有50%~
仍需反复推敲和练习
~

原创粉丝点击