模板和多態策略化加鎖

 

模版和多態策略化加鎖

關鍵詞:策略化模式 模板策略化 多態策略化 策略化加鎖模式 ACE BOOST C++ 設計模式

 

在ACE和BOOST的實現中都有大量的策略化加鎖(Strategized Locking)的模式,這種模式能比較方便的讓你的類兼容加鎖和不加鎖的兩種情況。ACE大師Douglas C. Schmidt有一片專門的論文Strategized Locking》對此做了介紹,國人Thzhang也對此問做過翻譯《ACE策略化的加鎖模式》。

本文的目的是介紹這種模式的兩種實現方式模版參數和多態方式以外,同時介紹兩者的優缺點。

 

模板參數(parameterized)策略化加鎖

如果一個監控組件,同時要在多線程和單線程環境下使用,而且其使用的平率非常高,多線程下必須加鎖,但如果因此而影響了單線程下的組件性能,是不合算的,但如果爲每一種環境寫一套代碼顯然不利於代碼的重用。那麼有沒有方法兼容兩種模式呢,這就是策略化加鎖的模式。

策略化加鎖的方法一般採用模版(ACE和BOOST都試用過類似方式)的方式完成中這個功能,這樣帶來的成本都在編譯器,幾乎沒有什麼性能影響。如下代碼:

//ZEN_Null_Mutex是一種用於單線程下的策略鎖,
class ZEN_Null_Mutex
{

public:

    //鎖定,其實什麼也沒有做
    void lock()
    {
    }
    //解鎖,其實什麼也沒有做
    void unlock()
    {
    }
}

//ZEN_Thread_Mutex是一種用於多線程下的策略鎖,
class ZEN_Thread_Mutex
{
protected:
    //線程鎖
    pthread_mutex_t  lock_;
public:

    //鎖定, ZEN_OS是爲爲了跨平臺封裝的函數,這兒不展開,大家可以認爲他就是mutex
    void lock()
    {
        ZEN_OS::pthread_mutex_lock(&lock_);
    }
    //解鎖, 
    void unlock()
    {
        ZEN_OS::pthread_mutex_unlock(&lock_);
    }
}

//ZEN_Server_Status_T是一個要在多線程或者單線程情況下試用的模版
//模版參數_ZEN_LOCK 決定鎖的行爲是多線程還是單線程
template <class _ZEN_LOCK>
class ZEN_Server_Status_T
{

protected:
    //策略化的鎖,根據模版參數決定行爲
    _ZEN_LOCK            stat_lock_;

public:

    //增加某項監控值,如果是在多線程下試用要避免衝突
    void increase_ byidx(size_t index, int64_t incre)
    {
        stat_lock_.lock();
        (stat_sandy_begin_ + index)->counter_ += incre;
        stat_lock_.unlock();
    }
    ……
}


上面的代碼template <class _ZEN_LOCK> class ZEN_Server_Status_T 就是帶有策略鎖功能的監控類,其中的模版參數_ZEN_LOCK 可以是ZEN_Null_Mutex 和 ZEN_Thread_Mutex,在不同的環境下我們可以試用不同的模版參數試用ZEN_Server_Status_T監控組件。這樣大家皆大歡喜,

在多線程環境下,你如下使用可以得到同步安全保護,

ZEN_Server_Status_T<ZEN_Thread_Mutex >::instance()->increase_byidx 

而在單線程環境下你如下使用,你可以得到最快的性能。

ZEN_Server_Status_T<ZEN_NULL_Mutex >::instance()->increase_byidx

 

而且你在這種模式下可以更容易的改變內部行爲,比如你實現一個讀寫鎖的

這種模式最大的好處講一個類的是否需要加鎖行爲的決定時間放到了編譯期,給一段代碼帶來了不同的行爲,而且性能代價最低。而且對於C++程序員,試用模版策略化的是一種很酷的表現,他讓你的代碼看上去更加上流(不要小瞧這種因素的影響力)。

 

模板策略化的不足

其實如果你仔細讀完Douglas C. Schmidt的論文《Strategized Locking》(可惜當年閱讀的時候能力有限,沒有體會),你會發現這樣一段。

There are two ways to strategize locking mechanisms: polymorphism and parameterized types . In general, parameterized types should be used when the locking strategy is known at compile-time. Likewise,polymorphism should be used when the locking strategy is not known until run-time. As usual, the trade-off is between the efficient run-time performance of parameterized types versus the potential for run-time extensibility with polymorphism.

翻譯:有兩種方式實現策略化鎖模式,多態或者模板參數化,一般模板參數化用於在編譯時決定鎖行爲,多態是在運行時決定鎖策略。通常權衡使用模版參數化方法還是多態方法的因素是運行時的性能和運行時的多態擴展性。

最近在自己寫的代碼中對上面這句話有了一些新認識,由於代碼的層級關係,我必須繼承監控的的類,於是,而同時由於我要保證鎖的效果,我仍然必須使用模版的鎖策略。

//模版模式在繼承的時候仍然必須試用模版
template <class _ZEN_LOCK>
class Comm_Server_Status :public ZEN_Server_Status< _ZEN_LOCK >


這還不是最讓人痛苦的,由於監控的點很多,我必須在基礎庫(注意是庫,而不是實現)的代碼也加入一些監控操作。此時你就發現如果你繼續保持模版的鎖策略,你必須改寫很多代碼。

