随笔-156  评论-223  文章-30  trackbacks-0
   本文描述了一种简单的跨平台锁框架的设计与实现,该框架小巧实用、易于扩展,它的特点如下:
     ● 实现了线程间互斥锁
     ● 实现优化了单线程环境中的空锁和空级别锁
     ● 支持编译时或运行时选择锁
     ● 支持对象和类级别的锁粒度
     ● 支持错误或异常处理


框架结构
   由锁抽象、锁适配器、锁守卫、线程互斥锁和锁级别5个基本组件构成,对应类的关系如下图。
基本组件
   锁抽象
   提供锁语义的抽象,一般有5种操作:创建或初始化、阻塞加锁、非阻塞加锁、解锁和销毁,实现为lock_base类。
 1class lock_base
 2{
 3public:
 4    lock_base(){}
 5    virtual ~lock_base(){}
 6        
 7    virtual int lock() = 0;
 8    virtual int trylock() = 0;
 9    virtual int unlock() = 0;
10}
;
   构造对应创建或初始化操作,析构对应销毁操作,lock对应阻塞加锁,trylock对应非阻塞加锁,unlock对应解锁。 

   锁适配器
   支持运行时动态绑定某个具体锁,实现为lock_adapter类模板,继承lock_base。
 1 template<class T>
 2 class lock_adapter : public lock_base
 3{
 4public:
 5    lock_adapter(T &lock)
 6    :lock_(&lock)
 7    ,del_(false)
 8    {}
 9        
10    lock_adapter()
11    :del_(true)
12    { lock_ = new T(); }
13        
14    ~lock_adapter()
15    if(del_) delete lock_; }
16        
17    virtual int lock()
18    return lock_->lock(); }
19        
20    virtual int trylock()
21    return lock_->trylock(); }
22        
23    virtual int unlock()
24    return lock->unlock(); }
25        
26private:
27    T *lock_;
28    bool del_;
29}
;
   提供2个构造函数,支持引用外部锁和构造内部锁,并将方法实现委托给对应的锁实例。

   线程互斥锁  
   一种支持线程间同步的具体锁,支持windows和linux平台,实现为thread_mutex类。  
 1thread_mutex::thread_mutex()
 2{
 3#ifdef _WIN32
 4    //because use SEH
 to handle API exception, so instead of using one free function to initialize the critical section 

 5    init_critical_section(&m_); 
 6#else
 7    int ret;
 8    if(ret=pthread_mutex_init(&m_,NULL))
 9        throw lock_error("pthread_mutex_init",ret);
10#endif
11}

12
13thread_mutex::~thread_mutex()
14{
15#ifdef _WIN32
16    DeleteCriticalSection(&m_);
17#else
18    pthread_mutex_destroy(&m_);
19#endif
20}

21
22int thread_mutex::lock() 
23
24#ifdef _WIN32
25    EnterCriticalSection(&m_);
26    return 0;
27#else
28    int ret;
29    if(ret=pthread_mutex_lock(&m_)){
30        errno = ret;
31        return -1;
32    }

33    return 0;
34#endif
35}

36
37int thread_mutex::trylock() 
38
39#ifdef _WIN32
40#if (defined _WIN32_WINNT) && _WIN32_WINNT >= 0x0400
41    if(!TryEnterCriticalSection(&m_)){
42        errno = EBUSY;
43        return -1;
44    }

45    return 0;
46#endif    
47    errno = ENOSYS;
48    return -1;
49#else
50    int ret;
51    if(ret=pthread_mutex_trylock(&m_)){
52        errno = ret;
53        return -1;
54    }

55    return 0;
56#endif
57}

58
59int thread_mutex::unlock()
60
61#ifdef _WIN32
62    LeaveCriticalSection(&m_); 
63    return 0;
64#else
65    int ret;
66    if(ret = pthread_mutex_unlock(&m_)){
67        errno = ret;
68        return -1;
69    }

70    return 0;            
71#endif
72}
  lock、trylock和unlock返回0表示成;-1表示失败,errno指示错误码; 当仅用于单线程环境时,加解锁没有意义,提供一个空锁,实现为null_mutex类。
