嘗試在C++裡實現 Java 的 synchronized 關鍵字

來源:互聯網
上載者:User

  話說Java裡有個很強大的關鍵字叫synchronized,可以方便的實現線程同步。今天異想天開,嘗試在C++裡類比一個類似的。

  最近在學習C++的STL,看見智能指標這章節時,無不感歎利用語言的豐富特徵,來各種實現各種巧妙的構思。最經典的莫過於使用棧物件建構/解構函式,來維護局部資源的初始化和釋放。照著這個巧妙的方法,依樣畫葫蘆自己也來寫一個,來實現局部代碼線程同步。

  Java裡的synchronized有兩種形式,一種是基於函數的,另種則是語塊的。前者受C++的文法所限,估計是沒法實現了,所以就嘗試後者。
  塊級文法很簡單:

synchronized(syncObject) {    // code}

  
  因為Java所有變數都繼承於Object,所以任意變數都能當作鎖用。這在C++裡無法簡易實現,因此我們用特定的類型執行個體當作同步變數使用。
  先從最經典簡易的同步類說起。

struct Lock : CRITICAL_SECTION {    Lock() {        ::InitializeCriticalSection(this);    }    ~Lock() {        ::DeleteCriticalSection(this);    }    void Enter() {        ::EnterCriticalSection(this);    }    void Leave() {        ::LeaveCriticalSection(this);    }};

 

  這是windows下實現線程同步最常見的封裝。只需聲明一個Lock執行個體,在需要同步的代碼前後分別調用Enter和Leave即可。
  既然用起來這麼簡單,為什麼還要繼續改進?顯然這種方法有個很大的缺陷,如果忘了調用Leave,或者在調用之前就return/throw退出,那麼就會引起死結。
  所以,我們需要類似auto_ptr的機制,自動維護棧資料的建立和刪除。就暫且稱它_auto_lock吧。

struct _auto_lock {    Lock& _lock;    _auto_lock(Lock& lock) : _lock(lock) {        _lock.Enter();    }    ~_auto_lock() {        _lock.Leave();    }};

  _auto_lock通過引用一個Lock執行個體來初始化,並立即鎖住臨界區;被銷毀時則釋放鎖。

  有了這個機制,我們再也不用擔心忘了調用.Leave()。只需提供一個Lock對象,就能在當前語塊自動加鎖解鎖。再也不用擔心死結的問題了。

Lock mylock; void Test(){    // code1 ...     // syn code    {        _auto_lock x(mylock);    }     // code2 ...}

  進入syn code的"{"之後,_auto_lock被構造;無論用那種方式離開"}",解構函式都會被調用。
  上述代碼類似的在stl和boost裡都是及其常見的。利用棧對象的構造/解構函式維護局部資源,算是C++很常用的一技巧。
  我們的目標又近了一步。下面開始利用經典的宏定義,製造一顆synchronized文法糖,最終實現這樣的文法:

Lock mylock; void Test(){    // code1 ...     synchronized(mylock)    {        // sync code    }     // code2 ...}

  顯然需要一個叫synchronized宏,並且在裡面定義_auto_lock。

#define synchronized(lock)        ..... _auto_lock x(lock) ......

  乍一看這文法很像迴圈,並且要在迴圈內定義變數,所以用for(;;)的結構是再好不過了。

for(_auto_lock x(mylock); ; )

  不過sync code我們只需執行一次,所以還需另一個變數來控制次數。由於for裡面只能聲明一種類型的變數,所以我們在外面再套一層迴圈:

for(int _i=0; _i<1; _i++)for(_auto_lock x(mylock); _i<1; _i++)

  synchronized宏將mylock替換成上述代碼,既沒有違反文法,也實現相同的流程。得益於迴圈文法,甚至可以在synchronized內使用break來跳出同步塊

  
  我們將上述代碼整理下,並做個簡單的測試。

#include <stdio.h>#include <windows.h>#include <process.h>struct Lock : CRITICAL_SECTION {    Lock() {        ::InitializeCriticalSection(this);    }    ~Lock() {        ::DeleteCriticalSection(this);    }    void Enter() {        ::EnterCriticalSection(this);    }    void Leave() {        ::LeaveCriticalSection(this);    }};struct _auto_lock {    Lock& _lock;    _auto_lock(Lock& lock) : _lock(lock) {        _lock.Enter();    }    ~_auto_lock() {        _lock.Leave();    }};#define synchronized(lock)        for(int _i=0; _i<1; _i++)for(_auto_lock lock##_x(lock); _i<1; _i++)// ---------- demo ----------Lock mylock;// ---------- test1 ----------void WaitTest(int id){    printf("No.%d waiting...\n", id);    synchronized(mylock)    {        Sleep(1000);    }    printf("No.%d done\n", id);}void Test1(){    _beginthread((void(__cdecl*)(void*))WaitTest, 0, (void*) 1);    _beginthread((void(__cdecl*)(void*))WaitTest, 0, (void*) 2);    _beginthread((void(__cdecl*)(void*))WaitTest, 0, (void*) 3);}// ---------- test2 ----------void ThrowFunc(int id){    printf("No.%d waiting...\n", id);    synchronized(mylock)    {        Sleep(1000);        throw "some err";    }    printf("No.%d done\n", id);}void ThrowTest(int id){    try    {        ThrowFunc(id);    }    catch(...)    {        printf("%d excepted\n", id);    }}void Test2(){    _beginthread((void(__cdecl*)(void*))ThrowTest, 0, (void*) 1);    _beginthread((void(__cdecl*)(void*))ThrowTest, 0, (void*) 2);    _beginthread((void(__cdecl*)(void*))ThrowTest, 0, (void*) 3);}// ---------- test3 ----------void BreakTest(int id){    printf("No.%d waiting...\n", id);    synchronized(mylock)    {        Sleep(1000);        break;        Sleep(99999999);    }    printf("No.%d done\n", id);}void Test3(){    _beginthread((void(__cdecl*)(void*))BreakTest, 0, (void*) 1);    _beginthread((void(__cdecl*)(void*))BreakTest, 0, (void*) 2);    _beginthread((void(__cdecl*)(void*))BreakTest, 0, (void*) 3);}int main(int argc, char* argv[]){    printf("Wait Test. Press any key to start...\n");    getchar();    Test1();    getchar();    printf("Exception Test. Press any key to start...\n");    getchar();    Test2();    getchar();    printf("Break Test. Press any key to start...\n");    getchar();    Test3();    getchar();    return 0;}

  

  使用文法糖除了好看外,有個最重要的功能就是可以在synchronized同步塊裡使用break來跳出,並且不會引起死結,這是其他方法無法實現的。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.