(C++ : Concurrency) 7. lock management

Posted by : at

Category : Cpp


lock_guard

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

std::mutex m;

void goo()
{
    m.lock();
    std::cout << "using shared data" << std::endl;
    throw std::exception();
    // m.unlock();
    // 실수로 unlock을 하지않았다면?
    // 혹은 lock이후에 exception이 발생하다면?

    // -> 사용자가 unlock하게 하지말자
}

void foo()
{
    try
    {
        goo();
    }
    catch(...)
    {
        std::cout << "catch exception" << std::endl;
    }
}

int main()
{
    std::thread t1(foo);
    std::thread t2(foo);
    t1.join();
    t2.join();
}
void goo()
{
    std::lock_guard<std::mutex> lg(m);
    std::cout << "using shared data" << std::endl;
    throw std::exception(); // 예외발생시 지역변수는 안전하게 해제가 되기에 unlock이 호출되게 된다.
}

unique_lock

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std::literals;

std::mutex m1, m2, m3;
std::timed_mutex tm1, tm2, tm3;

int main()
{
    std::unique_lock<std::mutex> u1;
    std::unique_lock<std::mutex> u2(m1);                    // m1.lock()을 해달라

    std::unique_lock<std::mutex> u3(m2, std::try_to_lock);  // m2.try_lock()을 해달라

    if(u3.owns_lock())
        std::cout << "acquire lock" << std::endl;
    else
        std::cout << "fail lock" << std::endl;

    m3.lock();
    std::unique_lock<std::mutex> u4(m3, std::adopt_lock);   // 이미 lock을 획득한 뮤텍스 관리

    std::unique_lock<std::mutex> u5(tm1, std::defer_lock);  // 나중에 lock을 호출예정
    auto ret = u5.try_lock_for(2s);

    std::unique_lock<std::mutex> u6(tm2, 2s);   // tm2.try_lock_for() 사용
    std::unique_lock<std::mutex> u7(tm3, std::chrono::steady_clock::now() + 2s);
    // tm3.try_lock_until() 사용
}
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std::literals;

std::timed_mutex m;

int main()
{
    std::unique_lock<std::timed_mutex> u1;
    std::unique_lock<std::timed_mutex> u2(m);

    u1 = u2;            // Error - 복사 생성자 지원안함
    u1 = std::move(u2); // Okay

    std::cout << u1.owns_lock() << std::endl;   // 1

    if(u1)
        std::cout << "acquire" << std::endl;
    
    u1.unlock();

    std::cout << u1.owns_lock() << std::endl;   // 0

    if(u1.try_lock_for(2s))
    {
        // ..
        u1.unlock();
    }

    u1.release();   // unlock은 하지않고 mutex와 연결만 끊는다
}


scoped_lock

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std::literals;

struct Account
{
    std::mutex m;
    int money = 100;
};

void transfer(Account& acc1, Acount& acc2, int cnt)
{
    // 데드락에 걸리게된다.
    acc1.m.lock();
    std::this_thread::sleep_for(10ms);
    acc2.m.lock();
    acc1.money -= cnt;
    acc2.money += cnt;
    std::cout << "finish transfer" << std::endl;
    acc2.m.unlock();
    acc1.m.unlock();
}

int main()
{
    Acount kim, lee;
    
    std::thread t1(transfer, std::ref(kim), std::ref(lee), 5);
    std::thread t2(transfer, std::ref(lee), std::ref(kim), 5);
    t1.join();
    t2.join();
}

해결해보자(해결방법은 세 가지)

// ...

void transfer(Account& acc1, Acount& acc2, int cnt)
{
    // (1) std::lock : 데드락회피기술을 통해 여러개의 mutex를 안전하게 lock
    std::lock(acc1.m, acc2.m);

    // (2) 락가드를 쓰고싶다면 아래처럼 std::adopt_lock를 옵션으로 넣자
    // std::lock_guard<std::mutex> lg1(acc1.m, std::adopt_lock);
    // std::lock_guard<std::mutex> lg2(acc2.m, std::adopt_lock);

    // (3) (C++17) scopred_lock
    // std::scopred_lock lg(acc1.m,, acc2.m);

    acc1.money -= cnt;
    acc2.money += cnt;
    std::cout << "finish transfer" << std::endl;
    acc2.m.unlock();
    acc1.m.unlock();
}

// ...

shared_lock

#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
#include <string_view>
using namespace std::literals;

std::shared_mutex m;
int share_data = 0;

void Writer()
{
    while(1)
    {
        m.lock();
        share_data = share_data + 1;
        std::cout << "Writer : " << share_data << std::endl;
        std::this_thread::sleep_for(1s);
        m.unlock();
        std::this_thread::sleep_for(10ms);
    }
}

void Reader(std::string_view name)
{
    while(1)
    {
        // 락가드로 처리해보자
        m.lock_shared();
        std::cout << "Reader(" << name << ") : " << share_data << std::endl;
        std::this_thread::sleep_for(500ms);
        m.unlock_shared();
        std::this_thread::sleep_for(10ms);
    }
}

int main()
{
    std::thread t1(Writer);
    std::thread t2(Reader, "A");
    std::thread t3(Reader, "B");
    std::thread t4(Reader, "C");
    t1.join();
    t2.join();
    t3.join();
    t4.join();
}
// ...

void Reader(std::string_view name)
{
    while(1)
    {
        std::shared_lock<std:shared_mutex> lg(m);
        std::cout << "Reader(" << name << ") : " << share_data << std::endl;
        std::this_thread::sleep_for(10ms);
    }
}

// ...

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links