1. 程式人生 > >C++11併發學習之三:執行緒同步

C++11併發學習之三:執行緒同步

1.<mutex> 標頭檔案介紹 

(1)Mutex系列類(四種)
std::mutex,最基本的 Mutex 類。
std::recursive_mutex,遞迴 Mutex 類。
std::time_mutex,定時 Mutex 類。
std::recursive_timed_mutex,定時遞迴 Mutex 類。

(2)Lock系列類(兩種)
std::lock_guard,與 Mutex RAII 相關,方便執行緒對互斥量上鎖。
std::unique_lock,與 Mutex RAII 相關,方便執行緒對互斥量上鎖,但提供了更好的上鎖和解鎖控制。

(3)其他型別(結構體)
std::adopt_lock_t——它的常量物件定義為constexpr adopt_lock_t adopt_lock {};//constexpr 是C++11 中的新關鍵字)
std::defer_lock_t——它的常量物件定義為constexpr defer_lock_t defer_lock {}; //constexpr 是C++11 中的新關鍵字)
std::try_to_lock_t——它的常量物件定義為constexpr try_to_lock_t try_to_lock {};//constexpr 是C++11 中的新關鍵字)

(4)函式
std::try_lock,嘗試同時對多個互斥量上鎖。
std::lock,可以同時對多個互斥量上鎖。
std::call_once,如果多個執行緒需要同時呼叫某個函式,call_once 可以保證多個執行緒對該函式只調用一次。

2.常用型別舉例

(1)std::mutex類

☆建構函式,std::mutex不允許拷貝構造,也不允許 move 拷貝,最初產生的 mutex 物件是處於 unlocked 狀態的。
☆lock(),呼叫執行緒將鎖住該互斥量。執行緒呼叫該函式會發生下面 3 種情況:①如果該互斥量當前沒有被鎖住,則呼叫執行緒將該互斥量鎖住,直到呼叫 unlock之前,該執行緒一直擁有該鎖。②如果當前互斥量被其他執行緒鎖住,則當前的呼叫執行緒被阻塞住。③如果當前互斥量被當前呼叫執行緒鎖住,則會產生死鎖(deadlock)。
☆unlock(), 解鎖,釋放對互斥量的所有權。
☆try_lock(),嘗試鎖住互斥量,如果互斥量被其他執行緒佔有,則當前執行緒也不會被阻塞。執行緒呼叫該函式也會出現下面 3 種情況:① 如果該互斥量當前沒有被鎖住,則該執行緒鎖住互斥量,直到該執行緒呼叫 unlock 釋放互斥量。②如果當前互斥量被其他執行緒鎖住,則當前呼叫執行緒返回 false,而並不會被阻塞掉。③如果當前互斥量被當前呼叫執行緒鎖住,則會產生死鎖(deadlock)。

不論是lock()還是try_lock()都需要和unlock()配套使用,下面舉例說明lock()和try_lock()的區別。

#include <thread>
#include <iostream>
#include <string>
#include <chrono>
#include <assert.h>
#include <mutex>
int counter=0;
std::mutex mtx;
void func()
{
    for (int i=0; i<10000; ++i)
    {
            mtx.lock();
            ++counter;
            mtx.unlock();
    }
}
 
int main()
{
    std::thread workerThreads[10];
    for (int i=0; i<10; ++i)
    {
        workerThreads[i] = std::thread(func);
    }
    for (auto& workerThread : workerThreads)
    {
        workerThread.join();
    }
    std::cout << counter << " successful increases of the counter"<<std::endl;
 
    return 0;
}

由於lock()的阻塞特性,所以每個執行緒都統計了10000次,一共是10*10000=100000次。
#include <thread>
#include <iostream>
#include <string>
#include <chrono>
#include <assert.h>
#include <mutex>
int counter=0;
std::mutex mtx;
void func()
{
    for (int i=0; i<10000; ++i)
    {
        if (mtx.try_lock())
        {
            ++counter;
            mtx.unlock();
        }
    }
}
 
