[C++ 서버] Lock

이정석·2023년 11월 5일

CppServer

목록 보기
2/8

Lock

멀티쓰레드 환경에서 가장 중요한 개념중 하나는 역시 Lock이다. 각 쓰레드에서 공유자원에 접근하기전에 해당 자원에 대한 Lock을 얻고 자원에 접근함으로 자원에 대한 상호베제를 얻을 수 있다.

임계영역과 Lock에 대한 설명은 여기서보다 자세하게 다루었다.

1. Push

vector 공유자원에 두 쓰레드가 동시에 push_back하는 아래코드 예제를 보자.

  vector<int32> v;

  void Push() {
      for (int32 i = 0; i < 10000; i++) {
          v.push_back(i);
      }
  }

  int main() {
      std::thread t1(Push);
      std::thread t2(Push);

      t1.join();
      t2.join();

      cout << v.size() << endl;

      return 0;
  }

쓰레드 t1t2가 벡터에 만번의 데이터를 push_back하는 코드인데 실행해보면 에러가 발생한다.

발생하는 에러는 vector의 특성 때문에 발생하는 에러인데 vector는 연속된 메모리 블록에 값을 저장하지만 할당받은 영역보다 큰 영역을 요구할 때 다른 블록을 할당받고, 데이터를 옮기고, 기존의 데이터 블록을 할당해제한다.

여기서 기존의 데이터 블록을 할당해제할 때 에러가 발생할 수 있다. 멀티 쓰레드 환경에서, t1이 기존의 데이터 블록을 할당해제하고 그 뒤에 t2가 이미 할당해제된 영역을 다시 할당해제하는 경우에 에러가 발생하는 것이다.

그렇다면, 아래와 같이 충분한 영역의 vector 크기를 예약해놓으면 어떻게 될까?

  int main() {
      v.reserve(20000);
      std::thread t1(Push);
      std::thread t2(Push);

      t1.join();
      t2.join();

      cout << v.size() << endl;

      return 0;
  }

위 코드를 실행하면 에러는 발생하지 않지만 기대하는 값이 나오지 않는다. t1t2가 각각 만번의 삽입연산을 했기 때문에 출력값은 20000이어야 하지만 더 적은 값이 나온다.

2. Mutex

C++에서는 상호베제를 위해 mutex를 사용할 수 있다. 뮤텍스를 통해 임계영역에 해당하는 코드영역에 상호베제를 보장할 수 있다. lock을 이용해 해당 영역에 들어가기 전에 다른 쓰레드(프로세스)가 영역에 존재하는지를 확인한다. 연산이 끝났다면 unlock을 통해 mutex객체의 통제권을 반환한다.

  vector<int32> v;
  mutex m;

  void Push() {
      for (int32 i = 0; i < 10000; i++) {
          m.lock();

          v.push_back(i);

          m.unlock();
      }
  }
  1. 재귀적으로 걸 수 있는가?
    코드가 길어진다면 재귀적으로 lock을 하는 구조가 필요할 때가 있는데 mutex는 재귀기능이 없고 recursive_mutex를 사용해야 한다.
  2. lock을 걸고 풀지 않는다면?
    예외가 발생하거나, 함수 내에서 lock을 한 상태에서 return한 경우 의도적이지 않기 unlock을 호출하지 않는 경우가 발생할 수 있다. 이를 위해 lock_guard, unique_lock을 사용할 수 있다. lock_guard는 객체가 소멸할 때 자동으로 unlock을 호출해주며 unique_locklock_guard에 이후에 lock을 걸 수 있도록 해주는 기능을 추가한 객체이다.
profile
게임 개발자가 되고 싶은 한 소?년

0개의 댓글