学习笔记—多线程同步与互斥

来源:互联网 发布:软件随想录 txt 编辑:程序博客网 时间:2024/06/05 06:52
 

多线程编程是一个难点,不仅要考虑各个线程之间的同步和互斥问题,避免死锁,活锁和优先级反转;还要考虑对资源的释放,做到没有内存泄露就是最好的实现了。

还记得大学时候,操作系统课程介绍了进程的同步与互斥,用PV原语实现进程间的同步与互斥;当初复习考研的时候也做过很多的练习;前一段时间和最近一段时间也学习过这方面的知识;现在,当前工作中,项目用的就是多线程实现的,里面不说每种方法都涉及了的,至少涉及了两三种。

言归正传,总结一下多线程同步与互斥实现的一些方法和技术。这些讨论是基于windows平台。

一:单个进程之间多线程的同步技术

1.关键段——这个是在用户模式下的线程同步;

MS 定义了一个CRITICAL_SECTION数据结构,对外没有公开,在相应的winbase.h或者winnt.h可以找到定义。在内部也使用了Interlocked原子锁。使用起来很方便,但是,很容易造成死锁,因为在等待进入关键代码段时无法设定超时值。

初始化函数:VOID InitializeCriticalSection(PCRITICAL_SECTION pcs)//对pcs指向CRITICAL_SECTION进行初始化

释放关键段资源:VOID DeleteCriticalSection(PCRITICAL_SECTION pcs)

使用:在公共资源出使用 EnterCriticalSection(CRITICAL_SECTION g_cs);

使用完,一定要用LeaveCreaticalSection(CRITICAL_SECTION g_cs)表示对资源不再访问,其他线程可以对此资源进行访问。

2.Sim读/写锁——这个是在用户模式下的线程同步;

对于多个读取者和多个写入者,这个是最好的解决办法;MS定义了一个SRWLOCK结构体使用InitializeSRWLock(PSRWLOCK srwlock)对它进行初始化;Sim读/写锁不需要程序员对他进行释放,操作系统会自己释放锁使用的资源;

对写入者线程来使用AcquireSRWLockExclusive(PSRWLOCK srwlock)获取资源使用权,对资源进行更新后调用ReleaseSRWLock(PSRWLOCK srwlock)释放对共享资源的占用;

对读取者线程来说方法有一点小区别,它使用AcquireSRWLockShared(PSRWLOCK srwlock)获得对共享资源的访问,多个读取者(对于多CPU就是实际上的 “同时”)可以同时对共享资源进行读(他们并不会破坏数据完整性);调用ReleaseSRWLockShared(PSRWLOCK srwlock)解除对资源的访问权。

3.条件变量——这个是在用户模式下的线程同步;

对于多个读取者和多个写入者,存在一种情况:当读取者没有数据可以读取的时候,应该将锁释放;同样,写入者写入的数据已满就应该释放锁并进入睡眠状态。

这种情况的解决就用到了条件变量:通过SleepConditionVariableCS或者SleepConditionVariableSRW设置条件变量;当调用线程(读取者检测到有数据的时候)检测到条件满足时,会通过调用WakeConditionVariable或者WakeAllConditionVariable,将阻塞的线程(写入者阻塞自己)唤醒。

三种线程同步的方法各有利弊,sim读写锁的效率高于关键段;所以优先采用sim读写锁,不适用的话可以采用关键段。

 

二.多个进程之间同步互斥,采用内核对象实现线程同步;这种方法实现同步会比较慢,CPU花很多时间在用户模式和内核模式进行切换。

1.事件

用CreateEvent(psa, bool, bool, pszname)创建事件内核对象,第一个参数是安全属性,第二个是手动还是自动重置事件,第三个表示是将事件初始化为触发还是触发状态,最后一个对象的名称。当不需要关注事件对象的句柄时,要用CloseHandle将其关闭。

使用:SetEvent设置成触发状态;ResetEvent把事件变成未触发状态。

其他进程的线程可以通过调用OpenEvent(DEWORD,bool, pszname) 访问事件内核对象。

2.可等待计时器

用CreateWaitableTimer(psa,   bool,   pszname)同样,第二个参数表示是手动还是自动重置事件。再创建的时候总是处于未触发的状态,必须调用SetWaitableTimer通知可等待计时器何时触发,SetWaitableTimer和SetTimer很像,每个一定的时间触发一次,但是可以设置触发一次等等。

使用CancelWaitableTimer取消计时器。

3.信号量

和其他的内核对象一样,包含了一个使用计数,同时还包含了:一个最大资源计数和一个当前资源数;分别是第三个参数和第二个参数;用CreateSemaphore(psa, long, long, pszname),其他进程可以通过OpenSemaphore访问一个已经存在的信号量内核对象。

使用:等待函数(WaitSingleObject)传入信号量的句柄,此时,OS会检测信号量的当前资源计数,如果大于零(有信号状态),等待函数会递减资源使用数;使用完后,通过调用ReleaseSemaphore来递增资源的使用数量。

4.互斥量

互斥量对象包含一个使用计数和线程ID和一个递归计数。使用CreateMutex(psa, bool, pszname)或者CreateMutexEx(psa,pszname, dwFlags,   dwDesiredAccess)具体怎么使用可以查MSDN。

使用等待函数WaitSingleObject来请求对资源的访问权,使用ReleaseMutex释放互斥量。

其他进程的线程可以通过OpenMutex访问一个已经创建了的互斥对象内核对象。由于互斥量有“线程所有权”的概念,操作系统会检测想要获得互斥量的线程ID与互斥量线程的线程ID,如果一致就会调度此线程,而不管互斥量是否处于有信号状态。

 

三.其他的实现线程同步的方法

WaitForInputIdle,MsgWaitForMutilpleObjects(Ex),WaitForDebugEvent,SigalObjectAndWait等等。

 

参考资料:孙鑫《深入详解MFC》,《Windows核心编程》第五版。