iT邦幫忙

2023 iThome 鐵人賽

DAY 4
0
Software Development

C++ 實務基礎經驗系列 第 22

併發相關 mutex

  • 分享至 

  • xImage
  •  

併發相關 mutex

今天開始就來介紹跟併發相關的部分,先從mutex開始

mutex

mutex是多執行緒中會使用到的功能,能針對一段代碼區段上鎖,確保同一時間下只有一個執行緒能夠存取這段代碼區段

那先來看一下,如果多執行緒不上鎖會發現什麼事

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

using namespace std;

int g_sum = 0;

void IncrementSomething()
{
    for (size_t i = 0; i < 10; i++)
    {
        g_sum += i;
    }
    
    cout << "sum is " << g_sum << endl;
}

int main(int argc, char const *argv[])
{
    thread t1(IncrementSomething);
    thread t2(IncrementSomething);

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

    return 0;
}

https://ithelp.ithome.com.tw/upload/images/20230930/20111996GZuFxxVI05.jpg

可以看到上圖的結果,會出現順序不對的情況,那是因為沒上鎖的情況,哪個執行緒先開始做,另一個執行緒也能從中途插進去做

那現在就來看看上鎖後的情況,這邊使用lock_gurad函數來上鎖

mutex mtx;

int g_sum = 0;

void IncrementSomething()
{
    // 透過RAII(Resource Acquisition is Iniatialization)資源獲取即初始化,當離開此區段mutex就會自動unlock
    lock_guard<mutex> lock(mtx);
    // 手動上鎖
    // mtx.lock();
    for (size_t i = 0; i < 10; i++)
    {
        g_sum += i;
    }
    
    cout << "sum is " << g_sum << endl;
    // 手動解鎖
    // mtx.unlock();
}

try_lock

mutex被某個線程lock住了的時候,實際上別的線程在lock時是阻塞的,而try_lock函數是非阻塞的,不過若mutex被其他線程lock住,函數會回false,不會阻塞

void IncrementSomething()
{
    while (!mtx.try_lock())
    {
        this_thread::sleep_for(chrono::microseconds(100)); // 等100奈秒
    }

    for (size_t i = 0; i < 1000000; i++)
    {
        g_sum ++;
    }
    
    cout << "sum is " << g_sum << endl;
    mtx.unlock();
}

recursive_mutex

再來看如何解決同一線程需要多次取得mutex而造成死鎖的問題,參考下面例子

void Func2()
{
    lock_guard<mutex> lock(mtx); // mtx被Func1鎖住但未釋放,卡住
    cout << "hihi\n";
}

void Func1()
{
    lock_guard<mutex> lock(mtx); 
    Func2();
}

int main(int argc, char const *argv[])
{
    Func1(); // deadlock

    return 0;
}

這時候可以使用recursive_mutex來解決上面的問題

recursive_mutex rec_mtx;
void Func2()
{
    lock_guard<recursive_mutex> lock(rec_mtx);
    cout << "hihi\n";
}

void Func1()
{
    lock_guard<recursive_mutex> lock(rec_mtx);
    Func2();
}

int main(int argc, char const *argv[])
{
    Func1(); // hihi

    return 0;
}

timed_mutex

超時的mutex這個跟try_lock用法也很像,就是加上個timeout,參考下面例子

timed_mutex time_mtx;
void work()
{
    chrono::milliseconds timeout(100);

    if (time_mtx.try_lock_for(timeout))
    {
        cout << this_thread::get_id() << ": do work with the mutex" << endl;
        chrono::milliseconds sleepDuration(250); // 讓先搶到鎖的執行緒睡到另一個執行緒timeout
        this_thread::sleep_for(sleepDuration);
        time_mtx.unlock();
    }
    else
    {
        cout << this_thread::get_id() << ": do work without the mutex" << endl;
    }
}

int main(int argc, char const *argv[])
{
    thread t1(work);
    thread t2(work);
    t1.join();
    t2.join();

    return 0;
}

上一篇
資源管理 內存洩漏
下一篇
併發相關 讀寫鎖
系列文
C++ 實務基礎經驗25
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言