C++ 뮤텍스 이중 lock 호출 문제 해결방안

"A fashion show depicted by M.C. Escher" from DALL-E 2

[개요]

C++로 작성된 회사 프로젝트에서 Onvif를 연동한 소스코드가 있는데, profileMutex, streamMutex 등의 뮤텍스를 사용하여 PTZ와 Onvif SDK를 사용하였다.

 

 

 

위의 각 PTZ 함수들이 모두 profileMutex를 잠근 후 사용하고 있는데, 문제는 뮤텍스를 사용하는 함수에서 또 뮤텍스를 사용하는 다른 함수를 호출하는 경우가 존재한다.

 

 

위 함수는 SetPreset() 함수인데, 처음에 profileMutex를 잠근 후에 아래에서 RemovePreset()을 호출하고, 다음에는 GetPresets()를 호출하고 있다.

 

이 코드는 그나마 리팩토링이 되어서 이중락이 발생하지 않도록 수정되었으나, 다른 함수들에서도 이런 경우가 발생할 여지가 높아보이도록 코드가 작성되었다.

 

[개선방안]

mutex_manager라는 클래스를 생성하고 멤버함수를 통해 락을 취할 수 있도록 하였다. 이때, 락이 이미 잠겨있는 상태 (다른 스레드가 사용중인 경우) 일 때, 사용중인 스레드가 현재 요청한 스레드와 동일한 경우, 락 요청을 무시하고, 다른 스레드가 락을 요청한 경우 기존 스레드가 작업을 끝날 때 까지 대기하도록 하여 이중락 문제를 개선하였다.

 

class mutex_manager {

  public:

    mutex_manager(const int & channelIndex):

    channelIndex(channelIndex),

    deviceAddr("0.0.0.0") {}



  mutex_manager(const int & channelIndex,

      const std::string & deviceAddr):

    channelIndex(channelIndex),

    deviceAddr(deviceAddr) {}



  //INFO: 같은 스레드에서 잠금 시도시 무시하고, 다른 스레드에서 잠금 시도 시 대기

  void get_mutex_lock(boost::mutex::scoped_try_lock & locker) {

    const boost::thread::id & currThreadId = boost::this_thread::get_id();

    boost::mutex::scoped_try_lock tempLock(mtx);

    //INFO: 뮤텍스 획득 실패

    if (!tempLock.owns_lock()) {

      //INFO: 같은 스레드에서 뮤텍스를 이미 사용중인 경우 무시

      if (mtxIndex == currThreadId) {

        InnoLogWarn("[%s] %s; channelIndex=%d. this thread already has profileMutex lock", deviceAddr.c_str(), __func__, channelIndex);

        return;

      }



      //INFO: 다른 스레드에서 요청한 경우 대기

      //InnoLogTrace("[%s] %s; other thread has lock. wait.", deviceAddr.c_str(), __func__);

      tempLock.lock();

    }

    mtxIndex = currThreadId;



    tempLock.swap(locker);

  }



  private:

    //INFO: 이 뮤텍스는 get_profile_mutex_lock() 함수를 통해서만 사용할 것

    boost::mutex mtx;

  //INFO: profileMutex를 현재 사용중인 스레드 ID를 보관

  boost::thread::id mtxIndex;

  //INFO: 채널 인덱스

  int channelIndex;

  //INFO: IP주소

  std::string deviceAddr;

};

 

기존 코드에서는 뮤텍스의 락을 할 때 다음과 같이 블록을 만든 후 boost::mutex::scoped_lock을 선언하여 사용하였다.

 

 

개선방안에서는 뮤텍스를 직접 잠그는 것이 아니라 get_mutex_lock() 멤버함수를 호출하고, 만약 해당 뮤텍스가 잠겨있지 않다면 새로운 locker를 만들고 파라미터로 받은 locker로 넘겨준다. 이미 잠겨있다면 현재 사용중인 스레드를 검사하는데, 그 스레드가 현재 요청한 스레드와 같다면 이중락을 막기 위해 그대로 return하고, 다른 스레드라면 이전 스레드가 처리를 끝낼 때 까지 대기하게 된다.

 

 

이로써, 같은 스레드에서 같은 뮤텍스를 여러번 호출하여 이중락이 발생하는 문제를 막을 수 있게 된다.