正文

线程同步能避免出现“race conditions”(竞争条件)和“data c2013-01-26 09:24:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/edwardguo/54099.html

分享到:

线程同步能避免出现“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同时发生:

  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <windows.h> 
  4.  
  5. typedef struct _Node 
  6. struct _Node *next; 
  7. int data; 
  8.  
  9. }Node; 
  10. typedef struct _List 
  11. Node *head; 
  12. CRITICAL_SECTION critical_sec; 
  13. }List; 
  14.  
  15. List *CreateList() 
  16. List *pList=(List *)malloc(sizeof(List)); 
  17. pList->head=NULL; 
  18. InitializeCriticalSection(&pList->critical_sec); 
  19. return pList; 
  20.  
  21. void DeleteList(List *pList) 
  22. DeleteCriticalSection(&pList->critical_sec); 
  23. free(pList); 
  24. void AddHead(List * pList,Node *node) 
  25. EnterCriticalSection(&pList->critical_sec); 
  26. node->next=pList->head; 
  27. pList->head=node; 
  28. LeaveCriticalSection(&pList->critical_sec); 
  29.  
  30.  
  31. void Insert(List *pList,Node *afterNode,Node *newNode) 
  32. EnterCriticalSection(&pList->critical_sec); 
  33. if(afterNode==NULL) 
  34.   AddHead(pList,newNode); 
  35. else 
  36.   newNode->next=afterNode->next; 
  37.   afterNode->next=newNode; 
  38. LeaveCriticalSection(&pList->critical_sec); 
  39.  
  40. Node * Next(List *pList,Node *node) 
  41. Node *next; 
  42. EnterCriticalSection(&pList->critical_sec); 
  43. next=node->next; 
  44. LeaveCriticalSection(&pList->critical_sec); 
  45. 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();

  1. typedef struct _Node 
  2. struct _Node *next; 
  3. int data; 
  4.  
  5. }Node; 
  6. typedef struct _List 
  7. Node *head; 
  8. HANDLE hMutex; 
  9. }List; 
  10. void SwapLists(struct List *list1,Struct List *list2) 
  11. struct List *tmp_list; 
  12. HANDLE arrhandles[2]; 
  13. arrhandles[0]=list1->hMutex; 
  14. arrhandles[1]=list2->hMutex; 
  15. WaitForMultipleObjects(2,arrHandles,TRUE,INFINITES); 
  16. /*相关操作*/ 
  17. ReleaseMutex(arrhandles[0]); 
  18. ReleaseMutex(arrhandles[1]); 
  19.  
  20.  



-----信号量(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.或用来设计某些自定义的同步对象。它是一个核心对象,完全在程序的掌控之下,适用于设
计新的同步对象。“要求苏醒”的请求并不会被存储起来,可能会遗失掉。可以具名,因此可以被其他进程开启.

阅读(3707) | 评论(1)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

loading...
您需要登录后才能评论,请 登录 或者 注册