您的位置:首页 > 其它

Windows 线程同步

2014-06-29 17:53 169 查看
    当多个线程访问同一个共享资源时,为了保证数据的完整性,引入了线程同步机制。

一、用户模式下的线程同步
1、原子访问
    CPU平台支持原子访问。如果是x86系列CPU,那么Interlocked系列函数会在总线上维持一个硬件信号,这个信号会阻止其他CPU访问同一个内存地址。我们必须保证传给Interlocked系列函数的变量地址是经过对其的,否则这些函数可能会失败。
   Interlocked系列函数(包括普通的数值类型,普通的算术运算)
   还有对单项链表的栈操作:   InitializeSListHead,   InterlockedPushEntrySList,   InterlockedPopEntrySList,   ... ...

2、关键段 critical section 
    易用、快速(内部使用interlocked系列函数),但是无法在多个进程的线程间进行同步

    一个EnterCriticalSection函数必须有一个LeaveCriticalSection函数与之对应,否则一个线程会一直独占了某些资源,即使该线程结束之后,这些资源也被关键代码段锁定。而其他线程如果调用EnterCriticalSection进入该关键代码段是无法访问这些资源的。
 如果一个关键代码段已经被其他线程所拥有,那么如果当前线程试图进入这个关键代码段的时候,会立即被设置为等待状态。意味着该线程必须从用户模式转入内核模式,大约需要1000个CPU周期。这种转换是需要付出代价的。实际上,在多CPU计算机上,当前拥有资源的线程可能正执行在另一个CPU上,这样,它很可能会马上离开关键代码段,释放相关资源,所以可以使用关键段+自旋锁。
CRITICAL_SECTION,InitializeCriticalSetion
Entercriticalsection/ TryEnterCriticalSetion
Leavecriticalsection,Deletecriticalsection  
3、关键段和旋转锁
    先在用户态等待,然后再陷入内核等待,比关键段更快速
    如果两个或两个以上线程在同一时刻争夺同一个关键段,那么关键段会在内部使用一个事件内核对象。当内存不足的情况下,可能创建事件内核对象会失败,这个时候EnterCriticalSection会抛EXCEPTION_INVALID_HANDLE异常,这时候需要作一些特殊处理。要注意的情况是,如果在单CPU的计算机上,该函数的第二个参数dwSpinCount会被忽略,永远为0,因为在单CPU上,如果一个线程在循环尝试请求资源,而当前拥有资源的线程不可能被调度,资源是无法释放的。

InitializeCriticalSetionAndSpinCount
SetCriticalSetionSpinCount

4、读写锁
    适用于生产者消费者模型,它可以区分读取资源的线程和更新资源的线程
    SRWLock,InitializeSRWLock,AcquireSRWLockExclusive,ReleaseSRWLockExclusive

5、条件变量
    如果读取者线程没有数据可以读取,那么它应该将锁释放并等待,直到写入者线程产生了新的数据为止。如果用来接收写入者线程产生的数据结构已满,那么写入者同样应该释放SRWLock并进入睡眠状态,直到读取这线程把数据结构清空为止。当线程检测到相应的条件满足的时候(比如,有数据供读取者使用),它会调用
WakeConditionVariable 或  WakeAllConditionVariable,这样阻塞在Sleep*函数中的线程就会被唤醒。

     条件变量一般是配合其他同步机制使用的。例如在生产者线程离开关键段的时候,WakeAllConditionVariavble;当消费者进入关键段,发现没有东西可读时,可以使用SleepConditionVariable。
    SleepConditionVariableSRW,SleepConditionVariableCS,WakeConditionVariable, WakeAllConditionVariable
6、高速缓存行

    多CPU情况下,CPU的高速缓存行,可能会造成数据不完整性。我们可以利用CPU的这个特性,来组织数据结构,把数据和缓存行的边界对齐,把经常修改的数据和只读数据分开存放。

7、一些技巧
(1)以独占方式操作一组对象时使用一个锁,不要为每个对象都创建一个锁;
(2)同时访问多个逻辑资源,有多个锁时,一定要按照顺序来获取 锁;
(3)不要长时间占用锁;
二、使用内核对象进行线程同步

    WaitforXXX 副作用:一个“自动重置”的事件内核对象收到通知,转变为已通知状态的时候,最多只能唤醒“一个”等待在它上的线程。一个“人工重置”的事件内核对象收到通知,转变为已通知状态的时候,能够唤醒“所有”等待在它上的线程。
1、信号
CreateEvent
SetEvent 有信号
ResetEvent 无信号
palseEvent 先出发信号,马上回到无信号状态

2、计时器
    隔多长时间触发,内核对象变成触发状态。
CreateWaitableTimer,OpenWaitableTimer,CancleWaitableTimer

3、信号量
    如果当前资源计数大于0,那么信号量处于触发状态,=0,为未触发状态,系统绝对不会让当前资源计数变成0,当前资源计数绝对不会大于最大资源计数。releaseSemaphore会增加当前资源计数。
CreateSemaphoreEx
OpenSemaphore
ReleaseSemaphore

4、互斥量
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpsa,BOOL fInitialOwner ,LPTSTR
lpszMutexName);
    fInitialOwner表示创建互斥量的线程是否是该互斥量的最初拥有者。如果为TRUE,表示线程将拥有它所创建的这个互斥量,因此互斥量将处于无信号状态,只有创建该互斥量的线程此时可以访问互斥量保护的数据,而其它等待这个互斥量的线程都将被挂起,等待着互斥量变成有信号状态之后它们才能继续执行,否则就在那里等着,也就是在那里睡觉。lpszMutexName为所创建的互斥量起一个名字,这个名字用于在多个进程间共享互斥量。

    互斥量也是一个内核对象,它用来确保一个线程独占一个资源的访问。互斥量与关键段的行为非常相似,并且互斥量可以用于不同进程中的线程互斥访问资源。也能完美的解决某进程意外终止所造成的“遗弃”问题。

    互斥量是属于线程的,如果使用ExitThread或TerminateThread来强行退出线程,互斥量没有被释放,不过系统会将这个遗弃的互斥量交给正在等待这个互斥量的对象。

    CreateMutexEx,OpenMutex,ReleaseMutex

三、死锁检测
GetThreadWaitChain
OpenThreadWaitChainSession
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  线程同步