1class null_mutex
2{
3public:
4    int lock() return 0;}
5    int trylock() return 0;}
6    int unlock() return 0; }
7}
;

   锁守卫
   用于自动获取和释放锁,保证当异常发生时能自动解锁,实现为lock_guard类模板,T表示锁类型,只要这个类型提供lock、trylock和unlock三种语义。
 1template<class T>
 2  class lock_guard : noncopyable
 3{
 4public:
 5    explicit lock_guard(T &lockbool block=true)
 6        : lock_(&lock)
 7    {
 8        owner_ = (block ? lock_->lock() : lock_->trylock());
 9    }

10        
11    ~lock_guard()
12    {
13        if(0==owner_) lock_->unlock();
14    }

15        
16    int locked() const
17    return owner_; }
18    
19private:
20    T *lock_;
21    int owner_; 
22}
;
   构造函数形参block为true表示阻塞加锁,否则非阻塞加锁。当T为null_mutex时,为了避免调用lock和unlock的开销,特化如下。
1template<>
2class lock_guard<null_mutex>
3{
4    public:
5        explicit lock_guard(null_mutex&){}
6        ~lock_guard() {}
7    }
;

   锁级别
   提供类级别和对象级别2种锁粒度:类级别是指所有对象共享同一个锁,实现为class_level_lock类模板;对象级别是指每个对象持有自己的锁, 实现为object_level_lock类模板。
 1template<class T>
 2class level_lock_base : noncopyable
 3{
 4public:
 5    typedef lock_guard<const T> lock_guard_type;
 6    
 7    int lock() const
 8    return static_cast<const T*>(this)->lock_.lock(); }
 9    
10    int trylock() const
11    return static_cast<const T*>(this)->lock_.trylock();}
12  
13    int unlock() const
14    return static_cast<const T*>(this)->lock_.unlock(); }
15    
16protected:
17    ~level_lock_base(){}
18}
;
19    
20 template<class T,class L>
21 class class_level_lock : public level_lock_base<class_level_lock<T,L> >
22{
23    template<class U> 
24    friend class level_lock_base;
25    
26protected:
27    ~class_level_lock(){}
28    
29private:
30    static L lock_;        
31}
;
32    
33 template<class T,class L>
34 L class_level_lock<T,L>::lock_;
35    
36 template<class T,class L>
37 class object_level_lock : public level_lock_base<object_level_lock<T,L> >
38{
39    template<class U> 
40    friend class level_lock_base;
41    
42protected:
43    ~object_level_lock(){}
44    
45private:
46    mutable L lock_;    
47}
;
   level_lock_base是为遵循DRY SPOT原则而衍生的基类,使用CRTP模式,T是继承它的级别子类类型;它避免了每个子类都要编写lock、trylock和unlock的冗余,并使用const T定义了锁守卫类型别名,这是为了支持子类的const方法。class_level_lock和object_level_lock中的T是宿主类类型,L是锁类型,当L为null_mutex时,为避免调用lock(或trylock)和unlock的开销,提供了一个空级别,实现为null_level_lock类模板,继承level_lock_base,并且必须要以它特化锁守卫,这样在实际使用时就不会引起"lock_不是null_level_lock<T,L>的成员"编译错误。
 1 template<class T,class L>
 2 class null_level_lock : public level_lock_base<null_level_lock<T,L> >
 3{
 4protected:
 5    ~null_level_lock(){}
 6}
;
 7    
 8 template<class T,class L>
 9 class lock_guard<const null_level_lock<T,L> >
10{
11public:
12    explicit lock_guard(const null_level_lock<T,L>&){}
13    ~lock_guard(){}
14}
;

