《Windows核心编程》第二部分(用户模式下的线程同步)

(5)用户模式下的线程同步

线程必须访问系统资源,如果一个线程独占了对一个资源的访问,那么其他线程就需要做同步处理。必须限制线程对已经被占有的共享资源的访问。比如一个线程在对一个内存进行占有时其他线程就要做同步处理,限制其他线程占有此内存。

线程之间的通信可以让多个线程约定访问一个线程,同时不破坏资源的完整性;一个线程可以通知其他线程某项任务已完成。

线程同步的windows下的几种方法:
(1)原子访问:保证一块资源同时只有一个线程访问。a++;就是一个很好的例子。a++;操作实际上分三步是先取出然后增加然后在放回内存。加上原子操作可以让一个线程执行a++;时其他线程不会在上一个线程加一的结果还没有放入内存时就再次取出a的值,然后导致结果不对。
解决方法可以使用Interlocked系列函数,这些函数可以以一个原子方式操控一个值。

//plAddend需要加的变量,lIncrement加的数量,做减法传入负值即可
LONG InterlockedExchangeAdd(PLONG volatile plAddend,LONG lIncrement);  
LONGLONG InterlockedExchangeAdd64(PLONGLONG volatile pllAddend,LONGLONG llIncrement);
//只是默认给加数加一
InterlockedIncrement(LONG volatile* Addend)
//这系列函数相当于常见的赋值操作(把一个变量的值赋给另一个变量)
//第一个参数所指向的内存地址的当前值以原子方式替换为第二个参数指定的值
//函数都返回原来的值
LONG InterlockedExchange(PLONG volatile plTarget,LONG lValue);
LONGLONG InterlockedExchange64(PLONGLONG volatile plTarget,LONGLONG lValue);
//原子地用于替换地址值,32位和64位可以自动转化
PVOID InterlockedExchangePointer(PVOID *volatile ppvTarget,PVOID pvValue);
//可以用来实现旋转锁
//假设use是某个资源是否被使用的一个标志
//在多个线程中While循环不停的执行,把use的值设为true并检查原来的值是否为true。
//如果原来的值为false,则说明资源尚未被使用。于是将其设为使用中。
//如果原来的值是true则表明有其他线程正在使用该资源,调用sleep放弃该时间片的调度。
//一般会规定循环的次数,保证不会一直请求获取资源
//在单CPU上使用循环锁没有意义
Bool use=false;
Void func()
{
   While(InterlockedExchange(&use,true)==true)
     Sleep(0);
//........
InterlockedExchange(&use,false);
}
//一组比较后赋值的互锁函数。
//plDestination和lCompared比较,如果相等则将plDestination改为lExchange的值
//如果不相等则返回plDestination的值
//以上操作都是原子操作
PVOID InterlockedCompareExchange(PLONG plDestination,LONG lExchange,LONG lCompared);
PVOID InterlockedCompareExchangePointer(PVOID* ppvDestination,PVOID pvExchange,PVOID pvCompared);

每个CPU上都有一个高速缓存行,一个CPU要获取数据时并不会直接去内存获取,而是先搜索高速缓存行,提高性能。在多CPU的情况下,这种方式就有可能会损伤性能,因为如果一个内容同时被加载到多个CPU的高速缓存行,当一个CPU变化了该缓存时,会通知其他CPU上的相应数据失效。

为了提高性能,根据高速缓存的大小来将应用程序的数据组织在一起,将数据与缓存行的边界对齐。并把只读数据与可读写数据分别存放。比如

struct test{
	DWORD id;//只读
	int name;//读写
	char szName[100];//只读
	FILETIME data//读写
}
应变为
struct test{
	DWORD id;//只读
	char szName[100];//只读
	int name;//读写
	FILETIME data//读写
}

