C11線程管理:互斥鎖


1、概述

  鎖類型

  c11提供了跨平台的線程同步手段,用來保護多線程同時訪問的共享數據。
  std::mutex,最基本的 Mutex 類,獨占的互斥量,不能遞歸使用。
  std::time_mutex,帶超時的獨占互斥量,不能遞歸使用。
  std::recursive_mutex,遞歸互斥量,不帶超時功能。
  std::recursive_timed_mutex,帶超時的遞歸互斥量。

  lock類型

  std::lock_guard,與 Mutex RAII 相關,方便線程對互斥量上鎖。
  std::unique_lock,與 Mutex RAII 相關,方便線程對互斥量上鎖,但提供了更好的上鎖和解鎖控制。

  鎖函數

  std::try_lock,嘗試同時對多個互斥量上鎖。
  std::lock,可以同時對多個互斥量上鎖。
  std::unlock,解鎖。

2、獨占互斥量std::mutex

  互斥量使用都很簡單,接口用法也很簡單。一般是通過lock()來阻塞線程,直到獲取到互斥量為止。在獲取互斥量完成之后,使用unlock()來解除互斥量的占用。lock()和unlock()必須成對出現。

  std::mutex不允許拷貝構造,也不允許 move 拷貝,最初產生的 mutex 對象是處於 unlocked 狀態的。

  lock(),調用線程將鎖住該互斥量。線程調用該函數會發生下面 3 種情況:(1). 如果該互斥量當前沒有被鎖住,則調用線程將該互斥量鎖住,直到調用 unlock之前,該線程一直擁有該鎖。(2). 如果當前互斥量被其他線程鎖住,則當前的調用線程被阻塞住。(3). 如果當前互斥量被當前調用線程鎖住,則會產生死鎖(deadlock)。

  unlock(), 解鎖,釋放對互斥量的所有權。

  try_lock(),嘗試鎖住互斥量,如果互斥量被其他線程占有,則當前線程也不會被阻塞。線程調用該函數也會出現下面 3 種情況,(1). 如果當前互斥量沒有被其他線程占有,則該線程鎖住互斥量,直到該線程調用 unlock 釋放互斥量。(2). 如果當前互斥量被其他線程鎖住,則當前調用線程返回 false,而並不會被阻塞掉。(3). 如果當前互斥量被當前調用線程鎖住,則會產生死鎖(deadlock)。

#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>

std::mutex g_lock;

void vFunc()
{
    g_lock.lock();

    std::cout << "entered thread:" << std::this_thread::get_id() << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "leave thread:" << std::this_thread::get_id() << std::endl;

    g_lock.unlock();
}

int main()
{
    std::thread t(vFunc);
    std::thread t1(vFunc);
    std::thread t2(vFunc);

    t.join();
    t1.join();
    t2.join();

    return 0;
}
//輸出結果
entered thread:4420
leave thread:4420
entered thread:5288
leave thread:5288
entered thread:1432
leave thread:1432

3、std::lock_guard和std::unique_lock

  使用lock_guard和std::unique_lock可以簡化lock/unlock的寫法,同時也更安全,因為lock_guard使用了RAII技術,在構造分配資源,在析構釋放資源,會在構造的時候自動鎖定互斥量,在退出作用域之后進行析構自動解鎖。所以不用擔心沒有解鎖的情況,更加安全。

  std::lock_guard與 Mutex RAII 相關,方便線程對互斥量上鎖,std::unique_lock與 Mutex RAII 相關,方便線程對互斥量上鎖,但提供了更好的上鎖和解鎖控制,它可以自由的釋放mutex,而std::lock_guard需要等生命周期結束才能釋放。

void vFunc()
{
    std::lock_guard<std::mutex> locker(g_lock);//出作用域自動解鎖
    std::cout << "entered thread:" << std::this_thread::get_id() << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "leave thread:" << std::this_thread::get_id() << std::endl;
}

3、遞歸互斥鎖std::recuisive_mutex

  同一個線程不能多次獲取同一個獨占互斥鎖,一個線程多次獲取同一個互斥鎖發生死鎖。

//std::mutex mutex;

void funcA()
{
    std::lock_guard<std::mutex> lock(mutex);
    //do something
}

void funcB()
{
    std::lock_guard<std::mutex> lock(mutex);
    //do something
}

void funcC()
{
    std::lock_guard<std::mutex> lock(mutex);
    funcA();
    funcB();
}

int main()
{
    //發生死鎖,funcC已經獲取,無法釋放,funcA無法獲取
    funcC(); 
    
    return 0;
}

  為了解決死鎖的問題,C11有了遞歸鎖std::recursive_mutex,遞歸鎖允許一個線程多次獲取該鎖。

//std::recursive_mutex mutex;

void funcA()
{
    std::lock_guard<std::recursive_mutex> lock(mutex);
    //do something
}

void funcB()
{
    std::lock_guard<std::recursive_mutex> lock(mutex);
    //do something
}

void funcC()
{
    std::lock_guard<std::recursive_mutex> lock(mutex);
    funcA();
    funcB();
}

int main()
{
    //同一個線程可以多次獲取同一互斥量,不會發生死鎖
    funcC(); 
    
    return 0;
}

  需要注意的是,盡量少用遞歸鎖,原因如下:

  允許遞歸互斥容易放縱復雜邏輯的產生,從而導致一些多線程同步引起的晦澀問題;
  遞歸鎖比非遞歸鎖效率低;
  遞歸鎖雖然可以在同一線程多次獲取,但是獲取次數過多容易發生問題,引發std::system錯誤。

4、超時鎖

  std::time_mutex是超時的獨占鎖,std::recursive_timed_mutex是超時的遞歸鎖,主要用於在獲取鎖時增加超時等待功能,設置一個等待獲取鎖的時間,超時后做其他的事情。超時鎖多了兩個獲取鎖的接口,try_lock_for和try_lock_until,這兩個接口用來獲取互斥量的超時時間。

  try_lock_for 函數接受一個時間范圍,表示在這一段時間范圍之內線程如果沒有獲得鎖則被阻塞住(與 std::mutex 的 try_lock() 不同,try_lock 如果被調用時沒有獲得鎖則直接返回 false),如果在此期間其他線程釋放了鎖,則該線程可以獲得對互斥量的鎖,如果超時(即在指定時間內還是沒有獲得鎖),則返回 false。

  try_lock_until函數則接受一個時間點作為參數,在指定時間點未到來之前線程如果沒有獲得鎖則被阻塞住,如果在此期間其他線程釋放了鎖,則該線程可以獲得對互斥量的鎖,如果超時(即在指定時間內還是沒有獲得鎖),則返回 false。

#include <iostream>       // std::cout
#include <chrono>         // std::chrono::milliseconds
#include <thread>         // std::thread
#include <mutex>          // std::timed_mutex

std::timed_mutex mtx;

void fireworks() {
    // waiting to get a lock: each thread prints "-" every 200ms:
    while (!mtx.try_lock_for(std::chrono::milliseconds(200))) {
        std::cout << "-";
    }
    // got a lock! - wait for 1s, then this thread prints "*"
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    std::cout << "*\n";
    mtx.unlock();
}

int main()
{
    std::thread threads[10];
    // spawn 10 threads:
    for (int i = 0; i<10; ++i)
        threads[i] = std::thread(fireworks);

    for (auto& th : threads) th.join();

    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM