이번 글은 mutex와 lockwrapper 클래스(boost_guard와 scoped_lock)을 소개합니다.
좀 더 복잡한 lock wrapper 클래스인 unique_lock은 다음 편에 소개하려 합니다.
mutex와 lock
MUTual EXclusion에서 따온 말로, 한국어로 번역하면 상호 배제입니다. 그런데 이렇게 쓰면 잘 와닿지가 않습니다. 일반적으로 mutex를 설명하기 위해 자물쇠 or 화장실 잠금장치 등을 예로 사용합니다. 자물쇠는 열거나(lock), 잠그는(unlock) 동작을 수행할 수 있습니다. 그래서 mutex 클래스에는 lock, try_lock, unlock이라는 locking에 대한 함수가 존재합니다.
①사람(쓰레드)이 특정 영역(화장실)에 들어가서 ②mutex(자물쇠 or 잠금장치)를 lock을 하면 ③아무도 들어갈 수 없고 사람들은 대기합니다. ④mutex(자물쇠 or 잠금장치)가 unlock이 되어 사람이 나오면, 다음 사람이 사용할 수 있습니다.
화장실에 들어가더라도(mutex를 선언만 하고) 잠금장치를 잠그지 않으면(lock을 안하면), 누구든지 들어올 수 있겠지요? 끔찍하네요…
std::mutex
쓰레드는 lock (또는 try_lock) 호출이 성공한 시점부터 unlock을 호출 할 때까지 mutex를 소유합니다.
void lock() | mutex를 잠급니다. 다른 스레드가 mutex를 소유하고 있다면, lock이 풀릴 때까지 블로킹 합니다. |
bool try_lock() | mutex 잠금을 시도합니다. 다른 스레드가 mutex를 소유하고 있다면 false를 return하고, 그렇지 않으면 mutex를 잠그고 소유하게 됩니다. 주로 mutex를 소유할 수 없을 때 다른 액션을 취하고 싶을 경우 사용합니다. |
void unlock() | mutex 잠금을 풉니다. |
사용예제는 아래와 같습니다.
#include <thread>
#include <mutex>
#include <iostream>
void func()
{
static std::mutex m;
// Example1
m.lock();
/* 작업 중...*/
m.unlock();
// Example2
if(m.try_lock())
{
/* 작업 중...*/
m.unlock();
}
else
{
// lock 실패... 안녕..
}
}
std::lock_guard<std::mutex> (since C++11)
또는
std::scoped_lock (since C++17)
둘 다 mutex wrapper 클래스입니다. lock_guard 또는 scoped_lock 객체가 생성될 때, 주어진 mutex의 소유권을 얻기 위해 잠금을 시도합니다. 객체의 생성 범위(Scope)를 벗어나면 소멸자가 호출되면서 Mutex 잠금이 해제됩니다. lock ~ unlock을 직접 하는 것보다 편리할 뿐 아니라 코드도 깔끔해지고, unlock을 깜박할 가능성을 원천 차단시켜 줍니다.
둘 간의 차이는 scoped_lock이 나중에 나왔다…? 정도로 보이고, 역할은 거의 비슷한 것 같은데요. 확실한 차이점은 scoped_lock은 다중잠금이 가능하다는 점입니다. scoped_lock 객체가 소멸될 때는 mutex 잠금이 역방향으로 해제됩니다.
std::mutex m1, m2, m3;
std::scoped_lock lock(m1, m2, m3); // 가능
std::lock_guard<std::mutex> guard(m1); // 1개만 가능
예제 코드
1) Mutex를 사용하지 않을 때
#include <iostream>
#include <thread>
#include <mutex>
#include <string>
void func(std::string message)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "func..." << message << std::endl;
}
int main()
{
std::vector<std::thread> threads;
threads.emplace_back(func, "test1");
threads.emplace_back(func, "test2");
threads.emplace_back(func, "test3");
threads.emplace_back(func, "test4");
threads.emplace_back(func, "test5");
for (auto &thread : threads)
thread.join();
return 0;
}
결과는 엉망진창입니다.2) Mutex – lock & unlock 사용
의도했던 순서대로 나옵니다.
#include <iostream>
#include <thread>
#include <mutex>
#include <string>
void func(std::string message)
{
static std::mutex m;
m.lock();
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "func..." << message << std::endl;
m.unlock();
}
int main()
{
std::vector<std::thread> threads;
threads.emplace_back(func, "test1");
threads.emplace_back(func, "test2");
threads.emplace_back(func, "test3");
threads.emplace_back(func, "test4");
threads.emplace_back(func, "test5");
for (auto &thread : threads)
thread.join();
return 0;
}
순서대로 잘 나옵니다.3) Mutex – try_lock & unlock 사용
#include <iostream>
#include <thread>
#include <mutex>
#include <string>
void func(std::string message)
{
static std::mutex m;
if (m.try_lock())
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "func..." << message << std::endl;
m.unlock();
}
}
int main()
{
std::vector<std::thread> threads;
threads.emplace_back(func, "test1");
threads.emplace_back(func, "test2");
threads.emplace_back(func, "test3");
threads.emplace_back(func, "test4");
threads.emplace_back(func, "test5");
for (auto &thread : threads)
thread.join();
return 0;
}
첫번째 thread만 정상적으로 수행됩니다.4) Mutex – scoped_lock 사용
#include <iostream>
#include <thread>
#include <mutex>
#include <string>
void func(std::string message)
{
static std::mutex m;
std::scoped_lock lock(m);
//std::lock_guard<std::mutex> guard(m); // scoped_lock 대신 사용해도 같은 결과
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "func..." << message << std::endl;
}
int main()
{
std::vector<std::thread> threads;
threads.emplace_back(func, "test1");
threads.emplace_back(func, "test2");
threads.emplace_back(func, "test3");
threads.emplace_back(func, "test4");
threads.emplace_back(func, "test5");
for (auto &thread : threads)
thread.join();
return 0;
}