再论 无锁数据结构 (上)

来源:互联网 发布:linux man命令命令详解 编辑:程序博客网 时间:2024/05/19 18:17

看过 Andrei Alexandrescu 老师的 《锁无关的(Lock-Free)数据结构》

和 《锁无关的数据结构与Hazard指针》两篇文章,感觉受益颇深

前文给出一个 WRRMBNTM (Write-Rarely-Read-Many-But-Not-Many,少写多  读,但不是太多)的map

后文给出一个 Hazard 指针彻底解决这个问题,真正实现 WRRM 的map

不过看他的解法好像有点复杂,看了半天才明白,原来是实现了一种垃圾回收机制(里面的Hazard指针非常值得借鉴)

还要使用线程局部存储 为啥C语言解决一个资源释放的问题要这么麻烦?

本人经过分析和实验,给出如下解决方案:

 

具体思想是在对象还能被全局访问到时使用全局的那个引用计数,在对象不能被全局访问到(只能被正在读取它的线程访问到)时

使用对象备份的引用计数,引用计数降为0则销毁对象。

以下是具体的实现代码和测试程序(windows下编写,使用的Interlocked API)

 


#include "stdafx.h"
#include <windows.h>
#include <winbase.h>
#include <process.h>
#include <stdio.h>

//对象的引用
struct object
{
    void* pAnyRes;    //point to any resource etc list,map...
    int    nref_count;
    int nTmpRelease;    //对象临时使用的,释放计数,为解决 写线程意外死亡造成的 活锁问题
};
typedef struct object object;

//对象的拥有者
struct owner
{
    union
    {
        struct
        {
            object* pobject;
            int nref_count;
        };
        __int64 nobj_count;
    };
};
typedef struct owner owner;

#ifdef _DEBUG
LONG g_nNewCount = 0;
LONG g_nDelCount = 0;
#define DEBUG_INC( X )    InterlockedIncrement( (volatile long*)&X)
#else
#define DEBUG_INC( X )    
#endif

#define INVALID_REF_COUNT    (-100)    //标识一个无效的引用计数
//全局的引用者
owner    g_owner;
//获得对象指针
object* Acquire()
{
    owner stuOld,stuNew;
    do
    {
        stuOld.pobject = g_owner.pobject;
        stuOld.nref_count = g_owner.nref_count;
        
        stuNew.pobject = stuOld.pobject;
        stuNew.nref_count = stuOld.nref_count + 1;
        
    } while ( stuOld.nobj_count != InterlockedCompareExchange64( (LONGLONG volatile*)&g_owner.nobj_count , stuNew.nobj_count , stuOld.nobj_count ) );
    return stuNew.pobject;
}

void Release( object* pobject )
{
    bool bReleaseInOwner = (pobject == g_owner.pobject);
    if( bReleaseInOwner )//当前对象还是当初访问的对象
    {
        owner stuOld,stuNew;
        owner stuTest;
        do
        {
            stuOld.pobject = stuNew.pobject = pobject;
            stuOld.nref_count = g_owner.nref_count;
            stuNew.nref_count = stuOld.nref_count - 1;
            stuTest.nobj_count = InterlockedCompareExchange64( (volatile __int64*)&g_owner , stuNew.nobj_count , stuOld.nobj_count );
            if( stuTest.nobj_count == stuOld.nobj_count )
            {
                if( stuNew.nref_count == 0 )
                {
                    DEBUG_INC( g_nDelCount );
                    delete pobject;
                }
                break;
            }
            else if( stuTest.pobject != stuOld.pobject )//原对象已被更新
            {
                bReleaseInOwner = false;
                break;
            }
        } while ( true );
    }
    
    if( !bReleaseInOwner )
    {
        bool bReleaseObject = true;
        if( pobject->nref_count == INVALID_REF_COUNT )
        {
            object stuOldObj,stuNewObj,stuTestObj;
            do
            {
                stuOldObj.nref_count = INVALID_REF_COUNT;
                stuOldObj.nTmpRelease = pobject->nTmpRelease;
                stuNewObj.nref_count = stuOldObj.nref_count;
                stuNewObj.nTmpRelease = stuOldObj.nTmpRelease + 1;
                __int64 llOld = *(__int64*)&stuOldObj.nref_count;
                __int64 llNew = *(__int64*)&stuNewObj.nref_count;
                __int64 llTest = InterlockedCompareExchange64( (volatile __int64*)&pobject->nref_count , llNew , llOld );
                if( llTest == llOld )
                {
                    bReleaseObject = false;
                    break;
                }
                else
                {
                    *((__int64*)&(stuTestObj.nref_count)) = llTest;
                    if( stuTestObj.nref_count != stuOldObj.nref_count )//对象的引用计数已经更新
                    {
                        break;
                    }
                }
            } while ( true );
        }
        if( bReleaseObject )
        {
            long nRet = InterlockedDecrement( (volatile long*)&pobject->nref_count );
            if( nRet == 0 )
            {
                DEBUG_INC( g_nDelCount );
                delete pobject;
            }
            else if( nRet < 0 )
            {
                DEBUG_INC( g_nDelCount );
                delete pobject;    //must error
            }
        }
    }
}

