unique_lock和lock_guard詳解

一、unique_lock的頭文件

unique_lock是C++11中新增的一個互斥量鎖的類型,定義在頭文件中。unique_lock最主要的一個作用是實現線程安全的資源管理。unique_lock可以被移動但不能被複制,因為一個unique_lock保存了與某個mutex相關的關鍵信息。

#include <mutex>
std::unique_lock<std::mutex> lock(mutex);

unique_lock的構造函數接受一個mutex的引用,並嘗試使用這個mutex進行加鎖。如果mutex已經被其他線程鎖定,那麼這個線程會被阻塞直到這個鎖被釋放為止。下面是一個使用unique_lock的簡單示例:

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>

std::mutex mtx;
std::vector<int> vec;

void pushVec(int n)
{
    std::unique_lock<std::mutex> lock(mtx); // unique_lock加鎖
    vec.push_back(n); // 對共享資源進行操作
} 

int main()
{
    std::thread t1(pushVec, 1);
    std::thread t2(pushVec, 2);
    t1.join();
    t2.join();
    for(auto i : vec)
    {
        std::cout << i << " ";
    }
    return 0;
}

上面的示例將兩個數1和2分別加入到了std::vector中,並在主線程中通過迭代器遍歷輸出。

二、unique_lock詳解

1、unique_lock的基本使用

除了構造函數之外,unique_lock還提供了一些其他的使用方式。

  1. 可以使用unique_lock的lock()函數手動鎖定(或者解鎖)mutex。
  2. 當unique_lock的作用域結束時會自動釋放mutex。
  3. unique_lock還提供了與條件變數結合使用的功能,可以在wait()函數中自動解鎖mutex並等待條件變數的通知。

unique_lock在實現資源管理的方面非常有用。下面是一個使用unique_lock實現同步輸出的示例。這個示例中共有10個線程,每個線程循環執行10次,每次輸出自己的ID和一個隨機數字。我們要求它們一定按照ID遞增的順序依次輸出。

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool flag = false;
const int threadCount = 10;

void printID(int id)
{
    for(int i = 0; i < 10; ++i)
    {
        std::unique_lock<std::mutex> lock(mtx);
        while(flag == false || (id == 0 && i == 0))
        {
            cv.wait(lock);
        }
        std::cout << id << " " << rand()%100 << std::endl;
        flag = false;
        cv.notify_all();
    }
} 

int main()
{
    std::vector<std::thread> vec;
    for(int i = 0; i < threadCount; ++i)
    {
        vec.emplace_back(printID, i);
    }
    flag = true;
    cv.notify_all();
    for(auto &t : vec)
    {
        t.join();
    }
    return 0;
}

這個示例中有10個線程,任意一個線程中的操作都不能影響到另一個線程。程序的實現方式是為每個線程定義一個id,然後每個線程循環執行10次,每次輸出自己的id和一個隨機數。要求每個線程都輸出完自己的內容後,主線程輸出全部內容。

為了保證線程按照id遞增的順序輸出,我們使用了一個flag和一個條件變數cv。flag=true表示線程可以輸出,false表示線程要等待其他線程的輸出完成後才能輸出。當線程輸出完自己的內容後,需要喚醒其他等待線程的輸出。這裡使用了cv.wait(lock)等待條件變數cv的通知,同時unique_lock的構造函數會鎖住mutex,確保線程的安全性。

2、unique_lock的高級使用

unique_lock除了上面提到的一些基本使用方法,還有許多高級的操作。例如:

  1. 可以將unique_lock的lock()函數作為可調用對象傳遞給線程,線程會在運行時執行lock()函數並加鎖。
  2. 可以將unique_lock的拷貝/移動構造函數直接傳遞給線程作為參數,線程會在運行時創建unique_lock實例並加鎖。
  3. 可以使用notfy_one()喚醒等待中的線程。
  4. 可以使用try_lock()函數嘗試加鎖,返回真表示加鎖成功,返回假表示加鎖失敗。

喚醒等待中的線程非常有用,省去了等待時間,可以節省CPU時間。下面是一個使用notfy_one()的示例:

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
std::vector<std::thread::id> vec;
bool flag = false;

void addID()
{
    while(true)
    {
        std::unique_lock<std::mutex> lock(mtx);
        if(flag == true)
        {
            std::cout << "Inserting ID: " << std::this_thread::get_id() << std::endl;
            vec.push_back(std::this_thread::get_id());
            flag = false;
            cv.notify_one(); // 喚醒等待中的線程
        }
        else
        {
            cv.wait(lock);
        }
    }
} 

int main()
{
    std::vector<std::thread> tVec;
    for(int i = 0; i < 3; ++i)
    {
        tVec.emplace_back(addID);
    }
    std::this_thread::sleep_for(std::chrono::seconds(1));
    flag = true;
    cv.notify_all();
    std::this_thread::sleep_for(std::chrono::seconds(1));
    flag = true;
    cv.notify_all();
    std::this_thread::sleep_for(std::chrono::seconds(1));
    flag = true;
    cv.notify_all();
    std::this_thread::sleep_for(std::chrono::seconds(1));
    flag = true;
    cv.notify_all();
    for(auto &t : tVec)
    {
        t.join();
    }
    for(auto i : vec)
    {
        std::cout << i << " ";
    }
    std::cout << std::endl;
    return 0;
}

