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);
}
}
// ...