线程同步

效率
      you should try to use nonshared data first and then use volatile reads, volatile writes, interlocked APIs, SRWLocks, critical sections.
      And if all of these won't work for your situation, then and only then, use kernel objects

一,用户方式中
.原子访问 Atomic Access 
InterlockedExchange系列函数是针对于某个单一变量进行的原子操作          
Volatile关键字告诉编译器不要对变量的访问做任何优化,每次读访变量的值都要去内存中读而不是在寄存器中读。
运行的速度极快:调用一个互锁函数通常会导致执行几个C P U周期(通常小于5 0),并且不会从用户方式转换为内核方式(通常这需要执行1 0 0 0个C P U周期)。
.读写锁 (Vista以上支持)
  允许多个读线程同时读取共享数据,但在同一时间只允许一个写线程修改共享数据。            
   用法: InitializeSRWLock
          写者: AcquireSRWLockExclusiveReleaseSRWLockExclusive
          读者: AcquireSRWLockSharedReleaseSRWLockShared
   我们并不需要销毁读写锁对象,因为系统自动处理读写锁对象。
   读写锁的效率比临界区要高到少一倍左右。


.关键代码段(CriticalSection)
       临界区对象用法:InitializeCriticalSection(TryEnterCriticalSection ),EnterCriticalSectionLeaveCriticalSectionDeleteCriticalSection       EnterCriticalSection 线程进入临界区后 若其它线程试图想进入该临界区线程会处于阻塞(Wait)状态(该线程必须从用户方式转入内核方式(大约1 0 0 0个C P U周期)),使它们不占用任何CPU.  
      不能跨进程同步,请使用互斥内核对象。
1. 每个共享资源使用一个CRITICAL_SECTION变量;
2. 如果一个线程中同时进入了2个或更多的关键代码段,则其他同类线程的进入顺序必须相同,否则可能产生死锁;
3. InitializeCriticalSectionAndSpinCount作用:多处理器上可能有利于效率;有返回值(初始化分配内存可能失败);设置dwSpinCount 的高位,则会预先分配内核对象(事件)的空间——某种程度上避免的EnterCriticalSection函数失败;
4. 如果不想进程暂停那么试试TryEnterCriticalSection;
5. 实际上,EnterCriticalSection也会超时,通常是30天左右.

.条件变量
              条件变量要与临界区对象或读写锁对象一起使用。
              SleepConditionVariableCSSleepConditionVariableSRW
              WakeConditionVariableWakeAllConditionVariable            

二,Wait For Kernel Object(s) (event, timer, semaphore, mutex)
Common API:
  WaitForSingleObject  WaitForMultipleObjects
  MsgWaitForMultipleObjects
  CreateEvent CreateEventEx SetEvent ResetEvent OpenEvent
  CreateSemaphore OpenSemaphore
  CreateMutex OpenMutex ReleaseMutex
等待函数: 可使线程自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止
DWORD WaitForSingleObject(HANDLE hObject,DWORD dwMilliseconds);
DWORD WaitForMultipleObjects(DWORD dwCount,
   CONST HANDLE* phObjects,
   BOOL fWaitAll,
   DWORD dwMilliseconds);
返回值:WAIT_FAILED,WAIT_TIMEOUT,WAIT_OBJECT_0+X
MsgWaitForMultipleObjects函数
这个函数是用来在GUI现成中等待对象被激发,是用来修改主消息循环的  
  这个函数非常像WaitForMultipleObjects(),但是他会在“对象被激发”  
  或“消息到达队列”时被唤醒而返回。MsgWaitForMultipleObject()函数  
  比WaitForMultipleObject()多接受一个参数,允许指定哪些消息是观察对象  
  MsgWaitForMultipleObject()的正确使用方式是改写主消息循环,使得激发状态  
  的HANDLE得以像消息一样被等待

事件Event  : 能够通知一个操作已经完成
HANDLE CreateEvent(PSECURITY_ATTRIBUTES psa,
   BOOL fManualReset,

   BOOL fInitialState,
PCTSTR pszName);
当调用S e t E v e n t时,可以将事件改为已通知状态:
BOOL SetEvent(HANDLE hEvent);
当调用R e s e t E v e n t函数时,可以将该事件改为未通知状态:
BOOL ResetEvent(HANDLE hEvent);


      自动事件:当处于Signal状态时,只有一个等待该事件的线程会被唤醒,之后事件会被自动回到Non Signal状态。
      手动事件:当处于Signal状态时,所有等待该事件的线程都会被唤醒,之后需要调用ResetEventEvent对象调回到Non Signal状态。
    如果是AutoResetEvent,那么在signed之后,WaitForXXObject返回之前,会再次被unsigned.


      1) 多个线程WaitForSingleObject同一个AutoResetEvent,那么此对象signed之后,哪个线程变为可调度呢?(算法是公平的);signed之后,WaitForXXObject返回之前,会再次被unsigned
      2) 进程句柄、线程句柄、事件等等内核对象,正常使用时(运行时)都是unsigned的,完蛋了才signed;
      3) 完蛋了的Object才能使WaitForSingleObject或WaitForMultipleObjects返回(当然超时或出错除外);
      4) WaitForMultipleObjects如果不是WaitAll,那么句柄数组中有一个signed的就会返回,返回值 dwResult - WAIT_OBJECT_0 表示signed的句柄的索引值;
      5) ManualResetEvent和AutoResetEvent的差别在于:AutoResetEvent可以自动调度所有需要读写同一数据的线程,弊端是:无法确定谁先被调度;

