您的位置:首页 > 编程语言 > C语言/C++

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

2009-06-02 20:34 579 查看
Windows Via C/C++ 读书笔记 5

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 Line是CPU缓冲(cache)的最小单位。它的大小通常为32,64或128字节。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书。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