现在的位置: 首页 > 综合 > 正文

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

2018年04月13日 ⁄ 综合 ⁄ 共 5476字 ⁄ 字号 评论关闭

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

 

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

#if defined(WINCE) || defined(_WIN32_WCE)
#   define NX_OS_WINCE
#elif defined(WIN64) || defined(_WIN64) || defined(__WIN64__)
#   define NX_OS_WIN64
#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
#   define NX_OS_WIN32
#elif defined(__linux__) || defined(__linux)
#   define NX_OS_LINUX
#else
#   error "This OS is unsupported"
#endif

#if defined(NX_OS_WIN32) || defined(NX_OS_WIN64) || defined(NX_OS_WINCE)
#   define NX_OS_WIN
#else
#   define NX_OS_UNIX
#endif

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

#if defined(_MSC_VER)
#   if (_MSC_VER <= 1200)
#       define NX_CC_MSVC6
#   endif
#   define NX_CC_MSVC
#elif defined(__GNUC__)
#   define NX_CC_GNUC
#else
#   error "This CC is unsupported"
#endif

这里我们就只简单的考虑Windows、Linux,以及VC、gcc之间的区分。下面,我们需要定义跨平台的接口兼容层:

namespace atomic
{
#if defined(NX_OS_WIN32) || defined(NX_OS_WINCE)
    typedef volatile LONG   type_t;
    typedef LONG            norm_t;
#elif defined(NX_OS_WIN64)
    typedef volatile LONG64 type_t;
    typedef LONG64          norm_t;
#else
    typedef volatile long   type_t;
    typedef long            norm_t;
#endif
}

//////////////////////////////////////////////////////////////////////////

#if defined(NX_CC_GNUC)

#   define nx_atomic_inc(var)           __sync_fetch_and_add        (&(var), 1)
#   define nx_atomic_dec(var)           __sync_fetch_and_sub        (&(var), 1)
#   define nx_atomic_add(var, val)      __sync_fetch_and_add        (&(var), (val))
#   define nx_atomic_sub(var, val)      __sync_fetch_and_sub        (&(var), (val))
#   define nx_atomic_set(var, val)      __sync_lock_test_and_set    (&(var), (val))
#   define nx_atomic_cas(var, cmp, val) __sync_bool_compare_and_swap(&(var), (cmp), (val))

#elif defined(NX_OS_WIN32) || defined(NX_OS_WINCE)

#   define nx_atomic_inc(var)           InterlockedExchangeAdd      (&(var), 1)
#   define nx_atomic_dec(var)           InterlockedExchangeAdd      (&(var),-1)
#   define nx_atomic_add(var, val)      InterlockedExchangeAdd      (&(var), (val))
#   define nx_atomic_sub(var, val)      InterlockedExchangeAdd      (&(var),-(val))
#   define nx_atomic_set(var, val)      InterlockedExchange         (&(var), (val))
#   define nx_atomic_cas(var, cmp, val) ((cmp) == InterlockedCompareExchange(&(var), (val), (cmp)))

#elif defined(NX_OS_WIN64)

#   define nx_atomic_inc(var)           InterlockedExchangeAdd64    (&(var), 1)
#   define nx_atomic_dec(var)           InterlockedExchangeAdd64    (&(var),-1)
#   define nx_atomic_add(var, val)      InterlockedExchangeAdd64    (&(var), (val))
#   define nx_atomic_sub(var, val)      InterlockedExchangeAdd64    (&(var),-(val))
#   define nx_atomic_set(var, val)      InterlockedExchange64       (&(var), (val))
#   define nx_atomic_cas(var, cmp, val) ((cmp) == InterlockedCompareExchange64(&(var), (val), (cmp)))

#else
#   error "This platform is unsupported"
#endif

这里28、29行和37、38行并没有使用现成的InterlockedIncrement、InterlockedDecrement接口。是因为这里提供的接口均返回操作之前的原始值,而InterlockedIncrement、InterlockedDecrement接口返回操作之后的值。为了统一,此处使用InterlockedExchangeAdd来实现功能。考虑到宏的调试性和安全性不够好,我们可以使用函数对这些接口做一个简单的封装:

