引子:
这几天在写一个windows phone平台上的service,由于Windows phone 的内核是基于Windows NT的,这也意味着写Windows Phone的Service代码与Win32更接近了。
虽然功能很少,但是一旦release出去,就像泼出去的水,如果我的代码出了Bug,用户只有更新固件才能获取更新,压力还是挺大。
主要的担心点是线程同步:
代码注册了一个系统的事件,当系统触发事件时,会调用事件相应的callback函数,而此callback与main线程不在同一个线程,自然引发了线程同步的问题。
索性再次翻开《Windows via C++》,复习一下线程同步的基础知识,写完代码与老外讨论了一下,还存在一些细节问题,顺手记录一下,以便巩固和回顾。
线程同步的本质在于保证某一个变量在被多个线程访问时仍然保持一致性。
用户模式下的线程同步:
用户模式下有以下几种同步的方式:
- Interlocked系列函数。因为代码执行的原子操作是一条CPU指令而不是一句代码,一句 a++ 都是由几条指令组成的。InterlockedXXX的一系列函数能够保证对资源的操作是原子性的。
- CRITICAL_SECTION结构体,可以用InitializeCriticalSection()和DeleteCriticalSection()函数来初始化和删除结构体,而把需要同步的代码放在EnterCriticalSection()与LeaveCriticalSection()之间。
- SRWLock,用于区分读取资源和写入资源的线程,因为多个读取线程同时访问一个资源是线程安全的。
另外要注意的是:
- volatile关键字保证CPU从内存中读取数据而不是从寄存器中读取数据。
- CRITICAL_SECTION能保证操作时不会有其他线程进入,当然自己也不会重入。
- 不要长时间占用锁,在EnterCriticalSection中时间尽量保持到最少。
使用内核对象进行线程同步:
内核对象包括:进程,线程,文件,事件,timer,信号量,互斥量等。并且有触发/未触发 两种状态。内核对象可以用Handle类型表示。
- WaitForSingleObject(handle, Time) 可以让线程等待到第一个参数指定的内核对象被触发过,或者到达第二个参数指定的超时时间,可以将时间设置为INFINITE让线程无限等待直到内核对象状态变化,而且,不占用CPU时间,此时的线程状态是Wait,而不是Ready。
- WaitForMultiObjects(),可以等待多个内核对象的状态变化。
- 通常可以让WaitForSingle/MulitObject来等待事件对象,可以用CreateEvent()来创建一个事件的内核对象,可以通过SetEvent()改变事件的状态,使用ResetEvent()重置事件状态。
- 可等待的timer内核对象,可以用CreateWaitableTimer创建,使用SetWaitableTimer来触发。
- 信号量,可以对资源进行计数。使用CreateSemaphore创建并设置最大计数,使用ReleaseSemaphore来减少资源计数。
- 互斥量,确保一个线程独占一个资源。使用CreateMutex创建,使用ReleaseSemaphore来释放。它比CRITICAL_SECTION性能差但可以跨进程。
要注意的是:所有内核对象使用完成后都应该用CloseHandle关闭
这基本是跟Win32平台的写法一致了,只是手机的资源更紧张,要时刻考虑到电量的消耗。