无锁(lock-free)容器库libcds的使用指引及调查

写在最前:

(1).libcds依赖boost。Boost的下载、编译可参考博文:http://blog.csdn.net/great3779/article/details/7310392

(2).本文中用到了我自己封装的线程安全容器CWnQueue,可参考http://blog.csdn.net/great3779/article/details/7180383获取。

(3).本文中用到的测时套件TimerKit,可参考http://blog.csdn.net/great3779/article/details/7353028获取。

 

下面开始对libcds的讨论:

1.      获取:http://libcds.sourceforge.net/其中有源码、文档及论坛等。

2.      开源协议:BSD License

3.      主要功能:提供了lockfree(无锁的)浸入式和非浸入式容器。

非浸入式容器:类似于STL容器,入容器时采用值传递方式;

浸入式容器:入容器时采用引用传递方式。

浸入式容器是非浸入式容器的基类(即浸入式容器是基础,非浸入式容器实现于浸入式容器之上)

 

从上图可以看出,libcds主要提供了四类容器:Stacks(栈容器)、Queues(队列容器)、Maps(映射表容器)、Lists(链表容器),分别对应STL相对应的容器。

4.      编译:参考http://libcds.sourceforge.net/doc/cds-api/index.html

摘抄如下:


由于我在windows下试用libcds,因此总结一下windows下编译libcds的方法:

