线程同步能避免出现“race conditions”(竞争条件)和“data corruption”(数据破坏)的情况。
同步(synchronous)与异步(asynchronous)的定义:
当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1来,程序1才继续下去,这就是“同步”。
如果程序1调用程序2后,径自继续自己的下一个动作,那么两者之间就是“异步”。
Win32 API 中的SendMessage()是同步行为,而PostMessage()是异步行为。
win32中关于进程和线程的协调工作是由同步机制来完成的。同步机制相当于线程之间的红绿灯。
-----Critical Sections(关键区域、临界区域)
win32中最容易使用的同步机制就是critical sections.所谓critical sections意指一小块“用来处理一份被共享之资源
”的程序代码。这里的资源并不是来着.RES的Windows资源,而是广义地指一块内存、一个数据结构、一个文件,或任何其
他具有“使用之排他性”的东西。
为了防止问题的发生,一次只能有一个线程获准进入critical section中。实施的方式就是在程序中加上“进入”或“离
开”critical section的操作。
在win32中可以为一个需要保护的资源声明一个CRITICAL_SECTION类型的变量。这个变量扮演红绿灯的角色。
Critical section 并不是核心对象,没有handle这样的东西,它和核心对象不同,不存在进程的内存空间。你应该将一个
类型为CRITICAL_SECTION的局部变量初始化,方法是调用InitializeCriticalSection();
VOID InitializeCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // critical section 一个指针,指向欲初始化的CRITICAL_SECTION变量
);
当用完critical section时,必须调用DeleteCriticalSection()清除它。这个函数并没有“释放对象”的意义在里面,不
同于C++中的delete运算符。
VOID DeleteCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // critical section指向不需要的CRITICAL_SECTION变量
);
Critical section同步机制示例:
CRITICAL_SECTION gCriticalSection;
void CreateDeleteCriticalSection()
{
InitializeCriticalSection(&gCriticalSection);
/*Do Something here */
DeleteCriticalSection(&gCriticalSection);
}
一旦critical section 被初始化,每一个线程就可以进入其中,只要它通过了EnterCriticalSection()这一关。
VOID EnterCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // critical section指向一个你即将锁定的CRITICAL_SECTION变量
);
当线程准备好要离开critical seciton 时,必须调用LeaveCriticalSection():
VOID LeaveCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // critical section指向一个即将解除锁定的CRITICAL_SECTION变量
);
void UpdateData()
{
EnterCriticalSection(&gCriticalSection);
/*Update the resource*/
LeaveCriticalSection(&gCriticalSection);
}
使用critical section操作避免链表insert和add同时发生:
- #include <stdio.h>
- #include <stdlib.h>
- #include <windows.h>
- typedef struct _Node
- {
- struct _Node *next;
- int data;
- }Node;
- typedef struct _List
- {
- Node *head;
- CRITICAL_SECTION critical_sec;
- }List;
- List *CreateList()
- {
- List *pList=(List *)malloc(sizeof(List));
- pList->head=NULL;
- InitializeCriticalSection(&pList->critical_sec);
- return pList;
- }
- void DeleteList(List *pList)
- {
- DeleteCriticalSection(&pList->critical_sec);
- free(pList);
- }
- void AddHead(List * pList,Node *node)
- {
- EnterCriticalSection(&pList->critical_sec);
- node->next=pList->head;
- pList->head=node;
- LeaveCriticalSection(&pList->critical_sec);
- }
- void Insert(List *pList,Node *afterNode,Node *newNode)
- {
- EnterCriticalSection(&pList->critical_sec);
- if(afterNode==NULL)
- {
- AddHead(pList,newNode);
- }
- else
- {
- newNode->next=afterNode->next;
- afterNode->next=newNode;
- }
- LeaveCriticalSection(&pList->critical_sec);
- }
- Node * Next(List *pList,Node *node)
- {
- Node *next;
- EnterCriticalSection(&pList->critical_sec);
- next=node->next;
- LeaveCriticalSection(&pList->critical_sec);
- return next;
- }
一旦线程进入一个critical section,它就能够一再重复进入该critical section。如Insert()可以调用AddHead()而不需
要先调用LeaveCriticalSection()。
----最小锁定时间
不要长时间锁定一份资源,不要在一个critical section中调用Sleep()或任何API函数
Q:如果线程在critical section中停留很久,会怎么样?
A:不会出现任何错误信息。当主线程需要这个锁定的资源时,程序会挂在那,动也不动。
Q:如果线程在critical section中结束,会怎么样?
A:Critical section的一个缺点就是没有办法获知进入critical section中的那个线程的生死。如果进入critical
section的那个线程结束了,而没有调用LeaveCriticalSection()的话,系统没有办法将critical section清除。如果需要
那样的机能,应该使用mutex.
----死锁
任何时候,当一段代码需要两个资源时,都有潜在性的死锁阴影。
哲学家进餐问题要求同一时刻取两只筷子而不是一只筷子。我能够等待一个以上的critical sections吗?
WaitForMultipleObjects()允许你对操作系统发生等待要求。直到所有指定的对象都激活才返回。但是它等待的只是核心
对象,critical section并不是核心对象(它没有handle),因而必须使用win32中的同步机制:mutex;
-----互斥器(Mutexex)
Win32的Mutex用途和critical section非常类似,但是它牺牲速度以增加弹性。一个时间内只能够有一个线程拥有mutex,就犹如同一个时间内只能够有一线程进入同一个critical section一样。
mutex和critical section的差别:
1.锁住一个未被拥有的mutex,比锁住一个未被拥有的critical section,需要花费几乎100倍的时间。因为critical section 不需要进入操作系统的核心,直接在“user mode”就可能进行操作。
2.Mutexes可以跨进程使用。Critical section只能够在同一个进程中使用。
3.等待一个mutex时,可以指定“结束等待”的时间长度。但对于critical section不行。
两种对象的相关函数比较:
CRITICAL_SECTION Mutex核心对象
InitializeCriticalSection() CreateMutex()
OpenMutex()
EnterCriticalSection() WaitForSingleObject();
WaitForMultipleObjects();
MsgWaitForMultipleObjects();
LeaveCriticalSection() ReleaseMutex();
DeleteCriticalSection() CloseHandle();
为了能够跨进程使用同一个mutex,在产生mutex时指定其名称,不使用句柄。
----产生一个互斥器Mutex
Mutex是一个核心对象,被保存在系统核心之中,并且和其他核心对象一样,有所谓的引用计数器。利用CreateMutex()产生一个mutex:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // SD 安全属性
BOOL bInitialOwner, // initial owner mutex的拥有者,如果设置为TRUE,则拥有者为调用者
LPCTSTR lpName // object name mutex的名称,是一个字符串,可以根据名称使用
mutex
);
如果不需要一个mutex,可以调用CloseHandle()将它关闭。
----打开一个互斥器(Mutex)
如果mutex已经产生,并且有一个名字。那么任何其他的线程和进程都可根据名称来打开那个mutex.
HANDLE OpenMutex(
DWORD dwDesiredAccess, // access
BOOL bInheritHandle, // inheritance option
LPCTSTR lpName // object name
);
----锁住一个互斥器(Mutex)
想获得一个mutex的拥有权,使用Win32 的Wait()函数。Wait()函数对mutex所做的事情和EnterCriticalSection()对critical section所做的事情差不多。
一旦没有任何线程拥有mutex,这个mutex便处于激发状态。因此,如果没有任何线程有用mutex,Wait()便会成功。
BOOL ReleaseMutex(
HANDLE hMutex // handle to mutex
);
Mutex的拥有权并不是属于产生它的线程,而是那个最后对Mutex进行Wait..()操作并且没有进行ReleaseMutex()操作的线
程。线程拥有mutex就像线程进入critical section一样,一次只能有一个线程拥有该mutex.
----处理被舍弃的互斥器(Mutexes)
在一个程序中,线程绝对不应该在它即将结束前还拥有一个mutex,因为这意味着线程没有能够适当的清初其资源。
因为某种原因,线程可能没有在结束前调用ReleaseMutex().为了解决这个问题,mutex有一个非常重要的特性。这个性质是同步机制中独一无二的。
任何时候想锁住超过一个以上的同步对象,就有死锁的潜在病因。如果在相同的实际把所有的对象都锁住,问题就可以解决。
新版SwapList();
- typedef struct _Node
- {
- struct _Node *next;
- int data;
- }Node;
- typedef struct _List
- {
- Node *head;
- HANDLE hMutex;
- }List;
- void SwapLists(struct List *list1,Struct List *list2)
- {
- struct List *tmp_list;
- HANDLE arrhandles[2];
- arrhandles[0]=list1->hMutex;
- arrhandles[1]=list2->hMutex;
- WaitForMultipleObjects(2,arrHandles,TRUE,INFINITES);
- /*相关操作*/
- ReleaseMutex(arrhandles[0]);
- ReleaseMutex(arrhandles[1]);
- }
-----信号量(Semaphores)
信号量机制可以有效的解决生产者和消费者问题。
Win32中一个semaphore可以被锁住最多n次,其中n是semaphore被产生时指定的。n代表“可以锁住一份资源”的线程个数
,并非单独一个线程就不能够拥有所有的锁定。
mutex是sempahore的一种退化。如果你产生一个semaphore并令最大值为1,那就是一个mutex.故mutex又称为binary semaphore.
在Win32中semaphores被使用的情况少,因为有Mutex存在。
a.产生信号量CreateSemaphore();
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // SD
LONG lInitialCount, // initial count semaphore的初值,》0&&《IMaximumCount
LONG lMaximumCount, // maximum count 线程的最多个数
LPCTSTR lpName // object name 信号量的名字
);
semaphore的现值代表目前可用的资源数。一旦semaphore的值降低到0,就表示资源耗尽。任何线程调用Wait...()必须等待,直到某个锁定被解除为止。
b.解除锁定(Releasing Locks)
调用ReleaseSemaphore(),并将现值加1,并传回semaphore的前一个现值。
BOOL ReleaseSemaphore(
HANDLE hSemaphore, // handle to semaphore
LONG lReleaseCount, // count increment amount
LPLONG lpPreviousCount // previous count
);
与mutex不同的是,调用ReleaseSemaphore()的那个线程,并不一定就是调用Wait..()的那个线程。任何线程都可以在任何时间调用ReleaseSemaphore(),解除被任何线程锁定的semaphore.
-----事件(Event Objects)
Win32中最具有弹性的同步机制就是events 对象了。Event对象是一种核心对象。它的唯一目的就是成为激发状态或未激发状态。这两种状态完全有程序来控制,不会成为wait..()函数的副作用。与Mutexes和semaphores就不一样了,它们的状态会因为WaitForSingleObject()直接的函数调用而变化。你可以精确告诉一个event对象做什么事情,以及什么时候去做。
a.产生一个event对象
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // SD
BOOL bManualReset, // reset type 如果为false,这个event现在将变成激发状态之后,自动 重置为非激发状态,如果为True,不会自动重置,必须靠程序 (ResetEvent)操作才能激发状态的event重置为非激发状态
BOOL bInitialState, // initial state ,True表示一开始就处于激发状态,否则一开始为非激发状态
LPCTSTR lpName // object name
);
SetEvent() 把event对象设为激发状态
ResetEvent() 把event对象设为非激发状态
PulseEvent() 如果是一个Manual Reset Event:把event对象设为激发状态。唤醒所有的等待线程。然后event恢复为未激发状态。如果是一个Auto Reset Event:把event 对象设为激发状态,唤醒“一个”等待中的线程,然后event恢复为非激发状态。
同步机制总结
a.Critical Section
Critical Section(临界区)用来实现“排他性占有”。适用范围是单一进程的各个线程之间。它是(1)一个局部性对象,不是核心对象,快速而有效率,不能够同时有一个以上的critical section被等待无法侦测是否已被某个线程放弃
b.Mutex
它是一个核心对象,可以在不同的线程之间实现“排他性占有”,甚至那些线程属于不同的进程。它是一个核心对象。
如果拥有mutex的那个线程结束,则会产生一个“abandoned”错误信息。可以使用Wait...()等待一个mutex.可以具名,因此可卡因被其他进程开启。只能被拥有它的那个线程释放。
c.Semaphore
semaphore被用来追踪有限的资源。它是 一个核心对象,没有拥有者。可以具名,因此可以被其他进程开启。
可以被任何一个线程释放(released).
d.Event Object
通常适用于overlapped I/O.或用来设计某些自定义的同步对象。它是一个核心对象,完全在程序的掌控之下,适用于设计新的同步对象。“要求苏醒”的请求并不会被存储起来,可能会遗失掉。可以具名,因此可以被其他进程开启.
评论