본문 바로가기
💻 programming/c++

[c++] std::recursive_mutex (std::mutex 비교)

by 연구원-A 2020. 12. 16.
반응형

 

일반적으로 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

 

std::recursive_mutex - cppreference.com

class recursive_mutex; (since C++11) The recursive_mutex class is a synchronization primitive that can be used to protect shared data from being simultaneously accessed by multiple threads. recursive_mutex offers exclusive, recursive ownership semantics: A

en.cppreference.com

 

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

댓글