Windows Via C/C++ 读书笔记 5 用户模式的线程同步

Windows Via C/C++ 读书笔记 

1. 用户模式的线程同步

1.1. 原子操作函数

Windows提供几个原子操作函数:

//给变量做加减操作

InterlockedExchangeAdd

InterlockedExchangeAdd64

//修改变量值,返回变量原先值

InterlockedExchange

InterlockedExchange64

InterlockedExchangePointer

//如果变量当前值等于参数3,那么修改该变量为参数1

InterlockedCompareExchange

InterlockedCompareExchangePointer

InterlockedCompareExchange

//加减1

LONG InterlockedIncrement(PLONG plAddend);

LONG InterlockedDecrement(PLONG plAddend);

//InterlockedExchange实现的And OR XOR 操作

InterlockedAnd64

这些操作保证是原子性的,不要对变量加锁,适合非常短小的锁操作。比较简单,不多说了。

主要注意的是用InterlockedExchange实现Spinlock(自旋锁)。代码如下:

BOOL g_fResourceInUse = FALSE; ...

void Func1() {

   // Wait to access the resource.

   while (InterlockedExchange (&g_fResourceInUse, TRUE) == TRUE)

      Sleep(0);

   // Access the resource.

   ...

   // We no longer need to access the resource.

   InterlockedExchange(&g_fResourceInUse, FALSE);

}

InterlockedExchange修改一个变量,并返回之前的变量。G_fResourceInUse是锁标志。只有它从FALSE变为TRUE,才说明当前线程锁定成功。

Sleep代码段可以加上次数累加操作,使线程尝试一定次数后能失败退出,否则线程可能不停轮询CPU,非常耗资源。

需要注意:

1. 还有点要注意,要保证使用这个代码的所有线程优先级一样。否则容易出现某个高优先级线程一直处在轮询而又获取不到锁的状态,死锁。

2. 避免在单CPU系统用自旋锁

3. 保证锁变量和访问变量在不同的cache lines 。具体原因不懂,可能CPU需要来回读内存变量,而不能直接访问缓冲里的变量,造成效率下降吧。

1.2. Cache Lines

Cache LineCPU缓冲(cache)的最小单位。它的大小通常为3264128字节。CPU读内存的时候,会把变量和变量相近的内存块读到一个cache line里面。然后在缓冲区中读写内存,不会马上写入内存中。

可以想象,如果多个CPU都把同一个内存读入到缓冲区,必然造成CPU(线程)间同步问题。芯片设计者已经处理了这种情况,会对缓冲区加锁,但是会带来效率的下降。

因此,要尽量避免变量会被读入到不同的cache line

方法:

GetLogicalProcessorInformation 获取cache line的大小

把变量内存大小用cache line大小对齐

把读写变量和只读变量分开到不同的cache line

错误代码:

struct CUSTINFO {

   DWORD    dwCustomerID;     // Mostly read-only

   int      nBalanceDue;      // Read-write

   wchar_t  szName[100];      // Mostly read-only

   FILETIME ftLastOrderDate;  // Read-write

};

修改后代码:

#define CACHE_ALIGN 64

// Force each structure to be in a different cache line.

struct __declspec(align(CACHE_ALIGN)) CUSTINFO {

   DWORD    dwCustomerID;     // Mostly read-only

   wchar_t  szName[100];      // Mostly read-only

   // Force the following members to be in a different cache line.

   __declspec(align(CACHE_ALIGN))

   int nBalanceDue;           // Read-write

   FILETIME ftLastOrderDate;  // Read-write

};

如果你把变量都设计为线程的本地变量,那么就不存在上面的问题。除非你用了OpenMP之类的多核编译指令,把代码片拆分到不同的线程执行。

1.3. 临界区Critical Section

用一个结构记录了这个变量的访问情况。

有几点注意:

调用前,必须对临界变量做初始化

同一个线程对一个已经获取访问权的临界变量再次调用EnterCriticalSection,临界结构的计数会加一,可以实现递归锁操作。 

其它都是以前就知道的东西,不说了。

1.3.1. Critical Section 与 Spinlock

线程进入wait状态是需要从用户模式切到系统模式,非常耗CPU。因此Windows把自旋锁spinlock加入到了EnterCriticalSection 中,操作系统会先用spinlock尝试一定次数获取资源,失败后才会把线程切到"wait"状态。尝试次数通过InitializeCriticalSectionAndSpinCount函数设定

初始化"critical section"需要分配内存,"enter"一个"critical section"可能会创建一个"event"内核对象,如果内存不足,他们会失败。后一个情况可以通过设定足够大小的spinlock尝试次数来避免创建"event"对象。程序可以等待一段时间,当内存足够时再尝试访问。

1.4. Slim 读写锁/Slim Reader-Writer Locks

Vista新增了一个同步原语,提供了读写锁的支持。读写锁是什么,简单讲就是允许同时有多个读,但是只能有一个写。为什么允许,找本事务的书或者数据库的书看看就知道了。

Windows下面也有一些读写锁的实现,比如Ice库中的读写锁类。应该是通过读写计数来实现。

1.5. SleepConditionVariableCS

另外一个Vista增加的同步操作。等待一个变量条件,感觉和WaitForSingleObject差不多,没看出什么好处。不在Vista下编程,不太清楚。

关于这个API实现"生产者-消费者模式"要注意"骗醒"问题,见http://www.cnblogs.com/panda_lin/articles/1449139.html

最近有点忙,很多e文文档需要看。这几天才看完一个chapter,看这个月能不能看完这本e书。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值