三、lock_guard

1、lock_guard的使用

lock_guard是std::mutex的一個RAII封裝。在構造函數中嘗試對mutex加鎖,在析構函數中自動解鎖。由於lock_guard只有一個有參構造函數並且沒有拷貝/移動構造函數,因此不能被拷貝和移動。lock_guard的使用方式非常簡單:

void func()
{
    std::lock_guard<std::mutex> lock(mutex); // lock_guard加鎖
    // 對共享資源進行操作
}

lock_guard在使用中非常容易理解且安全,讓我們可以輕鬆地使用互斥量而不用擔心忘記解鎖互斥量。

2、lock_guard和unique_lock的區別

lock_guard與unique_lock最大的區別就是在使用方式上。lock_guard的構造函數中只接受一個mutex的引用,並對mutex進行加鎖;在lock_guard的析構函數中會對mutex進行解鎖。unique_lock的構造函數同樣接受mutex的引用,但unique_lock的構造函數可以接受多個參數,比如try_to_lock()表示嘗試對mutex進行加鎖,如果失敗就直接返回;還有一個defer_lock()表示unique_lock不會立即對mutex進行加鎖,而是後續再加鎖。unique_lock的析構函數會解鎖mutex。

總的來說,lock_guard更加簡單直接,而unique_lock相對來說更加靈活

3、lock_guard的簡單示例

下面是一個使用lock_guard實現同步累加器的示例。這個示例中有10個線程,每個線程循環執行10次,每次加1。最後輸出累加器的值。

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>

std::mutex mtx;
int sum = 0;

void add()
{
    for(int i = 0; i < 10; ++i)
    {
        std::lock_guard<std::mutex> lock(mtx); // lock_guard加鎖
        sum++;
    }
} 

int main()
{
    std::vector<std::thread> tVec;
    for(int i = 0; i < 10; ++i)
    {
        tVec.emplace_back(add);
    }
    for(auto &t : tVec)
    {
        t.join();
    }
    std::cout << sum << std::endl;
    return 0;
}

原創文章,作者:LIJEC,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/329985.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
LIJEC的頭像LIJEC
上一篇 2025-01-14 18:55
下一篇 2025-01-14 18:56

相關推薦

  • 神經網路代碼詳解

    神經網路作為一種人工智慧技術,被廣泛應用於語音識別、圖像識別、自然語言處理等領域。而神經網路的模型編寫,離不開代碼。本文將從多個方面詳細闡述神經網路模型編寫的代碼技術。 一、神經網…

    編程 2025-04-25
  • Linux sync詳解

    一、sync概述 sync是Linux中一個非常重要的命令,它可以將文件系統緩存中的內容,強制寫入磁碟中。在執行sync之前,所有的文件系統更新將不會立即寫入磁碟,而是先緩存在內存…

    編程 2025-04-25
  • git config user.name的詳解

    一、為什麼要使用git config user.name? git是一個非常流行的分散式版本控制系統,很多程序員都會用到它。在使用git commit提交代碼時,需要記錄commi…

    編程 2025-04-25
  • Linux修改文件名命令詳解

    在Linux系統中,修改文件名是一個很常見的操作。Linux提供了多種方式來修改文件名,這篇文章將介紹Linux修改文件名的詳細操作。 一、mv命令 mv命令是Linux下的常用命…

    編程 2025-04-25
  • MPU6050工作原理詳解

    一、什麼是MPU6050 MPU6050是一種六軸慣性感測器,能夠同時測量加速度和角速度。它由三個感測器組成:一個三軸加速度計和一個三軸陀螺儀。這個組合提供了非常精細的姿態解算,其…

    編程 2025-04-25
  • 詳解eclipse設置

    一、安裝與基礎設置 1、下載eclipse並進行安裝。 2、打開eclipse,選擇對應的工作空間路徑。 File -> Switch Workspace -> [選擇…

    編程 2025-04-25
  • Java BigDecimal 精度詳解

    一、基礎概念 Java BigDecimal 是一個用於高精度計算的類。普通的 double 或 float 類型只能精確表示有限的數字,而對於需要高精度計算的場景,BigDeci…

    編程 2025-04-25
  • Python輸入輸出詳解

    一、文件讀寫 Python中文件的讀寫操作是必不可少的基本技能之一。讀寫文件分別使用open()函數中的’r’和’w’參數,讀取文件…

    編程 2025-04-25
  • nginx與apache應用開發詳解

    一、概述 nginx和apache都是常見的web伺服器。nginx是一個高性能的反向代理web伺服器,將負載均衡和緩存集成在了一起,可以動靜分離。apache是一個可擴展的web…

    編程 2025-04-25
  • Python安裝OS庫詳解

    一、OS簡介 OS庫是Python標準庫的一部分,它提供了跨平台的操作系統功能,使得Python可以進行文件操作、進程管理、環境變數讀取等系統級操作。 OS庫中包含了大量的文件和目…

    編程 2025-04-25

發表回復

登錄後才能評論