//如果類ComponentA 要使用ZEN_Server_Status,
//同時Component_A本身也是一個組件,也是提供給其他人使用的,也無法在現在決定是否使用什麼鎖策略
//那麼你也只能也使用使用模版的策略鎖模式了
template <class _ZEN_LOCK>
class ComponentA
{

    int fun_a1
    {
        //如果你要在函數fun_a1中間使用監控數據
        ZEN_Server_Status<_ZEN_LOCK >::instance()->increase_byidx
            ……
    }
}

//如果你要在B組件中也用ZEN_Server_Status,也必須使用策略鎖
template <class _ZEN_LOCK>
class ComponentA
{
}

 

如果你使用希望使用監控的地方很多,那麼你的大量代碼就必須改成使用模版的方式。而且都必須使用策略鎖的方式。雖然我也不認爲這完全不可行,但這的確有將問題擴大化的嫌疑。特別是當你的ComponentA可能本身完全不需要考慮線程同步需求的時候。

觀察ACE的代碼,ACE的代碼大部分就是使用模版化的鎖策略,但這的確也給ACE的很多代碼帶來了痛苦(搜索ACE_LOCK就知道了),ACE的所有定時器其實都使用了模版化的策略鎖模式,而爲了代碼的方便,ACE往往在線程鎖作爲模版參數的類上繼承使用,比如ACE_Reactor默認就是加鎖的實現,所以本質上這樣並沒有帶來更好的性能。

//在多線程模式下
//ACE_Reactor
//實際是
//ACE_Select_Reactor_T<ACE_Reactor_Token_T<ACE_Token> >::handle_events(ACE_Time_Value & max_wait_time) 

 


 

多態(polymorphism)策略化

那麼如果採用多態策略化是否可以在某種程度上避免上述的問題?是的。多態決定行爲是在運行時,而不是編譯時,雖然有少量的性能損失,但將決定行爲時間的後移,也讓代碼在在某種程度獲得了更多的靈活性。

下面就是簡單的展現一段利用多態實現策略化鎖。

//利用多態的方式,實現策略,ZEN_Lock_Base作爲一個基類,
class ZEN_Lock_Base
{

public:

    //鎖定,其實什麼也沒有做,但同時也是一個虛函數,
    visual void lock()
    {
    }
    //解鎖,其實什麼也沒有做,但同時也是一個虛函數,
    visual void unlock()
    {
    }
}
typedef ZEN_Lock_Base ZEN_Null_Mutex;

//ZEN_Thread_Mutex是一種用於多線程下的策略鎖,
class ZEN_Thread_Mutex :public ZEN_Lock_Base
{
protected:
    //線程鎖
    pthread_mutex_t  lock_;
public:

    //鎖定, ZEN_OS是爲爲了跨平臺封裝的函數,這兒不展開,大家可以認爲他就是mutex
    visual void lock()
    {
        ZEN_OS::pthread_mutex_lock(&lock_);
    }
    //解鎖, 
    visual void unlock()
    {
        ZEN_OS::pthread_mutex_unlock(&lock_);
    }
}

// ZEN_Server_Status_P是一個要在多線程或者單線程情況下試用的模版
//模版參數_ZEN_LOCK 決定鎖的行爲是多線程還是單線程
class ZEN_Server_Status_P
{

protected:
    //多態的基類指針,根據初始化的參數決定是那個類的行爲
    ZEN_Lock_Base         *stat_lock_;

public:
    //初始化,根據外部的參數決定鎖的行爲
    void init(bool multi_thread)
    {
        if (multi_thread )
        {
            stat_lock_ = ZEN_Null_Mutex();
        }
        else
        {
            stat_lock_= new ZEN_Thread_Mutex();
        }
    }

    //增加某項監控值,如果是在多線程下試用要避免衝突
    void increase_ byidx(size_t index, int64_t incre)
    {
        //鎖的多態表現
        stat_lock_->lock();
        (stat_sandy_begin_ + index)->counter_ += incre;
        stat_lock_->unlock();
    }
    ……
}

 

 

你會發現,鎖策略可以在初始化函數時決定,不再需要將模版的參數的影響擴大到各個地方。比如你要在類庫中你也可以大量直接使用ZEN_Server_Status_P,因爲這時你並不用決定他的具體行爲。當然,由於動態是在運行時決定行爲,虛函數還是多多少少有一點點性能開銷的,當然在現在的CPU運算能力下,你不用過多的考慮這個問題。

 

比較兩種策略化的方式

模版策略化是使用模版參數實現策略化,將策略的行爲決定時間放到了編譯期,性能最優。但適合代碼規模不大,或者本身就是模板代碼使用,(追求酷代碼)。同時由於是將策略決定時間放在了編譯期,會在繼承,大規模使用的時候也必須使用模版策略行爲。從而使策略的影響擴大化。

多態策略化是使用多態方法決定策略行爲,在運行時調用者通過參數決定使用什麼多態行爲,同時由於決定行爲放在運行時,不需要相關代碼做出多大改變就可以使用。不足是性能相較模版策略化要弱一點點,在運行是必須要調用者控制策略行爲。

比較而言,我其實認爲多態策略化有更好的應用場景。個人感覺,有些時候,過度的模版設計反而會降低代碼的可用性,影響使用者。

 

【本文作者是雁渡寒潭,本着自由的精神,你可以在無盈利的情況完整轉載此文檔,轉載時請附上BLOG鏈接:http://www.cnblogs.com/fullsail/ 或者http://blog.csdn.net/fullsail,否則每字一元,每圖一百不講價。對Baidu文庫加價一倍】

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章