bool Update( object* pOld , object* pNew )
{
    owner stuOld,stuNew;
    owner stuTest;
    bool bret = false;
    do
    {
        stuOld.pobject = pOld;
        
        stuNew.pobject = pNew;
        stuNew.nref_count = 1;
        pNew->nref_count = INVALID_REF_COUNT;
        pNew->nTmpRelease = 0;
        
        stuOld.nref_count = g_owner.nref_count;
        
        InterlockedCompareExchange64( (volatile __int64*)pOld , stuOld.nobj_count , stuOld.nobj_count );
        
        stuTest.nobj_count = InterlockedCompareExchange64( (volatile __int64*)&g_owner , stuNew.nobj_count , stuOld.nobj_count );
        if( stuTest.nobj_count == stuOld.nobj_count )
        {
            bret = true;
            //复制引用计数
            object stuOldObj,stuNewObj,stuTestObj;
            do
            {
                stuOldObj.nref_count = pOld->nref_count;
                stuOldObj.nTmpRelease = pOld->nTmpRelease;
                stuNewObj.nref_count = stuOld.nref_count - stuOldObj.nTmpRelease;    //注意,这里更新
                stuNewObj.nTmpRelease = 0;
                __int64 llOld = *(__int64*)&stuOldObj.nref_count;
                __int64 llNew = *(__int64*)&stuNewObj.nref_count;
                __int64 llTest = InterlockedCompareExchange64( (volatile __int64*)&pOld->nref_count , llNew , llOld );
                if( llTest == llOld )
                {
                    *((__int64*)&(stuTestObj.nref_count)) = llTest;
                    if( stuNewObj.nref_count == 0 )//实际上是不可能的,因为在Update的时候 WriteThread自己还保留着1个,全局还有1个至少2个
                    {
                        delete pOld;
                    }
                    break;
                }
            } while (true);
            break;
        }
        else if( stuTest.pobject != stuOld.pobject )
        {
            break;
        }
    } while ( true );
    return bret;
}

UINT CALLBACK ReadThread( void* pCount )
{
    UINT uCount = (UINT)pCount;
    do
    {
        object* pObject = Acquire();
        // ... do some read on pObject
        Release( pObject );
        uCount --;
    } while ( uCount > 0 );
    return 0;
}

UINT CALLBACK WriteThread( void* pCount )
{
    UINT uCount = (UINT)pCount;
    do
    {
        object* pObject = NULL;
        object* pNew = NULL;
        do
        {
            if( pNew != NULL )
            {
                DEBUG_INC( g_nDelCount );
                delete pNew;
            }
            if( pObject != NULL )
            {
                Release( pObject );
            }
            pObject = Acquire();
            DEBUG_INC( g_nNewCount );
            pNew = new object;
            // ... do some modify on pNew
        } while ( !Update( pObject , pNew ) );
        if( pObject != NULL )
        {
            Release( pObject );    //一次是自己的
            Release( pObject );    //一次是全局的那次
        }
        uCount --;
    } while ( uCount > 0 );
    return 0;
}

int main(int argc, char* argv[])
{
    int nSize = sizeof(object);
    DEBUG_INC( g_nNewCount );
    g_owner.pobject = new object;
    g_owner.pobject->nTmpRelease = 0;
    g_owner.nref_count = 1;
#define READ_COUNT    10000000
#define WRITE_COUNT    1000000
#define THREAD_COUNT    4    
    HANDLE pWaitHandle[THREAD_COUNT];
    UINT uThreadID = 0;
    pWaitHandle[0] = (HANDLE)_beginthreadex( NULL , 0 , ReadThread , (void*)READ_COUNT , 0 ,  &uThreadID );
    pWaitHandle[1] = (HANDLE)_beginthreadex( NULL , 0 , ReadThread , (void*)READ_COUNT , 0 ,  &uThreadID );
    pWaitHandle[2] = (HANDLE)_beginthreadex( NULL , 0 , WriteThread , (void*)WRITE_COUNT , 0 ,  &uThreadID );
    pWaitHandle[3] = (HANDLE)_beginthreadex( NULL , 0 , ReadThread , (void*)READ_COUNT , 0 ,  &uThreadID );
    WriteThread( (void*)WRITE_COUNT );
    WaitForMultipleObjects( THREAD_COUNT , pWaitHandle , TRUE , INFINITE );
#ifdef _DEBUG
    printf( "new %d delete %d det is %d/n",g_nNewCount , g_nDelCount , g_nNewCount - g_nDelCount );
#endif
    return 0;
}

 

上述解法也存在问题,就是如果任何写线程 在 更新完指针 之后 在 pOld->nref_count = stuOld.nref_count; 之前 突然被杀掉

那么其他的读线程在Release的时候会 死循环 ,不过这种可能性 发生的几率极小,如何防微杜渐,请看下回分解。

 

参考文献
《lock-free Data Structures》  http://erdani.org/publications/cuj-2004-10.pdf
刘未鹏 同学翻译的:(真是个大善人啊)
http://blog.csdn.net/pongba/archive/2006/01/26/588638.aspx
http://blog.csdn.net/pongba/archive/2006/01/29/589864.aspx

原创粉丝点击