应用示例
   编译时选择锁类型与级别
   stl_sequence是以stl中的vector、list和deque三种序列容器为基础进行共性抽象的包装容器,为了使它灵活支持各种锁与级别,就需要将锁和级别定义为模板参数,并提供一个默认的类型。  
 1template<typename T,
 2         class L = null_mutex, //lock type
 3         template<class T,class L> class E = null_level_lock, //lock level
 4         template<class T,class U> class C = std::vector,
 5         template <class T> class U = std::allocator
 6         >
 7class stl_sequence : private E<stl_sequence<T,L,E,C,U>,L>
 8{
 9    typedef U<T> Allocator;
10    typedef C<T,Allocator> cont_type;
11    typedef stl_sequence<T,L,E,C,U> self_type;
12    typedef E<self_type,L> base_type;
13    typedef typename base_type::lock_guard_type lock_guard_type;
14    
15public:
16    
17    void add(const T &t,bool append = true)
18    {
19        lock_guard_type guard(*this);
20        //do add thing
21    }

22
23    void insert(size_t idx,const T &t)
24    {
25        lock_guard_type guard(*this);
26        //do insert thing
27    }

28
29    void erase(size_t idx)
30    {
31        lock_guard_type guard(*this);
32        //do erase thing
33    }

34
35    T* get(size_t idx) 
36    {
37        lock_guard_type guard(*this);
38        //do get thing
39    }

40    
41}
;
  从上可见,增加、删除、修改和查找操作都是通过首先调用lock_guard_type guard(*this)仅一行代码来支持线程同步,下面来看看它的使用。
   ● 使用空级别锁:seq1和seq2都没有锁,即使seq2使用了thread_mutex。
1    stl_sequence<int> seq1;
2    stl_sequence<int,thread_mutex> seq2;
   ● 使用对象级别锁:seq3和seq4具有各自的锁。
1   stl_sequence<int,thread_mutex,object_level_lock> seq3, seq4;
   ● 使用类级别锁:seq5和seq6共享同一个锁。
1   stl_sequence<int,thread_mutex,class_level_lock> seq5, seq6;
   ● 使用空锁:seq7、seq8和seq9都没有锁,但seq7效率稍高。
1   stl_sequence<int,null_mutex> seq7;
2   stl_sequence<int,null_mutex,class_level_lock> seq8;
3   stl_sequence<int,null_mutex,object_level_lock> seq9;
   综上可知,在单线程环境中,使用空级别空锁就够了;而在多线程环境中,可依据需求灵活选择对象级别或类级别锁。

   运行时绑定具体锁
 1    lock_base *lb;
 2    if(argc>1 && 0==strcmp(argv[1],"thread_mutex"))
 3        lb = new lock_adapter<thread_mutex>(*(new thread_mutex));
 4    else
 5        lb = new lock_adapter<null_mutex> (*(new null_mutex));
 6  auto_ptr<lock_base> ap(lb);
 7  lock_guard<lock_base> guard(*lb); 
 8  //do some thing
posted on 2014-12-28 23:38 春秋十二月 阅读(2368) 评论(6)  编辑 收藏 引用 所属分类: Opensrc

评论:
# re: 面向对象锁框架的设计与实现 2014-12-30 08:31 | 万连文
没想到锁搞这么复杂,多线程的问题可以通过规划线程模型来解决,底层的淫巧往往在于无锁编程。  回复  更多评论
  
# re: 面向对象锁框架的设计与实现 2014-12-30 09:56 | 路人
@万连文
这种框架不复杂,和ACE的类似,规划线程模型离不开锁,无锁编程也有很多问题。  回复  更多评论
  
# re: 面向对象锁框架的设计与实现 2014-12-31 09:23 | Richard Wei
哈哈, 我现在也比较趋向简单的设计, 有时间玩模板的奇淫技巧, 还不如花时间解决几个实际的问题。
曾经思考过C++的编程风格: http://www.cppblog.com/weiym/archive/2013/04/27/199781.html  回复  更多评论
  
# re: 面向对象锁框架的设计与实现[未登录] 2014-12-31 10:47 | 春秋十二月
@Richard Wei
这些都是c++基本的东西,编译器支持模板的差异性,库开发者就需要用奇淫技巧来跨平台,看boost的实现就知道了。c接口是最简洁通用的,应当首先。  回复  更多评论
  
# re: 面向对象锁框架的设计与实现 2014-12-31 14:49 | Richard Wei
@春秋十二月
理解, 基础库大量用模板我也没反对...  回复  更多评论
  
# re: 面向对象锁框架的设计与实现[未登录] 2015-03-17 16:33 | aa
有了boost库,再自己做简单跨平台有些多余了  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理