std::lock_guard 引起的思考

std::lock_guard 引起的思考


版權聲明:本文為 cheng-zhi 原創文章,可以隨意轉載,但必須在明確位置注明出處!

從哪里來的思考?

最近在項目總結過程中,發現項目大量使用了 std::lock_guard 這個模板類,仔細分析后發現這個類牽扯到了很多重要的計算機基礎,例如:多線程,互斥,鎖等等,這里便記錄下來,也算是一次簡單的總結。

std::lock_guard 簡介

這個類是一個互斥量的包裝類,用來提供自動為互斥量上鎖和解鎖的功能,簡化了多線程編程,用法如下:

#include <mutex>

std::mutex kMutex;

void function() {
  // 構造時自動加鎖
  std::lock_guard<std::mutex> (kMutex);
  
  // 離開局部作用域,析構函數自動完成解鎖功能
}

用法非常簡單,只需在保證線程安全的函數開始處加上一行代碼即可,其他的都在這個類的構造函數和析構函數中自動完成。

如何自動完成?其實 Just so so ...

實現 my_lock_guard

這是自己實現的一個 lock_guard,就是在構造和析構中完成加鎖和解鎖的操作,之所以會自動完成,是因為離開函數作用域會導致局部變量析構函數被調用,而我們又是手動構造了 lock_guard,因此這兩個函數都是自動被調用的。

namespace myspace {
    template<typename T> class my_lock_guard {
    public:
        // 在 std::mutex 的定義中,下面兩個函數被刪除了
        // mutex(const mutex&) = delete;
        // mutex& operator=(const mutex&) = delete;
        // 因此這里必須傳遞引用
        my_lock_guard(T& mutex) :mutex_(mutex){
            // 構造加鎖
            mutex_.lock();
        }

        ~my_lock_guard() {
            // 析構解鎖
            mutex_.unlock();
        }
    private:
        // 不可賦值,不可拷貝
        my_lock_guard(my_lock_guard const&);
        my_lock_guard& operator=(my_lock_guard const&);
    private:
        T& mutex_;
    };

};

要注意的是這個類官方定義是不可以賦值和拷貝,因此需要私有化 operator =copy 這兩個函數。

什么是 std::mutex ?

如果你細心可以發現,不管是 std::lock_guard,還是my_lock_guard,都使用了一個 std::mutex 作為構造函數的參數,這是因為我們的 lock_guard 只是一個包裝類,而實際的加鎖和解鎖的操作都還是 std::mutex 完成的,那什么是 std::mutex 呢?

std::mutex 其實是一個用于保護共享數據不會同時被多個線程訪問的類,它叫做互斥量,你可以把它看作一把鎖,它的基本使用方法如下:

#include <mutex>

std::mutex kMutex;

void function() {
  //加鎖
  kMutex.lock();
  //kMutex.try_lock();

  //do something that is thread safe...
  
  // 離開作用域解鎖
  kMutex.unlock();
}

前面都提到了這個概念,那么什么是鎖,有啥用處?

什么是鎖?

鎖是用來保護共享資源(變量或者代碼)不被并發訪問的一種方法,它只是方法,實際的實現就是 std::mutex 等等的類了。

可以簡單的理解為:

  1. 當前線程訪問一個變量之前,將這個變量放到盒子里鎖住,并且當前線程拿著鑰匙。這樣一來,如果有其他的線程也要訪問這個變量,則必須等待當前線程將盒子解鎖之后才能訪問,之后其他線程在訪問這個變量之前也將會再次鎖住這個變量。

  2. 當前線程執行完后,就將該盒子解鎖,這樣其他的線程就可以拿到盒子的鑰匙,并再次加鎖訪問這個變量了。

這樣就保證了同一時刻只有一個線程可以訪問共享資源,解決了簡單的線程安全問題。

什么,你還沒有遇到過線程安全問題?下面開始我的表演...

一個簡單的線程安全的例子

這個例子中,主線程開啟了 2 個子線程,每個子線程都修改共享的全局變量 kData,如果沒有增加必要的鎖機制,那么每個子線程打印出的 kData 就可能會出錯。

這里使用了 3 種不同的加鎖方法來解決:

  1. 使用 std::lock_guard
  2. 使用 std::mutex 實現原生的加鎖
  3. 使用自己的 myspace::my_lock_guard
#include <iostream>
#include <mutex>
#include <thread>

// 兩個子線程共享的全局變量
int kData = 0;

// std::mutex 提供了一種防止共享數據被多個線程并發訪問的簡單同步方法
// 調用線程可以通過 lock 和 try_lock 來獲取互斥量,使用 unlock() 釋放互斥量
std::mutex kMutex;


void increment() {
    // 1.創建一個互斥量的包裝類,用來自動管理互斥量的獲取和釋放
    // std::lock_guard<std::mutex> lock(kMutex);
    
    // 2.原生加鎖
    // kMutex.lock();

    // 3.自己實現的 std::mutex 的包裝類
    myspace::my_lock_guard<std::mutex> lock(kMutex);
    
    for (int i = 0; i < 10; i++) {
        // 打印當前線程的 id : kData
        std::cout << std::this_thread::get_id() 
                  << ":" << kData++ << std::endl;
    }
    
    // 2. 原生解鎖  
    //kMutex.unlock();
    
    // 離開局部作用域,局部鎖解鎖,釋放互斥量
    
}


int main()
{
    // 打印當前函數名
    std::cout << __FUNCTION__ << ":" << kData << std::endl;

    // 開啟兩個線程
    std::thread t1(increment);
    std::thread t2(increment);

    // 主線程等待這兩個線程完成操作之后再退出
    t1.join();
    t2.join();

    // 防止立刻退出
    getchar();
    return 0;

}

注意:在 vs 中編譯這段代碼。

結果分析

為什么不加鎖的結果會出錯?

首先線程是一種輕量級的進程,也存在調度,假設當前 CPU 使用的是基于時間片的輪轉調度算法,為每個進程分配一段可執行的時間片,因此每個線程都得到一段可以執行的時間(這里只是簡單概括,仔細研究其實是有點復雜的,涉及到內核線程和用戶線程,這里就不多說了,不是這里討論的重點),這就導致子線程 1 在修改并打印 kData 的時候,子線程 1 的時間片用完了,CPU 切換到子線程 2 去修改并打印 kData,這就導致了最終的打印結果不是預先的順序,就是這個原理,簡單的理解是不難的。

原文地址

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容