GetLogicalProcessorInformaiton函数可以获得cpu高速缓存行的大小,_declspec(align(#))指示符来对字段对齐加以控制。

volatile关键字告诉编译器这个变量可能会被应用程序之外的其他东西修改,声明了这个属性的变量,不会对此变量进行任何优化,始终从内存读入此数据。对一个结构加上volatile限定符等于给结构中的所有成员都加入volatile限定符
(2)关键段
关键段是一小段代码,它在执行之前需要独占对一些共享资源的访问权。总之关键段就是限制一段代码只能同时由一个线程占有执行。

要使用关键段首先需要定义CRITICAL_SECTION结构。然后把任何需要共享的代码放在EnterCriticalSection和LeaveCriticalSection之间。

Int g_a=0; 
//关键端的标志
CRITICAL_SECTION cs;  
DWORD WINAPI ThreadProc1(PVOID)  
{  
//进入关键端将cs标志为已占用
EnterCriticalSection(&cs);  
for(int i=0;i<100;i++)  
  g_a++;  
//退出关键端将cs标志为已释放,忘记调用则会一直占用
LeaveCriticalSection(&cs);  
 return 0;  
}  
DWORD WINAPI ThreadProc2(PVOID)  
{  
EnterCriticalSection(&cs);  
 for(int i=0;i<100;i++)  
  g_a++;  
LeaveCriticalSection(&cs);  
return 0;  
}  

一个CRITICAL_SECTION只能对应一个关键段,如果想要同时维护多个关键段可以定义多个CRITICAL_SECTION来进行标志。

使用关键段建议:
(1)对于多个没有关系的数据结构最好使用多个CRITICAL_SECTION来进行标志,以便让数据可以及时的获得利用。而不需要对不相关的数据结构操作完后才可以使用。
(2)同时访问多个资源,但是要注意多个线程中要按照相同的顺序对资源进行访问。
(3)不要长时间运行关键代码段。

关键段缺点是不能在多进程之间对线程进行同步。而信号量和事件则可以。

使用关键段有两个必要条件:
第 一:想要访问资源的线程必须知道用来访问资源的CRITICAL_SECTION结构的地址。
第二:在任何线程访问被保护的资源之前,要使用VOID InitializeCriticalSection(PCRITICAL_SECTION pcs); 对CRITICAL_SECTION进行初始化。也可使用InitializeCriticalSectionAndSpinCount(PCRITICAL_SECTION pcs,DWORD dwSpinCount)来进行初始化可以使关键段可以使用循环锁,第二个参数表示循环获取的次数。

在不需要访问共享资源时可以调用以下函数来清理CRITICAL_SECTION结构VOID DeleteCriticalSection(PCRITICAL_SECTION pcs);

TryEnterCriticalSection函数可以检查当前线程是否可以调用此资源,如果资源被占用返回false可以则返回true,防止资源占用当前线程一直等待。
(3)读写锁(RWLock)
可以支持多个线程同时读,但是写入的话会堵塞所有的读写操作。

(1)读写锁之前,需要分配一个SRWLOCK结构,并调用InitializeSRWLock初始化它
Void InitializeSRWLock(PSRWLOCK SRWLock);
(2)写入者线程可以调用AcquireSRWLockExclusive,将SRWLock对象地址传入,获得对被保护资源的独占访问
Void AcquireSRWLockExclusive(PSRWLOCK SRWLock);
(3)独占访问结束后,需要调用ReleaseSRWLockExclusive解除对资源的独占。
Void ReleaseSRWLockExclusive(PSRWLOCK SRWLock);
(4)对于读取线程可以通过AcquireSRWLockShared获得共享锁,ReleaseSRWLockShared释放锁
Void AcquireSRWLockShared(PSRWLOCK SRWLock);
Void ReleaseSRWLockShared(PSRWLOCK SRWLock);  

SleepConditionVariableCS或SleepConditionVariableSRW函数,阻塞当前线程,等待条件变量

// pConditonVariable指向一个以初始化的条件变量,调用线程将等待该条件变量
//第二个参数指向一个关键段或是SRWLock对象。该关键段或SRWLock用来同步对共享资源的访问
//Flags指定一旦条件变量被触发,线程将以何种方式获得锁
//dwMilliseconds表示等待的最大时间
	Bool SleepConditionVariableCS(
  PCONDITION_VARIABLE pConditionVariable,  
  PCRITICAL_SECTION pCriticalSection,  
  DWORD dwMilliseconds);  
Bool SleepConditionVariableSRW(  
  PCONDITION_VARIABLE pConditionVariable,  
  PSRWLOCK pSRWLock,  
  DWORD dwMilliseconds  
  ULONG Flags);

它会调用WakeConditionVariable或WakeAllConditionVariable,触发条件变量。这样调用Sleep*函数而阻塞在该条件变量的线程就会被唤醒。

Void WakeConditonVariable(
   PCONDITION_VARIABLE ConditionVariable);
Void WakeAllConditionVariable(
   PCONDITION_VARIABLE ConditionVariable);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值