LISTORY

[윈도우즈 시스템 프로그래밍] 실행 순서에 있어서의 동기화 본문

IT/윈도우 프로그래밍

[윈도우즈 시스템 프로그래밍] 실행 순서에 있어서의 동기화

LiStoryTeller 2018. 9. 21. 21:00


뇌를 자극하는 윈도우즈 시스템 프로그래밍 책 관련 유투브 강의 내용 정리이다.


이번에 정리할 내용은 14장. 쓰레드 동기화 기법2 부분이다.


YouTube 주소 : 실행 순서에 있어서의 동기화



쓰레드 동기화 기법


13장에서는 임계영역의 접근 동기화에 대해 다루었다.


이번 시간은 지난 시간에 이어 쓰레드의 순서 동기화 부분에 대해 다루도록 하겠다.


쓰레드의 순서는 왜 동기화 해야하는걸까?


교제에서는 생산자/소비자 모델을 제시하였다.



생산자/소비자 모델



이 모델은 보는 관점에 따라 I/O 모델이기도 하고, 쓰레드 모델이기도 하고, 둘 다이기도 하다.


하는 일은 간단하다. 일단, 데이터를 입려해야 한다.


보통 데이터 입력을 말하면 콘솔 생각을 하는 경우가 많은데, 좀더 다양하게 생각해 봐야 한다. 


여기서는 외부로부터 오는 데이터 입력이라고 해보자.


이 입력된 데이터를 출력, 혹은 가공해서 이용하는 모델이 있다고 가정해보자.


다시 단순하게 말하자면 데이터를 입력하고, 이 데이터를 가공 및 출력하면 된다.


이 입력과 출력을 번갈아 가면서 실행중이다. 근데, 여기서 하나의 문제가 있다.


출력은 이미 싸이클이 정해져있다. 시간도 예상할 수 있다.


출력은 입력된 데이터의 크기만큼 출력되므로, 입력에 의존적이다.


반면, 입력은 외부환경에 의존적이다.


외부로부터 데이터가 많으면 입력해야할 데이터가 많다.


여기서 문제가 발생한다. 10바이트씩 데이터가 들어오다가 어느 순간 외부 요인으로 인해 10메가바이트가 들어왔다.


이럴 경우, 이 데이터를 처리할 리소스를 가지고 있지 않으면 데이터의 손실이 발생할 수 있다.


물론 읽기만 하면 문제가 안생기지만, 한번 읽고 데이터를 출력해야할 경우, 손실이 발생할 수 있다.


그래서 나온 디자인이 다음과 같다.



입력, 출력 모델을 디자인할 때, 입력, 출력 쓰레드를 따로 생성한다.


입력은 데이터 입력만 받고, 받은 데이터를 출력용 버퍼에 넣어둔다.


그렇다면 출력용 버퍼가 버티는 한도 내에서는 출력이 아무리 더뎌도 문제가 되지 않는다.


여기서 데이터를 받는 쪽 쓰레드를 생산자 쓰레드, 상대되는 개념을 소비자 쓰레드라고 한다.


중요한건 순서이다. 생산자 스레드가 먼저 실행이 되고 그 다음에 소비자 스레드가 실행되어야 한다.


왜냐하면 생산자 스레드가 실행되는 도중, 소비자 스레드가 실행될 경우,


버퍼에 있는 값을 소비자 스레드가 함부로 가져갈 수 있고, 이렇게 될 경우 그 값은 쓰래기 값이 되기 때문이다.




⊙ 이벤트 기반 동기화


생산자 쓰레드가 있고 소비자 쓰레드가 있다.


생산자 쓰레드는 어떠한 영역에 데이터를 가져다 두고, 소비자는 그것을 가지고 간다. 이 영역을 버퍼라고 한다.


여기서 생산자와 서비자가 약속을 한다.


생산자 : 내가 데이터를 다 가져다 놓으면 너가 내 뒤를 이어 실행해라


문제는 생산자가 일을 언제 다 끝냈는지 알수 없다. 이는 생산자 본인만 알 수 있다.


그래서 이 둘은 커널 오브젝트를 만들고, 처음 생성됐을 시 non-signaled 상태가 되게 하기로 약속하였다.


생산자 쓰레드의 할 일이 다 끝나면 이를 알려 소비자 쓰레드가 실행되어야 하는데,


이를 위해 생산자 쓰레드는 할 일을 모두 마쳤을 경우, 이를 Signaled 상태로 바꾸어주기로 약속하였다.


즉, 소비자 쓰레드는 이 커널 오브젝트의 상태를 계속 관찰해야하는데, 이는 WaitforSingleObject() 함수를 사용한다.


물론 신호를 기다리는 동안 소비자 쓰레드는 blocking 상태이다.


생산자는 할일 다하면 커널 오브젝트의 상태를 바꿔주는데, 이는 SetEvent() 함수를 사용한다.



⊙ 수동 리셋 모드 이벤트 & 자동 리셋 모드 이벤트


이벤트 생성은 CreateEvent() 함수를 통해 가능하다.


근데 생성 시, 이벤트를 수동 리셋 모드, 또는 자동 리셋 모드로 설정할 수 있다.


각각의 모드에 간단히 설명하자면, 수동 리셋 모드는 이벤트 오브젝트의 상태를 일일이 수동으로 해주어야 하는 것이고


자동은 어느정도 상태가 자동으로 변경되는 모드이다.



자동 리셋 모드 이벤트라고 모든 신호가 자동으로 와서 상태가 자동으로 바뀌는 것은 아니다.


이벤트, 즉 Signaled 상태로 바꾸는 신호를 주는 건 여전히 수동이다.


왜냐하면 일이 끝났는지 여부는 생상자만이 알 기 때문이다. 


일이 끝나면 생산자는 SetEvent() 함수를 날려서 Signaled 상태로 바꾸어 주어야 한다.


하지만 이벤트 오브젝트가 Signaled 상태에서 Non-Signaled 상태로 가는건 자동이다.


WaitForSingleObject() 함수로 인해 소비자 쓰레드는 Blocking 상태이다.


Signaled 상태가 되면 쓰레드가 빠져나옴과 동시에 이벤트 오브젝트가 자동으로 Non-Signaled 상태가 된다.



그러면 어떠한 경우에 각각의 모드를 사용할까?


커널 오브젝트가 두개 있다. 하나는 자동 리셋 모드이고 하나는 수동 리셋 모드이다.


둘 다 쓰레드 A,B 가 대기중이다.


생산자 쓰레드가 자동 커널 오브젝트를 Signaled 상태로 바꾸었다.


그럼 WaitForSingleObject() 함수를 호출하여 대기하고 있는 두개의 쓰레드 중 하나만 빠져나오고 


한 쓰레드가 빠져나오자마자 커널 오브젝트는 다시 Non-Signaled 상태로 바뀐다.


즉, 둘 중 하나만 실행 기회를 얻는다.



반면 수동 리셋 모드일 경우, 


Signaled 상태로 바꾸면 계속 Signaled 상태이기 때문에 a,b 쓰레드가 동시에 빠져나온다.


결론적으로 정리하자면,


한 순간에 하나의 쓰레드만 깨어나게 하고 싶다? 자동 리셋 모드 사용


둘 이상의 쓰레드가 동시에 깨어나게 하고 싶다? 수동 리셋 모드 사용


다음 시간엔 관련된 예제를 다루도록 하겠다.









Comments