mutex 사용하기
하기 사이트에서 발췌하였습니다. 문제가 된다면 삭제하겠습니다.
http://jacking.tistory.com/1066
병렬 프로그래밍을 해보면 싫든 좋든 여러 스레드에서 하나의 객체에 동시에 접근하는 경우가 발생합니다. 앞선 std::thread에 관한 글을 보면 소개한 예제 중 그런 경우를 보실 수 있을 겁니다.
< 예제 >
#include <thread>
#include <iostream>
int main()
{
std::thread Threads1( [] ()
{
for( int i = 0; i < 5; ++i )
{
std::cout<< "Thread Num : " << i << std::endl;
}
} );
std::thread Threads2;
Threads2 = std::thread( [] ()
{
for( int i = 10; i < 15; ++i )
{
std::cout<< "Thread Num : " << i << std::endl;
}
} );
std::thread Threads3 = std::thread( [] ( int nParam )
{
for( int i = 20; i < 25; ++i )
{
std::cout<< "Thread Parameter : " << nParam << std::endl;
}
}, 4 );
getchar();
return 0;
}
위 예제에서 ‘std::cout’ 각 스레드에 동시에 접근하고 있습니다. 그래서 실행하면 아래와 같은 결
과가 나옵니다.
실행 화면을 보면 출력 결과 중 일부가 뒤죽박죽 되어 있는 것을 알 수 있습니다. 이유는 Thread1 스레드에서 출력스트림 결과를 다 출력하기 전에 Threads2가 출력스트림을 사용하여 발생한 문제입니다. 이런 것을 data-race(데이터 경합) 이라고 합니다.
그런데 위 예제를 여러 번 실행해 보면 아래처럼 올바르게 나올 때도 있습니다.
사실 이런 부분이 병렬 프로그래밍의 어려움 중의 하나입니다. 여러 스레드에서 하나의 공유 자원을 사용할 때 타이밍에 의해서 순서대로 사용할 수도 있고 그렇지 못할 수도 있기 때문에 버그 발생이 언제 어떻게 생길지 알기 힘듭니다.
병렬 프로그래밍을 할 때는 언제나 공유 자원을 최소한으로 하고, 공유 자원은 꼭 동기화 객체를 사용하여 여러 스레드가 동시에 사용하지 못하도록 해야 합니다.
위의 문제는 동기화 객체를 사용하면 해결 할 수 있습니다. 여기서는 앞선 글에서 사용한 적이 있는 뮤텍스(std::mutex)를 사용하겠습니다.
그럼 위 예제를 mutex를 사용하여 문제를 해결해 보겠습니다.
<예제 1 >
#include <thread>
#include <iostream>
#include< mutex>
int main()
{
std::mutex mtx_lock;
std::thread Threads1( [&] ()
{
for( int i = 0; i < 5; ++i )
{
mtx_lock.lock();
std::cout<< "Thread Num : " << i << std::endl;
mtx_lock.unlock();
}
} );
std::thread Threads2;
Threads2 = std::thread( [&] ()
{
for( int i = 10; i < 15; ++i )
{
mtx_lock.lock();
std::cout<< "Thread Num : " << i << std::endl;
mtx_lock.unlock();
}
} );
std::thread Threads3 = std::thread( [&] ( int nParam )
{
for( int i = 20; i < 25; ++i )
{
mtx_lock.lock();
std::cout << "Thread Parameter : " << nParam << std::endl;
mtx_lock.unlock();
}
}, 4 );
getchar();
return 0;
}
< 실행 결과>
std::mutex를 사용하기 위해서는 아래의 헤더 파일을 추가합니다.
#include <mutex>
스레드에서 공유 자원을 사용할 때는 아래 처럼 락을 걸어서 뮤텍스 mtx_lock의 소유권을 얻어서 다른 스레드가 접근하지 못하도록 합니다. 그리고 공유 자원을 다 사용하였다면 락을 해제하여 mtx_lock의 소유권을 버립니다.
mtx_lock.lock();
std::cout << "Thread Num : " << i << std::endl;
mtx_lock.unlock();
락을 건 후(lock) 락을 풀(unlock) 때까지는 다른 스레드는 대기를 하고, 락을 풀면 대기하고 있는 스레드 중 하나가 락을 건 후 공유 자원을 사용합니다.
그러므로 락을 건 후에는 꼭 락을 풀어야 하고(만약 풀지 않으면 데드락 상황에 빠지게 됩니다), 락을 사용하는 부위가 너무 빈번하면(스레드 대기가 늘어나므로) 병렬 프로그래밍의 장점이 많이 약해집니다.
try_lock
std::mutex는 lock과 unlock 이외에 try_lock 이라는 멤버 함수가 있습니다.
bool try_lock();
스레드에서 lock을 호출할 때 다른 스레드가 이미 lock을 호출하여 뮤텍스의 소유권을 가진 후 아직 unlock을 호출하지 않았다면 그 자리에서 대기를 합니다. 그러나 try_lock은 대기를 하지 않고 false를 반환합니다(뮤텍스의 소유권을 얻지 못한 경우). 반대로 true를 반환한 경우는 뮤텍스에 대한 소유권을 가지게 되므로 공유자원을 안전하게 사용할 수 있습니다.
try_lock을 사용하는 경우는 만약 공유 자원을 다른 스레드에서 사용 중이면 스레드가 대기하면서 그냥 시간을 소비하지 않고 또 다른 작업을 하도록 할 때 사용하면 좋습니다.
ps : 참고로 std::mutex는 Windows OS에서는 내부적으로는 크리티컬섹션을 사용합니다.