반응형
일반적으로 mutex의 lock을 두 번 호출하면 무한 대기 상태에 빠지지만,
recursive mutex는 lock을 여러 번 호출해도 문제가 없다
lock을 호출하는 동안은 계속 소유권을 가지고 있다가
lock을 호출한 횟수 만큼 unlock을 호출하면 소유권을 해제하기 때문이다
재귀함수를 만들어보면 쉽게 이해할 수 있다 (이름부터 recursive_mutex)
한 번 재귀함수를 병렬처리해보자
그냥 mutex를 사용했을 때 (무한 대기)
- worker 함수를 호출하는 4개의 스레드를 생성한다
- worker 함수는 자기 자신을 다시 호출하는 재귀 함수로 구성한다
- result라는 공유자원에 다른 스레드가 접근하지 못하도록 mutex lock을 수행한다
- worker 내부에서 worker 함수를 다시 호출하므로 unlock 되기 전에 lock이 먼저 호출된다
- lock이 해제되기도 전에 다시 호출되었으므로 무한 대기 상태에 빠진다
#include <iostream>
#include <mutex> // mutex 라이브러리
#include <thread>
#include <vector>
int worker(int& result, std::mutex& m, const int& end, const int& start) {
m.lock();
if (start == end) {
// do nothing
} else {
result = worker(result, m, end, start + 1) + 1; // 재귀함수
}
m.unlock();
return result;
}
int main() {
int counter = 0;
std::mutex m; // mutex
std::vector<std::thread> workers;
for (int i = 0; i < 4; i++) {
workers.push_back(std::thread(worker, std::ref(counter), std::ref(m), 10000, 0));
}
for (int i = 0; i < 4; i++) {
workers[i].join();
}
std::cout << "Counter 최종 값 : " << counter << std::endl;
}
recursive mutex를 사용했을 때
mutex를 그냥 recursive_mutex로 바꿔서 수행해보자
- result라는 공유자원에 다른 스레드가 접근하지 못하도록 recursive_mutex lock을 수행한다
- lock은 여러 번 호출되는 동안 계속 소유권을 가지게 됨
- = 다른 스레드는 result에 접근하지 못 함
- work 함수가 종료될 때마다 unlock을 호출하면서 재귀함수를 빠져나온다
- work 함수가 호출된 횟수만큼 lock이 호출되었고
- work 함수가 종료될 때마다 unlock이 호출됨
- 마지막 work 함수를 빠져나올 때 소유권이 해제됨
- recursive_mutex를 사용했을 땐 무한 대기에 빠지지 않고 result 결과가 40000으로 정상적으로 출력된다
#include <iostream>
#include <mutex> // mutex libracy
#include <thread>
#include <vector>
int worker(int& result, std::recursive_mutex& m, const int& end, const int& start) {
m.lock();
if (start == end) {
// do nothing
} else {
result = worker(result, m, end, start + 1) + 1;
}
m.unlock();
return result;
}
int main() {
int counter = 0;
std::recursive_mutex m; // recursive_mutex
std::vector<std::thread> workers;
for (int i = 0; i < 4; i++) {
workers.push_back(std::thread(worker, std::ref(counter), std::ref(m), 10000, 0));
}
for (int i = 0; i < 4; i++) {
workers[i].join();
}
std::cout << "Counter 최종 값 : " << counter << std::endl; // 40000
}
재귀함수를 빠져나오지 못하면?
재귀함수의 종료시점을 잘못 정하면 무한 루프에 빠지기 십상이다
무한 루프에 빠진 재귀함수는 lock을 무한히 호출하게 될 것이다 (아마도 segment fault)
recursive_mutex의 lock도 물론 무한히 호출될 수는 없다
recursive_mutex의 최대 lock 가능 횟수를 넘어가면 std::system_error 예외를 발생시킨다
cppreference에서 설명하는 글
en.cppreference.com/w/cpp/thread/recursive_mutex
recursive_mutex는 일반 mutex와 달리 상호배타적이면서도 재귀적인 소유권을 갖습니다.
- 스레드에서 lock 또는 try_lock을 호출하면 unlock 될 때까지 해당 mutex의 소유권을 가집니다
- 한 스레드에서 소유권을 가지고 있더라도 다른 스레드에서 lock 또는 try_lock을 호출할 수 있습니다
- 소유권은 스레드가 lock한 횟수만큼 unlock되어야 해제됩니다
- (lock 호출을 통해) 소유권을 가진 스레드가 존재하면 다른 스레드는 소유권을 가질 수 없습니다
- lock을 호출하면 block 되고 try_lock을 호출하면 false를 반환받습니다
- recursive_mutex 의 최대 lock 가능 횟수를 넘어가면 std::system_error 예외를 발생시킵니다
#include <iostream>
#include <thread>
#include <mutex>
class X {
std::recursive_mutex m;
std::string shared;
public:
void fun1() {
std::lock_guard<std::recursive_mutex> lk(m);
shared = "fun1";
std::cout << "in fun1, shared variable is now " << shared << '\n';
}
void fun2() {
std::lock_guard<std::recursive_mutex> lk(m);
shared = "fun2";
std::cout << "in fun2, shared variable is now " << shared << '\n';
fun1(); // recursive lock becomes useful here
std::cout << "back in fun2, shared variable is " << shared << '\n';
};
};
int main()
{
X x;
std::thread t1(&X::fun1, &x);
std::thread t2(&X::fun2, &x);
t1.join();
t2.join();
}
반응형
'💻 programming > c++' 카테고리의 다른 글
[c++] std::shared_mutex (0) | 2020.12.16 |
---|---|
[c++] std::timed_mutex (0) | 2020.12.16 |
[c++] std::mutex (0) | 2020.12.16 |
[c++] mutex, lock guard, unique lock, shared mutex, recursive mutex (0) | 2020.12.16 |
[c++] template meta programming (0) | 2020.12.15 |
댓글