sliver__

[Mastering C++ Programming] - deadlock 본문

CS/C++

[Mastering C++ Programming] - deadlock

sliver__ 2022. 12. 9. 15:44
728x90
  • 다중 스레드 응용 프로그램에서는 deadlock 상태에 빠질 때까지 모든 것이 멋지고 흥미롭게 보입니다. 
  • READER와 WRITER라는 두 개의 스레드가 있다고 가정합니다. 
  • READER 스레드가 WRITER가 이미 획득한 잠금을 기다리고 WRITER 스레드가 READER가 소유한 잠금을 리더가 해제할 때까지 기다리거나 그 반대의 경우 교착 상태가 발생할 수 있습니다. 
  • 일반적으로 교착 상태 시나리오에서 두 스레드는 끝없이 서로를 기다립니다.

  • 일반적으로 교착 상태는 설계 문제입니다. 
  • 때로는 교착 상태를 빠르게 감지할 수 있지만 때로는 근본 원인을 찾는 것이 매우 까다로울 수 있습니다. 
  • 따라서 결론은 동기화 메커니즘을 신중하게 올바른 의미로 사용해야 한다는 것입니다.

  • 교착 상태 시나리오를 생성하기 위해 약간 수정하여 Thread 클래스를 재사용할 것입니다.
#include "Thread.h"

mutex Thread::commonLock;

int Thread::count = 0;

Thread::Thread( ThreadType typeOfThread ) {
  pThread = NULL;
  stopped = false;
  threadType = typeOfThread;
  (threadType == READER) ? name = "READER" : name = "WRITER";
}

Thread::~Thread() {
  delete pThread;
  pThread = NULL;
}

int Thread::getCount( ) {
  cout << name << " is waiting for lock in getCount() method ..." <<
endl;
  lock_guard<mutex> locker(commonLock);
  return count;
}

int Thread::updateCount( ) {
  cout << name << " is waiting for lock in updateCount() method ..." << endl;
  lock_guard<mutex> locker(commonLock);
  int value = getCount();
  count = ++value;
  return count;
}

void Thread::run( ) {
  while ( 1 ) {
    switch ( threadType ) {
      case READER:
        cout << name<< " => value of count from getCount() method is " << getCount() << endl;
        this_thread::sleep_for ( 500ms );
      break;

      case WRITER:
        cout << name << " => value of count from updateCount() method is" << updateCount() << endl;
        this_thread::sleep_for ( 500ms );
      break;
    }
  }
}

void Thread::start( ) {
  pThread = new thread ( &Thread::run, this );
}

void Thread::stop( ) {
  stopped = true;
}

void Thread::join( ) {
  pThread->join();
}

void Thread::detach( ) {
  pThread->detach( );
}

 

  •  Thread::getCount() 및 Thread::updateCount() 메서드에 대한 논의에 집중하겠습니다. 
  • std::lock_guard<std::mutex>는 mutex::unlock()을 호출하지 않도록 하는 템플릿 클래스입니다. 
  • 스택 해제 프로세스 중에 lock_guard 소멸자가 호출됩니다. 
  • 이렇게 하면 mutex::unlock()이 호출됩니다.

  • 결론은 std::lock_guard<std::mutex> 인스턴스가 생성된 시점부터 메소드 끝까지 나타나는 모든 문은 뮤텍스에 의해 보호된다는 것입니다.
#include <iostream>
using namespace std;

#include "Thread.h"

int main ( ) {

      Thread reader( READER );
      Thread writer( WRITER );
      reader.start( );
      writer.start( );
      reader.join( );
      writer.join( );
      return 0;
}
  •  두 개의 스레드, 즉 reader와 writer를 생성했으며 각 스레드가 생성된 후 시작됩니다. 
  • 기본 스레드는 reader / writer 스레드가 종료될 때까지 기다려야 합니다.

 

 

  •  WRITER 스레드는 Thread::updateCount() 메서드를 호출하고, 이는 차례로 Thread::getCount() 메소드를 호출합니다.

  •  Thread::updateCount() 메서드가 먼저 잠금을 획득한 다음 Thread::getCount() 메서드를 호출했음이 인쇄 문에서 분명합니다. 
  • 그러나 Thread::updateCount() 메서드가 뮤텍스 잠금을 해제하지 않았으므로 WRITER 스레드에서 호출한 Thread::getCount() 메서드를 진행할 방법이 없습니다. 
  • 한편 OS 스케줄러는 READER 스레드를 시작했으며 WRITER 스레드가 획득한 뮤텍스 잠금을 기다리는 것 같습니다. 
  • 따라서 READER 스레드가 작업을 완료하려면 Thread::getCount() 메서드에 대한 잠금을 획득해야 합니다. 
  • 그러나 이는 WRITER 스레드가 잠금을 해제할 때까지 불가능합니다.
  • WRITER 스레드는 자신의 Thread::getCount() 메서드 호출이 작업을 완료할 때까지 작업을 완료할 수 없습니다. 
  • 이를 교착 상태라고 합니다.

  • 설계 또는 논리적 문제입니다. 
  • Unix 또는 Linux에서는 Helgrind 도구를 사용하여 유사한 동기화 문제를 처리하여 교착 상태를 찾을 수 있습니다. 
  • Helgrind 도구는 Valgrind 도구와 함께 제공됩니다. 

  • deadlock 또는 condition race로 이어지는 소스 줄 번호를 얻으려면 이제 -g 플래그로 표시된 것처럼 디버그 모드에서 코드를 컴파일해야 합니다.

 

valgrind --tool=helgrind ./a.out
728x90
Comments