삽질의 현장/- 윈도우 시스템

#021_WIndow_System_쓰레드 동기화

shovelman 2015. 9. 24. 16:02


안녕하세요 삽잡이입니다.


지난 시간에 멀티 쓰레드들이 공유 메모리를 접근할 때

발생하는 문제점을 어떻게 해결할지에 대해서 생각해보는 시간을 가졌었습니다.


이번 시간에는 동기화에 대해서 알아보도록 하겠습니다.

지난시간의 예시를 잠깐 살펴보도록 하겠습니다.


다수의 쓰레드가 있을 때 A라는 쓰레드는 Write 중이고,

다른 쓰레드들이 Read 중이라고 가정해보겠습니다.


이때 공유하고 있던 메모리가 수정이 되는 일이 발생함으로, 

쓰레드들은 자신이 모르는 사이에 데이터가 변경됨으로 혼란이 옵니다.

따라서 Memory Fault가 발생되는데,

이를 해결하기 위해 서로 데이터가 호환 가능하도록 해야합니다.

이를 우리는 동기화라고 부르게 되는 것입니다.

쓰레드에서는 공유 변수, 공유 메모리, 공유 데이터에 대한 동기화 부분이 가장 어렵고 신경써야할 부분입니다.


동기라는 뜻은 '순서 있는' 으로 통합니다.

그와 반대인 비동기는 '순서 없는', '동시에'라는 뜻으로 통합니다.


동기화를 하기 위해서는 '동기화 오브젝트'라는 녀석이 필요합니다.

동기화 오브젝트(객체, 요소)에는 여러가지 요소들이 있겠지만,

핵심적인 내용으로는 뮤텍스, 세마포어, 이벤트가 있습니다.

이 셋을 우리는 '커널 수준의 동기화 객체'라고 부릅니다.

이와 좀 다른 특성으로는, 유저 모드 동기화 객체가 있는데,

이는 크리티컬 섹션, 인터락등이 있습니다.


둘의 차이점에 대해서 생각해봅시다.

커널 모드 동기화 객체의 경우, 

동기화를 수행하기 위해서 시스템 수준에서 동작하는 특징이 있습니다.

유저 모드 동기화 객체의 경우, 

동기화를 수행하기 위해서 어플리케이션 수준에서 동작한다는 특징이 있습니다.


위에서 언급한 뮤텍스, 세마포어, 이벤트는 동기화 오브젝트이면서, 커널 객체입니다.

따라서, 프로세스간에 공유될 수 있다는 특징을 가지고 있습니다.

ID를 가지고 언제든지 다시 오픈하고, 핸들을 받아 할당받을 수 있다는 것입니다.

이와 반대로 유저 모드 객체는 프로세스 내에서만 사용하고, 핸들이 없다는 특징을 가지고 있습니다.


아니 그래서 동기화가 뭐라는 것이죠...

동기, 비동기라고 하는데, 동기는 Sync, 비동기는 Async라고 부릅니다.

Synchronize라고 하는데, Synchronize Swimming을 생각해봅시다...




아름다운 사진입니다. 똑같이 동작을 맞추는 스포츠이죠...

마치 한 사람이 동작하는 것처럼 말입니다.


동기적이라면...

즉, 여러 쓰레드들이 동작해도 단 하나의 쓰레드가 수행되는 것 처럼 보여야합니다.


A, B라는 두개의 쓰레드가 있다고 생각해봅시다.

A 쓰레드가 어떤 행위를 하면, B는 아무것도 못하고,

B가 어떤 행위를 하면 A는 아무것도 못하는 것과 같이...

누군가가 반응할 때 다른 놈은 반응하지 못하는 것을 동기화라고 하고, 

'순서 있는'이라고 하는 것입니다.

결과적으로 시간에 따른 프로세스라고 할 수 있습니다. 순서가 지켜지는 프로세스라는 것입니다.


작업이 하나만 있으면, 동기화, 비통기화 라는 단어가 존재할 수 없습니다.

그냥, 정말 하나이기 때문입니다.

동기, 비동기는 두개 이상의 것들이 존재할 때 비로소, 단어를 사용할 수 있게 됩니다.


독립적으로 동시에 실행될 수 있으면, 이는 '비동기적'이라고 부를 수 있습니다.

비동기라고 하면, 독립적으로 동시 작업을 수행하는 것이라고 생각하면 된다 이겁니다.

비동기 작업이란 한마디로, 

세개의 쓰레드가 실행중이라면, 각자 자기네 작업을 독립적으로 수행한다는 것입니다.


자... 그렇다면 동기적으로 만들고 싶다면 어떻게 해야할까요?

다시 질문을 해보겠습니다.

비동기적으로 동작하는 독립적인 작업들을 동기적으로 만들고자 한다면 어떻게 해야할까요?

여러 쓰레드들 중에 단 하나의 쓰레드만이 작업을 하고,

그 작업이 끝나면 또 다른 놈이 작업하도록 만들면 됩니다.

'한놈이 반응할 때 다른 놈들은 정지해있는다' 이런 것이죠...


동기화를 하는 목적은 크게 두가지입니다.


우선, 공유 데이터 보호를 하기 위해서입니다.

즉, 공유 데이터가 더렵혀지는 현상을 보호할 수 있도록 하기 위해서입니다.


다른 목적은, 순서를 제어하기 위해서입니다. 예를 들어보겠습니다.

어떤 데이터를 입력받아서 가공한 다음에(계산하고) 

화면에 출력하고, 파일에 출력하고, DB에 출력해야하는 임무가 주어졌다고 해봅시다.


여기에는 다섯개의 작업이 수행되고 있군요...

'입력', '계산', '결과를 프린터에 출력', '화면에 출력', '파일에 출력' 이렇게 말입니다...

작업의 양으로 따지면 다섯개의 작업이 있는데, 이 다섯개의 작업을 동시에 시행하겠다고 해봅시다...

그러면 문제가 발생하지요...

데이터를 입력받고 계산을 해야지 어떤 출력이던 할 수 있지 않겠습니까...

즉, 앞에 것들이 안되면 나머지 작업이 불가한 상황입니다.

적어도 입력을 받고, 계산하는 작업까지 완료가 되고나면,

나머지 작업들은 독립적으로 수행되어도 아무 상관이 없습니다.

이와 같은 것을 순서 제어라고 할 수 있습니다.


참고로, 공유 데이터를 보호하기 위해 동기화를 하는 경우가 80~90% 라면,

순서 제어를 위해 동기화를 하는 경우는 10~20% 정도 된다고 합니다.


동시 수행하는 작업이 없다면 동기화는 필요 없습니다.

동기화는 당연한 말이지만 항상 비동기적인 작업에 나타납니다.

왜? 그 비동기적인 작업을 순서화하기 위해서입니다.

그 비동기적인 작업을 동기적인 작업으로 시행시키기 위해서입니다...


이를 위해서는 동작의 양을 알고 있어야 합니다.

즉, 어떤 작업 때까지 동기화를 시킬 것인지에 대해서 판단을 하고 

당 작업이 완료될 때까지 다른 작업이 동시 수행되는 것을 막겠다는 판단을 할 수 있어야 된다는 것입니다.


정말로 중요해서 다시 반복하지만,

공유 메모리는 서로 다른 쓰레드가 Rewrite 하는 경우 

반드시 동기화를 통해 단 하나의 쓰레드만이 접근할 수 있도록 보호시켜야합니다....


자... 이제 동기화에 대해서 간략하게(?) 설명을 마무리 하겠습니다.

다음 시간에는 동기화 기법들에 대해서 살펴보도록 하겠습니다.


이상 삽잡이였습니다!