等待定时器WaitableTimer: 在某个时间或按规定的间隔时间发出自己的信号通知的内核对象。它们通常用来在某个时间执行某个操作。
HANDLE CreateWaitableTimer(
   PSECURITY_ATTRIBUTES psa,
   BOOL fManualReset,
   PCTSTR pszName);
BOOL SetWaitableTimer(
   HANDLE hTimer,
   const LARGE_INTEGER *pDueTime,
   LONG lPeriod,
   PTIMERAPCROUTINE pfnCompletionRoutine,
   PVOID pvArgToCompletionRoutine,
   BOOL fResume);


等待定时器与用户定时器(用SetTimer函数进行设置)之间的最大差别是: 用户定时器需要在应用程序中设置许多附加的用户界面结构,这使定时器变得资源更加密集。另外,等待定时器属于内核对象,这意味着它们可以供多个线程共享,并且是安全的。

信标(semaphore)用于对资源进行计数,控制可以时运行的线程的数量。
HANDLE CreateSemaphore(
   PSECURITY_ATTRIBUTE psa,
   LONG lInitialCount,  //
记录当前可运行线程的数量CurrentSource
   LONG lMaximumCount, //允许运行线程数目的最大值
   PCTSTR pszName);


      当CurrentSource大于0时,信标处于Signal状态。CurrentSource=0时,信标处于Non Signal状态。
      WaitForSingleObject会使CurrentSource减1, ReleaseSemaphore会使CurrentSource+1.
      假设信标中有3可用资源,并且有5个线程在等待,那么就会有3个线程变为可调度,同时资源数减为0.那么还有2个线程仍在等待。此时,有个xx线程(或xx函数)准备好了一个可用资源,打算让等待中的线程处理该资源,那么此xx可以调用 ReleaseSemaphore(hSemaphore, 1, NULL); 来添加一个资源数。在这之后,等待中的一个线程立刻获得调度权限,信标内资源数再次减为0,剩余最后一个线程继续苦苦等待...
      注意事项:ReleaseSemaphore的第二个参数传入0将毫无效果,第三个参数也不会正确地返回原来的资源数量;

互斥对象(Mutex)
HANDLE CreateMutex(
   PSECURITY_ATTRIBUTES psa,
   BOOL fInitialOwner,
   PCTSTR pszName);


互斥对象的特别之处:
       互斥对象的结构中有一个当前归属的线程ID成员,和使用计数成员。当该对象归属于某一个线程(A)时(即WaitForXXObject成功),那么dwID就设置为调度线程A的ID,同时dwCount设置为1。如果该线程再次WaitForXX,那么函数会立即返回,因为是同一dwID嘛,但是dwCount会递增1。如果此时另外一个线程B也来凑热闹,那么会进入睡眠状态,直到A调用ReleaseMutex相应的次数,那么dwID将设置为B的id,且dwCount为1。也就是说只要dwID不为0,那么该对象绝对是unsigned的;如果为0,那么该对象必定是signed的。
        当一个线程拥有了互斥对象,但是却意外终止了(例如TerminateThread --),没有来得及调用ReleaseMutex,那么系统将视为该对象已经被释放,其他等待的线程之一将获得调度,只不过WaitForxx返回值是WAIT_ABANDONED,表明上一个线程是非正常中止的,你(此线程)看着办吧。
        在Windows中使用互斥锁可以有效的避免死锁。当持有Mutex的线程在调用ReleaseMutex前意外退出,如ExitThread和TerminateThread。系统会把Mutex分给等待Mutex的线程中的一个并返回Wait_Abandoned,从而避免了死锁


其它好用的函数
1.WaitForInputIdle:传递一个进程句柄给它,那么就可以等待直到该进程创建完毕,显示了窗口,并且等待用户进行输入——这太有用了,当你的程序创建了一个子进程的时候,很可能需要知道子进程何时完全创建完毕,有了它,一切变得那么简单!
2.MsgWaitForMultipleObjects(Ex): 这两个函数可以用在同时处理消息的线程中,这样当句柄们都signed或者来了消息时都可以返回.具体和时返回取决于dwWakeMask参数~;
3.WaitForDebugEvent:等待调试事件,估计只有开发调试器时才可能用得到吧;
4.SignalObjectAndWait:该函数允许同时执行2件事:第一件事是把hObjectToSignal 置为signed,第二件事是等待hObjectToWaitOn。

posted @ 2009-06-17 10:31  辛勤耕耘  阅读(585)  评论(0编辑  收藏  举报