posted by REDFORCE 2017. 4. 5. 14:48

아는 웹툰 그리는 지인에게 커미션을 요청했었는데, 어제 밤 완성 된 개인 캐릭터가 도착했다.


요청한 내용은 저자 본인을 뜻하는 컨셉의 캐릭터를 그려달라는 커미션이었다.



완성 된 그림은 아래 3개.


추후에 계속 틈틈이 여러가지 모션을 더 요청해서 그려달라고 할 계획이다.


캐릭터 컨셉에 대한 내용은 

1. 캐릭터 : 늑대 귀/꼬리/후드/안경/프로그래밍 작업중인 모습

2. 구도 : 정면

3. 의상 : 면바지 / 후드


로 요청했었다.


만든이 : 김낑


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편으로 정리한 내용을 마치겠습니다.


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



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

posted by REDFORCE 2017. 3. 23. 09:31

와...드디어 마지막 목차 인 thread로 왔습니다.


이틀 만에 이걸 싹다 다시 공부하며 정리하면서 올렸는데


다시 상기가 많이 된 것 같군요.


(근데 왜 정작 쓸 때는 또 다시 켜서 보는걸까.....= _=난 빠가야로 였단말인가)



목차


1. Auto

2. range based for

3. enum class

4. non-static data member initializers

5. initializer lists

6. default / delete definition

7. override / final

8. emplacement

9. constexpr

10. lambda

11. random

12. thread



12. thread


이번 글은 마지막인 thread에 대해서 정리해보도록 하겠습니다.


프로그래밍의 꽃이라 부를 수 있는 thread ( 쓰 뤠 두으우엑 우엑...공부할 때 마다 극혐 토나온닷..)



여기서 정리하는 thread는 C++ 표준 thread 라이브 러리 입니다. (std::thread)


#include <thread> 헤더가 필요하며


각 OS API의 쓰레드보다 사용하기가 쉽다는 장점이 있습니다.



std::thread의 사용 법은 아래와 같이 간단합니다.



std::thread 타입으로 thread를 선언하고 ( ) 안에 함수 내용을 적으시면 됩니다.

위와 같이 람다로 구현하는 방법도 가능합니다.



thread를 생성하고 특정한 시기에 동작하게 하려면 다음과 같이 수행하면 됩니다.



위 코드와 같이 myThread를 선언만 해놓고

정의는 추후에 하는 방식으로 내가 원하는 타이밍에 동작하게 할 수 있습니다.


다음, thread의 파라미터 값을 넣는 것도 아래와 같이 수행하면 넣을 수 있습니다.



위 코드의 마지막 줄에 람다 함수가 끝나고 " , 4" 를 넣어준것 과 같이 파라미터를 전달 하면 됩니다~



이번에는 클래스의 멤버 함수를 스레드에서 사용하는 방법입니다.


아마 이 방법이 제일 궁금하셨을텐데요.


위에 적어둔 방법들과 똑같이 thread의 매개변수로 펑션만 던져주시면 됩니다.




쓰레드 대기


thread 클래스의 join 함수를 사용하여 스레드가 종료 할 때 까지 대기하는 방법입니다.


 - join() 함수를 호출하면 blocking이 된다.

 - join() 함수를 호출할 수 있는 지 알기 위해서는 joinable() 함수를 사용한다.



쓰레드 식별자


쓰레드를 식별 하는 방법은 get_id() 함수를 사용하면 해당 쓰레드의 식별자를 얻을 수 있습니다.


get_id() 함수를 통해서 멀티쓰레드에서 각각의 쓰레드를 구분 할 수 있지요.


get_id() 함수를 사용하면 멀티스레드에서 공용 리소스에 접근하는 스레드를 알 수 있고,

특정 스레드만 접근할 수 있게 하는게 가능합니다.



thread 오브젝트와 (커널)스레드 분리

  • detach 함수를 사용하면 thread 오브젝트와 스레드 연결 고리를 떼어낸다.
  • detach 이후에는 thread 오브젝트는 제어 할 수 없다.
  • detach 와 스레드의 종료는 상관 없다.



thread를 한번 detach 한 순간 부터는 그 쓰레드는 완전 독립이 되기 때문에 join과 같은 함수를 사용 할 수가 없습니다.


따라서 그 쓰레드가 언제 어떻게 될진 그 쓰레드한테 달려있다고 볼 수 있지요.


저는 개인적으로 이런식으로 detach를 쓸일이 있나? 하는 생각도 듭니다만...


= _=뭐 언젠가 필요한 때도 있겠지요.



일시중지와 양보

  • sleep_for 와 sleep_until을 사용하면 스레드를 일시 중지 시킬 수 있다.
  • sleep_for 는 지정한 시간 동안 ( 예: 100 밀리 세컨드 동안만 정지)
  • sleep_until 은 지정 시간까지 ( 예: 16시 10분까지 정지)
  • yield를 사용하여 자신(스레드)의 활동을 포기하고 다른 스레드에게 양보한다.
    std::this_thread::yield();


thread 종료
  • 스레드가 실행 중에 프로그램이 종료되면 crash가 발생 할 수 있다.
  • 프로그램 종료 전에 꼭 스레드를 먼저 종료 시키고 프로그램을 종료하도록 한다.



여기 까지가 기본적인 std::thread에 대한 정리였습니다.


그리고 더 이제 심화적이고 중요한 thread의 공유자원 사용하는 방법과 mutex / lock 에 대해서 다음 글을 통해 살펴보도록 하겠습니다.