第四章:同步控制
1.理解同步与异步的概念
2.Critical Sections:
(1)critical sections:是指“用来处理一份被共享之资源”的程序代码。如内存、数据结构、文件等。
(2)critical section并不是核心对象,存在于进程的内存中。
(3)VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);初始化CRITICAL_SECTION类型变量。
(4)VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);清除CRITICAL_SECTION类型变量。
(5)VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);线程进入,锁定lpCriticalSection。
(6)VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);线程离开,解除锁定。
(7)一旦线程进入一个 critical section,它就能够一再地重复进入该 critical section。唯一的警告就是,每一个“进入”操作都必须有一个对应的“离开”操作。
(8)不要长时间锁住一份资源:千万不要在一个 critical section 之中调用 Sleep() 或任何 Wait...() API 函数。
(9)避免Danling Critical Sections:Windows NT 和 Windows 95 在管理dangling critical sections 时有极大的不同。在 Windows NT 之中,如果一个线程进入某个 critical section 而在未离开的情况下就结束,该 critical section 会被永远锁住。然而在 Windows 95 中,如果发生同样的事情,其他等着要进入该 critical section 的线程,将获准进入。这基本上是一个严重的问题,因为你竟然可以在你的程序处于不稳定状态时进入该 critical section。
3.死锁
(1)任何时候当一段代码需要两个(或更多)资源时,都有潜在性的死锁阴影。
(2)强制将资源锁定,使它们成为“all-or-nothing”,可以阻止死锁的发生。
(3)哲学家进餐问题:WaitForMutipleObjects,只能用于核心对象,引出了Mutex。
4.互斥器(Mutexes)
(1)锁住Mutex比critical section 慢,因为要切换到OS核心态。
(2)Mutexes 可跨进程使用,Critical section 只能在同一个进程中使用。
(3)等待一个 mutex 时,可以指定“结束等待”的时间长度。但对于critical section 则不行。
(4)mutex、critical section相关函数比较:
InitializeCriticalSection() CreateMutex()
OpenMutex()
EnterCriticalSection() WaitForSingleObject()
WaitForMultipleObjects()
MsgWaitForMultipleObjects()
LeaveCriticalSection() ReleaseMutex()
DeleteCriticalSection() CloseHandle()
(5)创建mutex时指定名称,使其能跨进程使用。mutex名称是全局性的。
(6)HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全属性,NULL表示使用默认的属性
BOOL bInitialOwner, //TRUE表示调用这个函数的线程拥有该mutex
LPCTSTR lpName //mutex名称,不能包含
);
(7)“mutex 激发状态”:当没任何线程拥有该 mutex 而且有一个线程正以 Wait...() 等待该 mutex,该mutex 就会短暂地出现激发状态,使 Wait...() 得以返回。
(8)Bool ReleaseMutex(Handle hMutex) 释放mutex
(9)Mutex 的拥有权并非属那个产生它的线程,而是那个最后对此 mutex 进行 Wait...() 操作并且尚未进行 ReleaseMutex() 操作的线程。
(10)处理被舍弃的互斥器:如果线程拥有一个 mutex 而在结束前没有调用 ReleaseMutex(),mutex 不会被摧毁。取而代之的是,该 mutex会被视为“未被拥有”以及“未被激发”,而下一个等待中的线程会被以WAIT_ABANDONED_0 通知。如果其他线程正以 WaitForMultipleObjects() 等待此 mutex,该函数也会返回,传回值介于 WAIT_ABANDONED_0 和 (WAIT_ABANDONED_0_n +1)之间,其中的 n 是指handle 数组的元素个数。线程可以根据这个值了解到究竟哪一个 mutex 被放弃了。至于WaitForSingleObject() , 则只是传回WAIT_ABANDONED_0。
(11)CreateMutex() 的第二个参数 bInitialOwner,允许你指定现行线程(current thread)是否立刻拥有即将产生出来的mutex。乍见之下这个参数或许只是提供一种方便性,但事实上它阻止了一种 race condition 的发生,如果没有bInitialOwner,你就必须写下这样的代码:
HANDLE hMutex = CreateMutex(NULL, FALSE, "Sample Name");
int result = WaitForSingleObject(hMutex, INFINITE);
但是这样的安排可能会产生 race condition。
(12)一个线程拥有了某个mutex,后面的wait…()不会被阻塞。
5.信号量
(1)HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpAttributes, //安全属性,默认NULL
LONG lInitialCount, //semaphore的初值,必须大于或等于0,小于等于IMaximumCount
LONG lMaximumCount, //semaphore最大值,同一时间能够锁住semaphore之线程的最大数。
LPCTSTR lpName //semaphore的名称,NULL表示没有名字
);
(2) Semaphore没有拥有权的概念,一个线程可以反复调用 Wait...() 函数以产生新的锁定。
这和 mutex绝不相同:拥有 mutex 的线程不论再调用多少次 Wait...() 函数,也不会被阻塞住。
(3)BOOL ReleaseSemaphore(
HANDLE hSemaphore, //semaphore的handle
LONG lReleaseCount, //semaphore现值的增额,不可以是负值或0
LPLONG lpPreviousCount //借此传回semaphore原来的现值
);
注意:lpPreviousCount 所传回来的是一个瞬间值。你不可以把lReleaseCount 加上 *lpPreviousCount,就当作是 semaphore 的现值,因为其他线程可能已经改变了 semaphore 的值。
(4)设定semaphore初值的意义与CreateMutex() 的 bInitialOwner 参数的存在理由是一样的
6.事件
(1)Event的唯一目的就是成为激发或未激发状态,这两种状态全由程序控制。
(2)HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, //安全属性,NULL默认
BOOL bManualReset,//FALSE表示event在变成激发状态之后,自动重置为非激发状态,TRUE表示需要调用ResetEvent才能重置
BOOL bInitialState,//TRUE表示一开始处于激发状态,FALSE处于非激发状态
LPCTSTR lpName //event名称
);
(3)三个操作函数:
SetEvent:把event对象设为激发状态
ResetEvent:把event对象设为非激发状态
PulseEvent:对应Manual Reset Event:把event设为激发状态,唤醒所有等待线程,然后event恢复为非激发状态。
对应Auto Reset Event: 把event设为激发状态,唤醒一个等待线程,然后event恢复为非激发状态
(4)如果你面对一个 AutoReset event 对象调用 SetEvent() 或 PulseEvent(),而彼时并没有任何线程正在等待。这种情况下这个 event 会被遗失.
7.Interlocked Variables
(1) InterlockedIncrement,加1后与0比较,对应于 AddRef()操作
(2) InterlockedDecrement,减1后与0比较,对应于Release()操作
(3) InterlockedExchange设定一个新值,传回旧值,多线程安全。