static inline atomic::norm_t atomic_inc(atomic::type_t& v)
{
    return nx_atomic_inc(v);
}
static inline atomic::norm_t atomic_inc(atomic::type_t& v, atomic::norm_t n)
{
    return nx_atomic_add(v, n);
}
static inline atomic::norm_t atomic_dec(atomic::type_t& v)
{
    return nx_atomic_dec(v);
}
static inline atomic::norm_t atomic_dec(atomic::type_t& v, atomic::norm_t n)
{
    return nx_atomic_sub(v, n);
}
static inline atomic::norm_t atomic_set(atomic::type_t& v, atomic::norm_t n)
{
    return nx_atomic_set(v, n);
}
static inline bool atomic_cas(atomic::type_t& v, atomic::norm_t c, atomic::norm_t n)
{
    return nx_atomic_cas(v, c, n);
}

下面,我们尝试使用这些原子操作实现一个简单的循环锁。要实现原子循环锁,还另外需要一个跨平台接口:

namespace thread
{
    static inline void yield(void)
    {
#   if defined(NX_OS_WINCE)
        Sleep(5);
#   elif defined(NX_OS_WIN)
        SwitchToThread();
#   else
        pthread_yield();
#   endif
    }
}

它的作用是切换线程操作,让当前线程放弃剩余的时间片,把时间片让给其他线程。那么循环锁可以简单的实现如下:

namespace atomic
{
    typedef type_t lock_t;

    class Locker
    {
    protected:
        lock_t& _lc;

    public:
        Locker(lock_t& lc) : _lc(lc)
        {
            while(atomic_set(_lc, 1))
                thread::yield();
        }
        ~Locker()
        {
            atomic_set(_lc, 0);
        }
    };

    typedef Locker locker_t;
}

当Locker::_lc为1时,即表示处于加锁状态;为0则表示没有加锁。当处于加锁状态时,atomic_set(_lc,
1)的返回值将永远是1,此时Locker的构造函数将被锁住,直到有其他持有同一个lock_t对象的Locker析构并释放锁。为了方便的使用它,我们可以为它写一些便捷的接口:

class AtomicLock
{
protected:
    atomic::lock_t& get_lock(void)
    {
        static atomic::lock_t lc;   // 这里的实现是粗糙的
                                    // 若在实际项目中运用,需使用线程安全的单例
        return lc;
    }
};

#ifndef nx_atomic_lock
#define nx_atomic_lock() nx::atomic::locker_t locker(get_lock())
#endif

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

class RefBase : AtomicLock
{
public:
    void opt(void)
    {
        nx_atomic_lock();
        // ...
    }
};

只需要让我们的类继承自AtomicLock,然后这个类就可以使用nx_atomic_lock锁住某一段操作了。上面的原子锁虽然简单方便,但是还有两大缺点:

  • 1. 使用锁除了定义lock_t之外,还必须要使用Locker,lock_t自身不具备锁功能
  • 2. 锁是不可重入的,即递归中不能使用此锁

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

namespace thread
{
#if defined(NX_OS_WIN)
    typedef DWORD     type_t;
#else
    typedef pthread_t type_t;
#endif

    static inline type_t current(void)
    {
#   if defined(NX_OS_WIN)
        return GetCurrentThreadId();
#   else
        return pthread_self();
#   endif
    }
}

作用是获得当前线程的id。接下来,我们需要实现一个可重入的lock_t:

namespace atomic
{
    class Lock : NonCopyable
    {
    protected:
        long   _rc;
        type_t _id;

    public:
        Lock(void) : _rc(0), _id(0) {}
        ~Lock() { nx_assert(_rc <= 0); }

    public:
        void lock()
        {
            thread::type_t tid = thread::current();
            if (_id == (norm_t)tid)
                ++_rc;
            else
            {
                while(!atomic_cas(_id, 0, (norm_t)tid))
                    thread::yield();
                _rc = 1;
            }
        }
        void unlock()
        {
            if (_id == (norm_t)thread::current())
                if (--_rc <= 0)
                {
                    atomic_set(_id, 0);
                    _rc = 0;
                }
        }
    };

    typedef Lock   lock_t;

    class Locker
    {
    protected:
        lock_t& _lc;

    public:
        Locker(lock_t& lc) : _lc(lc) { _lc.lock(); }
        ~Locker()                    { _lc.unlock(); }
    };

    typedef Locker locker_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

更多内容请访问:http://darkc.at

抱歉!评论已关闭.