(1).编译并配置好boost。(关于boost的编译及配置,可以参考我相关文章:http://blog.csdn.net/great3779/article/details/7310392等)

(2).至cds目录下的\projects\Win子目录,选择相应的解决方案(.sln)。(例如,使用VS2008的朋友,可以打开vc9下的cds.sln)

(3).打开后,会发现其中包含了三个工程,编译cds工程即可。(其他两个工程是cds的单元测试以及hdr测试工程,可以不编译)。

(4).编译完成后,会在cds根目录下生成bin目录,内含cds的dll和lib文件。

(5).在你的demo工程中,引入dll和lib,就可以使用libcds了。

 

5.      使用示例(以Queues为例,其余容器类似):

(1).使用前准备

libcds文档原文摘抄如下:

First, in your code you should initialize cds library and a garbage collectorin main function:   

#include <cds/init.h>       // for cds::Initialize and cds::Terminate
    #include <cds/gc/hp.h>      // for cds::HP (Hazard Pointer) garbage collector
 
    int main(int argc, char** argv) 
    {
        // Initialize libcds
        cds::Initialize() ;
 
        {
            // Initialize Hazard Pointer singleton
            cds::gc::HP hpGC() ;
 
            // Do what you need
            ...
        }
 
        // Terminate libcds
        cds::Terminate()    ;
    }

Second, any of your thread should be attached to cds infrastructure.   

 #include <cds/gc/hp.h>
 
    int myThreadEntryPoint(void *)
    {
        // Attach the thread to Hazard Pointer singleton
        cds::gc::HP::thread_gc myThreadGC()    ;
 
        // Do some work
        ...
 
        // The destructor of myThreadGC object detaches the thread from HP singleton
    }

After that, you can use cds lock-freecontainers safely without any synchronization.

 

从文档中可以看出,要使用libcds,主线程需进行三个步骤:

(a). 初始化libcds

cds::Initialize();

(b). 初始化Hazard指针单例

cds::gc::HP hpGC();

(c). 结束libcds

cds::Terminate();

此外,每个子线程的开头需要关联线程至Hazard指针单例

cds::gc::HP::thread_gcmyThreadGC();

 

(2). Demo程序(小技巧:libcds源码注释中提供了比较详细的类使用方式,例如michael_map.h中就提供了非常详细的使用MichaelHashMap的方法)

 #include <cds/init.h> // for cds::Initialize andcds::Terminate

#include <cds/gc/hp.h>      // for cds::HP (Hazard Pointer) garbagecollector
#include <cds/container/msqueue.h>     // msqueue
 
typedef cds::container::MSQueue<cds::gc::HP,int> INT_MSQueue;
int main(int argc, char** argv)
{
         //Initialize libcds
         cds::Initialize();
 
 
         //Initialize Hazard Pointer singleton
         cds::gc::HPhpGC;
         //Attach the thread to Hazard Pointer singleton
         cds::gc::HP::thread_gcmyThreadGC;
 
         //define queues
         INT_MSQueue cds_queue;
         //push element to queue
         cds_queue.push(33);
 
 
         //Terminate libcds
         cds::Terminate();
}
总结一下:

(1).libcds容器的接口不丰富(或不完善)。这导致了实用性下降。

例如:有些容器不提供迭代器,例如MSQueue。或者就算提供了,也不太好用(相对标准STL),例如MichaelKVList的iterator。

 

上图左侧为STL queue接口,右侧为libcds MSQueue接口。MSQueue没有提供front方法,且size()方法无效(实践中发现其值总为0,也可参考其注释)。此外,enqueue/dequeue方法与push/pop方法有重复之嫌(为接口兼容?)。

 

(2).最重要的,与我自己封装的线程安全队列CWnQueue相比,性能上不占优势(甚至还更慢,这很难理解!)。

对比如下:

(a).单线程测试(push/pop操作)

int main(int argc, char** argv)
{
         //Initialize libcds
         cds::Initialize();
 
         //Initialize Hazard Pointer singleton
         cds::gc::HPhpGC;
         cds::gc::HP::thread_gcmyThreadGC;
 
         {
                   TKittk;
 
                   //Do what you need
                   intnSize = 10*1000*1000;
 
                   //test cds MSQueue
                   INT_MSQueuearr;
                   {
                            TUnittu("cds_queue_push");
                            RegisterUnit(tu);
 
                            for(inti = 0 ; i < nSize; ++i)
                                     arr.push(i);
                   }
                   {
                            TUnittu("cds_queue_pop");
                            RegisterUnit(tu);
 
                            inti;
                            while(arr.pop(i));
                   }
 
                   //test stl queue
                   queue<int>arr2;
                   {
                            TUnittu("stl_queue_push");
                            RegisterUnit(tu);
 
                            for(inti = 0; i < nSize; ++i)
                                     arr2.push(i);
                   }
                   {
                            TUnittu("stl_queue_pop");
                            RegisterUnit(tu);
 
                            while(!arr2.empty())
                                     arr2.pop();
                   }
 
                   //test CWnQueue
                   CWnQueue<int>arr3;
                   {
                            TUnittu("wn_queue_push");
                            RegisterUnit(tu);
 
                            for(inti = 0; i < nSize; ++i)
                                     arr3.push(i);
                   }
                   {
                            TUnittu("wn_queue_pop");
                            RegisterUnit(tu);
 
                            inti;
                            while(arr3.pop(i));
                   }
         }
 
         //Terminate libcds
         cds::Terminate();
}

测时结果:


从上图测时结果可以看出,cds的单线程push/pop表现均最差,甚至比CWnQueue也慢了不少!

 

(b).多线程测试(libcds意义所在)

#define TEST_CDS_QUEUE
 
#ifdef TEST_CDS_QUEUE
INT_MSQueue arr;
#else
CWnQueue<int> arr;
#endif
 
const int TEST_SIZE = 1000*1000*10;
 
void Push()
{
         TUnittu("push");
         RegisterUnit(tu);
 
#ifdef TEST_CDS_QUEUE
         cds::gc::HP::thread_gcmyThreadGC;
#endif
 
         for(inti = 0; i < TEST_SIZE; ++i)
         {
                   arr.push(i);
         }
}
 
void Pop()
{
         {
                   TUnittu("pop");
                   RegisterUnit(tu);
 
#ifdef TEST_CDS_QUEUE
                   cds::gc::HP::thread_gcmyThreadGC;
#endif
 
                   inti;
                   while(1)
                   {
                            while(arr.pop(i))
                            {
                                     if(i== TEST_SIZE-1)
                                     {
                                               cout<< "Finish" << endl;
                                               gotoEND;
                                     }
                            }
                            Sleep(100);
                   }
         }
 
END:
         EndTC();
}
 
int main(int argc, char** argv)
{
         //Initialize libcds
         cds::Initialize();
 
         //Initialize Hazard Pointer singleton
         cds::gc::HPhpGC;
         cds::gc::HP::thread_gcmyThreadGC;
 
         {
                   TKittk;
 
                   boost::threadt_push(Push);
 
                   for(inti = 0; i < 10; ++i)
                            boost::threadt_pop(Pop);
 
                   Sleep(10*1000);
         }
 
         //Terminate libcds
         cds::Terminate();
}
通过开启/关闭宏TEST_CDS_QUEUE,来分别测试MSQueue和CWnQueue的多线程表现力。测时结果如下:

 

MSQueue测时结果:

 

CWnQueue测试结果:

 

由于STL queue非线程安全,因此没有参与测试。从测试结果看,MSQueue在多线程表现下依旧没有任何优势,甚至表现还不如CWnQueue。而这原本应该是MSQueue的强项所在!

 

(c).带MSQueue的测试程序,在程序结束后总会报一个错误(Debug/Release下均有,经跟踪,是由于libcds内部获取某线程信息失败导致)。原因不明。不知是否与我的测试环境有关。

 

附上测试环境

软件环境:

WindowsServer 2003 Enterprise x64 Edition sp2 + VS2008sp1 +Boost1.49.0

硬件环境:

Pentium(R)Dual-Core E5200 + 4.00G + 160G(7200)

 

结论在最后:

Libcds容器既没有提供完善优雅的接口,在单/多线程下表现也不行(还不如我自己封装的线程安全容器)。似乎不是一个好的解决方案。(这实在是很难令人置信!!!)

(当然,由于测试还比较浅显、粗糙,有很大可能是我对libcds使用方法理解不够充分,从而误解了它。另外,对于浸入式容器,我也没测试,表现应该会比非浸入式容器好。但由于STL容器都是非浸入式容器,并且浸入式容器使用中陷阱很多,因此意义仿佛不是太大。)

最后非常希望有志之士能与我一起讨论libcds或纠正我对其使用/认识上的一些误区。邮件:great3779@sina.com

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值