跨平台的原子操作及简单的循环锁实现

/ 0评 / 0

Published by orzz.org(). (https://orzz.org/%e8%b7%a8%e5%b9%b3%e5%8f%b0%e7%9a%84%e5%8e%9f%e5%ad%90%e6%93%8d%e4%bd%9c%e5%8f%8a%e7%ae%80%e5%8d%95%e7%9a%84%e5%be%aa%e7%8e%af%e9%94%81%e5%ae%9e%e7%8e%b0/)

原子操作一直是多线程编程中的重要杀器之一。Win32里我们有Interlocked系列API,其他平台下也有各自的原子操作接口。如果想要让我们的程序能够拥有跨平台且统一的多线程调度方案,那么就必须得把不同的操作接口统一(C++11中已经有了跨平台的原子操作接口,不过当不方便使用C++11的时候,自己简单的写一套还是有一定需要的)。

首先,我们需要定义一套平台判断宏,来方便我们决定使用何种接口:

考虑到编译的差异(gcc下提供了一套跨平台的原子操作接口),编译器也需要区分开:

这里我们就只简单的考虑Windows、Linux,以及VC、gcc之间的区分。

下面,我们需要定义跨平台的接口兼容层:

这里28、29行和37、38行并没有使用现成的InterlockedIncrement、InterlockedDecrement接口。是因为这里提供的接口均返回操作之前的原始值,而InterlockedIncrement、InterlockedDecrement接口返回操作之后的值。为了统一,此处使用InterlockedExchangeAdd来实现功能。

考虑到宏的调试性和安全性不够好,我们可以使用函数对这些接口做一个简单的封装:

下面,我们尝试使用这些原子操作实现一个简单的循环锁。

要实现原子循环锁,还另外需要一个跨平台接口:

它的作用是切换线程操作,让当前线程放弃剩余的时间片,把时间片让给其他线程。

那么循环锁可以简单的实现如下:

当Locker::_lc为1时,即表示处于加锁状态;为0则表示没有加锁。当处于加锁状态时,atomic_set(_lc, 1)的返回值将永远是1,此时Locker的构造函数将被锁住,直到有其他持有同一个lock_t对象的Locker析构并释放锁。

为了方便的使用它,我们可以为它写一些便捷的接口:

我们可以像下面这样使用它:

只需要让我们的类继承自AtomicLock,然后这个类就可以使用nx_atomic_lock锁住某一段操作了。

上面的原子锁虽然简单方便,但是还有两大缺点:

为了克服上面所述的缺点,我们还需要再定义一个接口:

作用是获得当前线程的id。

接下来,我们需要实现一个可重入的lock_t:

lock_t提供了lock和unlock接口,内部通过判断当前线程id,并使用计数器来支持可重入。
代码第21行,使用一个CAS操作判断是否有其他线程已加锁,若未加锁,那么将_id设为当前的线程id。

使用上面的代码,我们前面写的AtomicLock和nx_atomic_lock均不需要调整,使用方法也和原来一致。但此时locker_t已可以独立于Locker等辅助类单独存在,并在我们需要的时候手动加锁解锁。


文中代码请参考:
http://neonx.googlecode.com/svn/trunk/neoncore/platform/atomic.h
http://neonx.googlecode.com/svn/trunk/neoncore/thread/locker.h

Published by orzz.org(). (https://orzz.org/%e8%b7%a8%e5%b9%b3%e5%8f%b0%e7%9a%84%e5%8e%9f%e5%ad%90%e6%93%8d%e4%bd%9c%e5%8f%8a%e7%ae%80%e5%8d%95%e7%9a%84%e5%be%aa%e7%8e%af%e9%94%81%e5%ae%9e%e7%8e%b0/)

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据