posted by REDFORCE 2017. 3. 23. 11:02

이번 글에서는 std::thread 의 공유자원에 대한 사용 방법과


mutex / lock 에 대해서 정리해보도록 하겠습니다.




공유 자원 사용하기

  • 멀티스레드 프로그래밍에서는 공유 리소스 관리가 가장 큰 문제
  • 너무 빡빡하게 관리하면 성능 하락의 위험
  • 너무 느슨하게 관리하면 언제 터질지 모름
  • mutex를 사용하여 공유 리소스를 관리하는 것이 가장 일반적인 방법
  • mutex는 크리티컬 섹션 (Windows에서는)을 사용한다.



#include <mutex> 헤더가 필요하고~


std::mutex mtx_lock를 선언하여

lock() / unlock() 함수를 통해 공유 자원에 접근하고 관리 할 수 있습니다.


( [&] 람다가 등장해 있군요. 다시 복습차원에서... [&] 캡쳐를 했다는 것은 스코프 영역의 모든 변수를 참조 한다는 뜻 입니다! )



공유 자원을 사용 할 수 있는지 조사


그럼 공유 자원을 mutex를 통해 lock / unlock만 하면 되냐~?

당연히 그걸론 부족하조.


다른 쓰레드가 lock을 걸어서 자원을 쓰고 있는지 안쓰고 있는지 검사를 해야겠지요.


순서를 정리해보자면 다음과 같습니다.

  • (1) 스레드 A 가 mutex의 lock 을 호출 했을 때,
    (2) 이미 스레드 B에서 lock을 호출 했다면?
    (3) A는 B가 lock을 풀어줄 때 까지 대기한다.

  • 스레드 A는 일을 하고 싶어도 할 수가 없다.
    이때 try_lock() 을 사용한다!

  • 다른 스레드가 먼저 락을 걸었다면 대기하지않고 즉시 false를 반환,
    반대로 true를 반환한 경우 공유 리소스의 소유권을 가진다.


자동으로 lock 풀기


그럼 이번에는 혹여나 실수로 unlock을 호출하지 않은 경우는 어떻게 되는가?


당연히 실수로 unlock을 호출하지않고 나와버렸으니 스레드는 데드락에 빠지는 상황에 놓입니다.

이 상황은 프로그래머의 코드 상의 실수 보다는

스레드가 코드 수행 중 예외 상황이 발생하여 빠져 나가버리는 경우가 큽니다.

(프로그래머가 이런 실수를 했다면 그건...야근을 너무 많이 한 탓일 수도 = _=;;)


해서 이런 문제가 발생할 경우를 대비해


lock_guard 라는 유틸리티 클래스를 사용합니다.


scope를 벗어날 때 자동으로 unlock을 호출하는 녀석인데요.




std::lock_guard<std::mutex> guard(mtx_lock) 부분을 보시면


먼저 std::lock_guard<std::mutex> 타입형으로 guard 라는 변수와 함께 파라미터로 ( mtx_lock ) 을 받습니다.


따라서 뮤텍스를 받는 녀석이라는 건데


이녀석을 알아서 스레드 안에 넣어두면 guard가 함수의 스코프가 벗어날 때 unlock을 호출하여 줍니다.





이번엔 데드락 상황을 한번 연출 해보고 


빠져나가는 방법을 적어보도록 하겠습니다.



상황은 위 코드와 같이 isCheck( ) 안에서 guard를 통해 lock1 이 만들어졌을 상황인데


아래 다른 쓰레드가 Add ( )를 호출하는 바람에 또 다시 Guard가 만들어져 lock2 만들어지게 되면


결국 데드락 상황에 빠지게 됩니다.



해서 이런 반복적인 락으로 인해 빠지는 데드락을 회피 하고자 쓰는 mutex가 있는데요.


바로 std::recursive_mutex 를 이용 하시면 됩니다.




m_Mutex 선언 부를 보시면 타입이 std::recursive_mutex로 바뀐 것을 볼 수 있습니다.


아직 저도 정확히 recursive_mutex는 어떤식으로 회피를 하는지는 모르겠습니다만 원초적인 원리를 알게 되면 따로 또 포스트를 적겠습니다.




쓰레드를 딱 한번만 실행

  • 멀티스레드 환경에서 프로그램 실행중 단 한번만 코드 실행이 필요 할 때
  • 보통은 이중 조사로 구현하지만...(실수가 발생 할 수 있음)
  • std::call_once 를 사용하면 쉽게 가능~



사용 방법은 위와 같이


(1) std::once_flag 를 이용하여 플래그 값을 먼저 세워두시고 

(2) std::call_once 를 통해 플래그 값과 펑션을 던져 주시면 됩니다.


만약 이 플래그 값을 통해 펑션을 수행했다면 flag 값이 변경되고 나 사용했음~~ 하고 표기할 수 있게 됩니다.


이를 응용하여 스레드에서 flag값을 보고 이거 한번 해야되는데...했었나? 안했었나?? 하는걸 알 수 있게 됩니다.




여기까지 Modern C++ 12편으로 정리한 내용을 마치겠습니다.


부족한 내용이나 설명이 부실 한것도 많습니다만.



= _=)..아몰랑 일단 나만 알면되...