程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 嘗試在C++裡實現 Java 的 synchronized 關鍵字,javasynchronized

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

編輯:C++入門知識

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


話說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來跳出,並且不會引起死鎖,這是其他方法無法實現的。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved