POSIX PTHREAD나 Win32 Thread의 경우, 프로세스 내에서 스레드의 동기화를 제공하는 방법으로 pthread는 뮤텍스의 속성을 지정하게 되고, Win32에서는 이런 경우에 특화되어 있는 경량형 뮤텍스인 CRITICAL_SECTION 구조체를 사용하게 된다. 만약 프로세스 사이에서 스레드의 동기화가 필요하다면 pthread는 뮤텍스의 속성을 지정하는 pthread_mutexattr_t을 적절하게 설정하고, Win32의 경우에는 핸들을 사용하는 뮤텍스를 생성하여 사용한다. (즉, 커널 오브젝트를 생성한다)
여튼, 뮤텍스를 사용하고자 마음 먹었다면, 중요한 것은 생성한 뮤텍스의 범위를 어떻게 설정하는가이다. 즉, 여러개의 스레드가 존재하고, 이 스레드들이 공유된 메모리 영역에 접근하고자 할 때 이 공유 영역을 임계 영역이라 한다면, 이 경우 뮤텍스를 설정하지 않았다면 경쟁 조건(race condition)이 발생한다. 이 경우 반드시 뮤텍스로 임계 영역에 대해서 적절히 통제해야 하는데, 문제는 이 뮤텍스는 항상 전역 변수로 설정되어 있어야 하는가? 그렇지 않다면, 어느 범위에 이 뮤텍스를 설정해야 적절하게 통제가 가능한가?
뮤텍스는 반드시 전역 변수로 선언될 필요는 없다. 하지만, 실행하고자 하는 작업이 서로 적절하게 동기화가 되려면, 그 작업들을 동기화 해주는 뮤텍스는 같은 뮤텍스여야 한다. 그 범위에 맞추어서 유효한 뮤텍스의 범위를 설정하면 될 것이다.
예를 들어보자. Base 클래스의 Do() 메서드를 두 개의 스레드에서 동시에 실행하면서, 적절한 동기화를 제공하고 싶다. 그렇다면 다음의 방법은 맞는가?
class Base
{
public:
Base() { .... }
~Base() { .... }
void Do()
{
pthread_mutex_lock(&m_Mutex);
for(int i = 0;i < 10;i++)
{
std::cout<<"count : "<<i<<std::endl;
}
}
private:
pthread_mutex_t m_Mutex;
};
void* StartThread1(void*)
{
Base base;
base.Do();
}
void* StartThread2(void*)
{
Base base;
base.Do();
}
int main()
{
....
pthread_create(Thread1, 0, StartThread1, 0);
pthread_create(Thread2, 0, StartThread2, 0);
pthread_join(Thread1, 0);
pthread_join(Thread2, 0);
....
}
이 코드에서는, 당연히 원하는대로 동기화가 이루어지지 않는데, 뮤텍스가 각각의 인스턴스에 따로 존재하기 때문이다. 당연히, StartThread1과 StartThread2에서 잠금이 설정된 뮤텍스는 서로가 완전히 무관한 다른 인스턴스이며, 작업은 당연하게도 동기화되지 않는다. 작업을 원하는대로 동기화하기 위해서는, Base 클래스가 전역으로 선언되어 하나의 인스턴스에 대해서만 Do()를 호출하거나, 클래스마다 각각의 인스턴스로 생성되도록 선언되어 있는 뮤텍스를 static으로 선언하여 모든 인스턴스가 하나의 동기화 개체만 참조하도록 해야 한다. 혹은 뮤텍스를 전역 범위로 선언하여 이를 참조할 수도 있지만, 이 전역 개체에 대해 무제한적인 접근이 허락된다면 좋은 방법이 아니다. 첫번째 방법은 추가적인 싱글턴 인프라가 필요하며, 두번째 방법은 동기화가 필요한 클래스마다 static으로 선언된 뮤텍스를 가져야 한다. static으로 선언된 멤버는 개체가 생성될 때마다 다시 생성되는 것이 아니므로, 같은 타입의 개체들이 하나의 뮤텍스를 공유해야 하는 상황인 경우 적합한 방법이다.
결론적으로, 어떤 작업을 동기화하고자 한다면, 경쟁 조건이 발생할 수 있는 같은 범위의 각각의 작업들은, 같은 뮤텍스를 사용하여 동기화를 해야 한다. 유효한 뮤텍스를 얻어내기 위한 범위라는 것보다, 동기화하고자 하는 작업에 대해서의 뮤텍스 동기화가 더 적절한 표현인 것 같다.
여튼, 뮤텍스를 사용하고자 마음 먹었다면, 중요한 것은 생성한 뮤텍스의 범위를 어떻게 설정하는가이다. 즉, 여러개의 스레드가 존재하고, 이 스레드들이 공유된 메모리 영역에 접근하고자 할 때 이 공유 영역을 임계 영역이라 한다면, 이 경우 뮤텍스를 설정하지 않았다면 경쟁 조건(race condition)이 발생한다. 이 경우 반드시 뮤텍스로 임계 영역에 대해서 적절히 통제해야 하는데, 문제는 이 뮤텍스는 항상 전역 변수로 설정되어 있어야 하는가? 그렇지 않다면, 어느 범위에 이 뮤텍스를 설정해야 적절하게 통제가 가능한가?
뮤텍스는 반드시 전역 변수로 선언될 필요는 없다. 하지만, 실행하고자 하는 작업이 서로 적절하게 동기화가 되려면, 그 작업들을 동기화 해주는 뮤텍스는 같은 뮤텍스여야 한다. 그 범위에 맞추어서 유효한 뮤텍스의 범위를 설정하면 될 것이다.
예를 들어보자. Base 클래스의 Do() 메서드를 두 개의 스레드에서 동시에 실행하면서, 적절한 동기화를 제공하고 싶다. 그렇다면 다음의 방법은 맞는가?
class Base
{
public:
Base() { .... }
~Base() { .... }
void Do()
{
pthread_mutex_lock(&m_Mutex);
for(int i = 0;i < 10;i++)
{
std::cout<<"count : "<<i<<std::endl;
}
}
private:
pthread_mutex_t m_Mutex;
};
void* StartThread1(void*)
{
Base base;
base.Do();
}
void* StartThread2(void*)
{
Base base;
base.Do();
}
int main()
{
....
pthread_create(Thread1, 0, StartThread1, 0);
pthread_create(Thread2, 0, StartThread2, 0);
pthread_join(Thread1, 0);
pthread_join(Thread2, 0);
....
}
이 코드에서는, 당연히 원하는대로 동기화가 이루어지지 않는데, 뮤텍스가 각각의 인스턴스에 따로 존재하기 때문이다. 당연히, StartThread1과 StartThread2에서 잠금이 설정된 뮤텍스는 서로가 완전히 무관한 다른 인스턴스이며, 작업은 당연하게도 동기화되지 않는다. 작업을 원하는대로 동기화하기 위해서는, Base 클래스가 전역으로 선언되어 하나의 인스턴스에 대해서만 Do()를 호출하거나, 클래스마다 각각의 인스턴스로 생성되도록 선언되어 있는 뮤텍스를 static으로 선언하여 모든 인스턴스가 하나의 동기화 개체만 참조하도록 해야 한다. 혹은 뮤텍스를 전역 범위로 선언하여 이를 참조할 수도 있지만, 이 전역 개체에 대해 무제한적인 접근이 허락된다면 좋은 방법이 아니다. 첫번째 방법은 추가적인 싱글턴 인프라가 필요하며, 두번째 방법은 동기화가 필요한 클래스마다 static으로 선언된 뮤텍스를 가져야 한다. static으로 선언된 멤버는 개체가 생성될 때마다 다시 생성되는 것이 아니므로, 같은 타입의 개체들이 하나의 뮤텍스를 공유해야 하는 상황인 경우 적합한 방법이다.
결론적으로, 어떤 작업을 동기화하고자 한다면, 경쟁 조건이 발생할 수 있는 같은 범위의 각각의 작업들은, 같은 뮤텍스를 사용하여 동기화를 해야 한다. 유효한 뮤텍스를 얻어내기 위한 범위라는 것보다, 동기화하고자 하는 작업에 대해서의 뮤텍스 동기화가 더 적절한 표현인 것 같다.