멀티 스레드에서 공유 자원 문제 해결을 위한 방법을 학습하고 그 중 뮤텍스를 배운다.
mutex는 mutual exclusive(상호 배제)에서 따온 말이다.
std::mutex
는 멀티 스레드 프로그래밍에서 공유 자원에 대한 여러 스레드의 접근을 제한하는 데 사용된다.
오직 한 스레드만이 std::mutex
의 lock
을 취득해서 공유 자원을 적절히 처리하고 작업이 끝나면 unlock
을 해서 다른 스레드도 공유 자원을 사용할 수 있도록 한다.
사용법은 여전히 간단하다.
std::mutex
객체를 생성해서 공유 자원에 대한 처리의 시작과 끝에 lock()
과 unlock()
함수를 호출해주면 된다.
#include <iostream>
#include <mutex>
#include <vector>
#include <thread>
std::mutex g_lock;
int g_data = 0;
void func()
{
for(int i = 0; i < 100000; i++)
{
g_lock.lock();
g_data++;
g_lock.unlock();
}
}
int main()
{
std::vector<std::thread> threads;
for(int i = 0; i < 3; i++)
{
threads.push_back(std::thread(func));
}
for(int i = 0; i < 3; i++)
threads[i].join();
std::cout << g_data << std::endl;
return 0;
}
이렇게 lock()
과 unlock()
함수를 명시적으로 사용해줄 수 있다.
하지만 이 방법은 프로그래머가 lock()
을 했다면 반드시 unlock()
을 해줘야 하는 것을 까먹지 않아야만 한다는 단점이 있다.
처음 락을 배우는 사람이면 어떤 바보가 lock
하고 unlock
을 안 하겠어?(내가 그랬다) 하겠지만 코드가 복잡해지거나 lock()
과 unlock()
사이에 return
이 되는 코드가 있거나 예외같은 문제가 있는 경우 unlock()
을 수행하지 못 할 수도 있다.
이런 모든 경우에 맞게 꼼꼼히 unlock()
하는 것은 생각보다 쉽지 않은 일이다.
(데드락에 걸려서 디버깅을 몇 번 해보고 나면 느끼게 된다.)
이에 대한 해결책으로 std::lock_guard
를 활용할 수 있다.
std::lock_guard
는 mutex
객체를 생성자에서 받아서 생성자에서 해당 mutex
의 lock()
을 호출한다. 그리고 소멸자에서 mutex
의 unlock()
을 호출한다.
객체의 생성자, 소멸자에서 자동으로 lock을 관리해주니 스코프를 벗어나게 되도 unlock()을 하지 않았다는 실수를 걱정을 할 필요가 없어진다.
#include <iostream>
#include <mutex>
#include <vector>
#include <thread>
std::mutex g_lock;
int g_data = 0;
void func()
{
for(int i = 0; i < 100000; i++)
{
std::lock_guard guard(g_lock);
g_data++;
}
}
int main()
{
std::vector<std::thread> threads;
for(int i = 0; i < 3; i++)
{
threads.push_back(std::thread(func));
}
for(int i = 0; i < 3; i++)
threads[i].join();
std::cout << g_data << std::endl;
return 0;
}