int main()
{
    std::thread workerThreads[10];
    for (int i=0; i<10; ++i)
    {
        workerThreads[i] = std::thread(func);
    }
    for (auto& workerThread : workerThreads)
    {
        workerThread.join();
    }
    std::cout << counter << " successful increases of the counter"<<std::endl;
 
    return 0;
}

由於try_lock()的非阻塞特性,如果當前互斥量被其他執行緒鎖住,則當前try_lock()返回 false,此時counter並不會增加1。所以這十個執行緒的統計結果具有隨機性,下次執行程式時,統計值不一定是16191。
(2).std::lock_guard和std::unique_lock類

std::lock_guard使用起來比較簡單,除了建構函式外沒有其他成員函式。
std::unique_lock除了lock_guard的功能外,提供了更多的成員函式,相對來說更靈活一些。這些成員函式包括lock,try_lock,try_lock_for,try_lock_until、unlock等。

std::unique_lock::lock——用它所管理的Mutex物件的 lock 函式。

std::unique_lock::try_lock——用它所管理的Mutex物件的 try_lock函式。

std::unique_lock::unlock——用它所管理的Mutex物件的 unlock函式。

這兩個類相比使用std::mutex的優勢在於不用配對使用,無需擔心忘記呼叫unlock而導致的程式死鎖。

#include <thread>
#include <iostream>
#include <string>
#include <chrono>
#include <assert.h>
#include <mutex>
int counter=0;
std::mutex mtx;
void func()
{
    for (int i=0; i<10000; ++i)
    {
        //將std::lock_guard替換成std::unique_lock,效果是一樣的
        std::lock_guard<std::mutex> lck (mtx);
        ++counter;
    }
}
 
int main()
{
    std::thread workerThreads[10];
    for (int i=0; i<10; ++i)
    {
        workerThreads[i] = std::thread(func);
    }
    for (auto& workerThread : workerThreads)
    {
        workerThread.join();
    }
    std::cout << counter << " successful increases of the counter"<<std::endl;
 
    return 0;
}

std::uniqure_lock建構函式的第二個引數可以是std::defer_lock,std::try_to_lock或std::adopt_lock

#include <thread>
#include <iostream>
#include <string>
#include <chrono>
#include <assert.h>
#include <mutex>
int counter=0;
std::mutex mtx;
void func()
{
    for (int i=0; i<10000; ++i)
    {
        mtx.lock();
        //注意此時Tag引數為std::adopt_lock表明當前執行緒已經獲得了鎖,
        //此後mtx物件的解鎖操作交由unique_lock物件lck來管理,在lck的生命週期結束之後,
        //mtx物件會自動解鎖。
        std::unique_lock<std::mutex> lck(mtx,std::adopt_lock);
        ++counter;
    }
}
 
int main()
{
    std::thread workerThreads[10];
    for (int i=0; i<10; ++i)
    {
        workerThreads[i] = std::thread(func);
    }
    for (auto& workerThread : workerThreads)
    {
        workerThread.join();
    }
    std::cout << counter << " successful increases of the counter"<<std::endl;
 
    return 0;
}


#include <chrono>
#include <assert.h>
#include <mutex>
int counter=0;
std::mutex mtx;
void func()
{
    for (int i=0; i<10000; ++i)
    {
        //注意此時Tag引數為std::defer_lock表明當前執行緒沒有獲得了鎖,
        //需要通過lck的lock和unlock來加鎖和解鎖,
        std::unique_lock<std::mutex> lck(mtx,std::defer_lock);
        lck.lock();
        ++counter;
        lck.unlock();
    }
}
 
int main()
{
    std::thread workerThreads[10];
    for (int i=0; i<10; ++i)
    {
        workerThreads[i] = std::thread(func);
    }
    for (auto& workerThread : workerThreads)
    {
        workerThread.join();
    }
    std::cout << counter << " successful increases of the counter"<<std::endl;
 
    return 0;
}

參考連結:http://www.cnblogs.com/haippy/p/3346477.html
--------------------- 
作者:燦哥哥 
來源:CSDN 
原文:https://blog.csdn.net/caoshangpa/article/details/52842618?utm_source=copy 
版權宣告:本文為博主原創文章,轉載請附上博文連結!