Thread 동기화 객체 및 IPC의 선택

-- VC++ 2011. 8. 26. 15:36
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

동기화객체에 대해 예전에 정리할 때 참조했던 스레드인데, 이제야 정리해둔다.

최근 아래 그림과 같은 구조로 두 프로세스 사이에 통신을 해야 하는 코드를 만들어보았다. 먼저 Process A의 스레드 중 한번에 하나씩만 Process B에 요청을 할 수 있고, Process B는 작업 처리 후 다시 돌려주는 것이다. 그런데 별 생각 없이 사용하였던 Win32 semaphore, event가 생각보다 큰 오버헤드로 적지 않은 고생을 하였다.

1. 프로세스간의 통신 (IPC)

IPC
에는 무지하게 많은 방법이 있다. 간단하게 SendMessage/Post(Thread)Message를 이용한 방법이 있으며, Mailsot, Pipe 등이 있다. 먼저 메세지를 사용하려고 했으나 여러 문제가 있었다. SendMessage를 사용하려면 윈도우 프로시져가 필요하고, , 윈도우 객체를 만들어야 하는 부담이 있었으며, 프로그램의 구조를 비동기적인 구조로 만들어야 한다. , 연속적인 스텝으로 Process A B가 데이터를 주고 받아야할 때 메세지 루프를 다시 거쳐야하는 거시기한 꼴이 나온다. (물론 시퀀셜하게 GetMessage를 돌릴 수는 있다.) 그러나 무엇보다 오버헤드가 크고 전달할 수 있는 데이터의 크기도 매우 제한적이다. (송신 8바이트, 수신 4바이트) 물론 WM_COPYDATA가 있지만 고려하지 않았다.

그래서
 Memory-mapped file(CreateFileMapping)을 이용한 shared memory를 사용하기로 하였다. 그리고 요청과 응답이 도착했다는 사실을 알려주기 위해 event 객체(CreateEvent)를 가지고 IPC 메카니즘을 만들었다. MMF는 거의 오버헤드 없이 두 프로세스 사이에 데이터를 교환할 수 있으나,
정작 데이터 수신과 송신을 알려주는 이벤트가 생각보다 오버헤드가 컸다. 만약 SendMessage를 두 프로세스 사이에 썼더라면 이런 오버헤드는 피할 수가 없다. 그러나, Process A의 스레드가 Process B로 부터 응답을 받을 때까지 event 객체를 기다리는 것이 아니라 무식해보이지만 busy waiting을 하면 오히려 throughput이 훨씬 증가함을 직접 확인하였다.(참고 1, 2, 3)

 

__forceinline static void WaitForHeapServerResponse(HS_QUEUE* pQ)
{
#ifndef _DEBUG
    DWORD nSpin = 0;
    while (
pQ->req.dwSpin == 0
)
    {
        if ((++nSpin & 63) == 0)
           
SleepEx
(0, FALSE);
    }
    pQ->req.dwSpin = 0;
    return;
#else
   
WaitForSingleObject
(g_hEventHSRes, INFINITE);
#endif
}

 

무식하게 루프를 돌며 잠깐 잠깐 SleepEx를 호출하는 경우가 WaitForSingleObject를 이용하는 것 보다 훨씬 좋은 성능을 보였다. 물론 CPU 점유율이 올라가는 단점이 있어서 문제가 되지만, 최근 많이 사용되는 듀얼 코어 CPU를 생각하면 크게 문제가 되지 않는 것 같다. WaitFor...의 단점은 이 함수는 kernel-user mode state transition을 유발한다는 점이고, 이는 수천 사이클의 시간이 소요된다. , event non-signal이라면 스레드는 잠을 자버린다. 그리고 event가 켜지면 이 잠에서 깨어나는데 이 비용이 만만치 않다는 것이다.물론 두 프로세스 사이의 통신 빈도가 낮으면 문제가 없으나, 초당 저런 통신이 수십만번 일어난다면 (정말 초당 20만번까지 일어나는 경우가 있었다) 이 오버헤드는 상당히 컸다. 반면, spin-lock style의 경우 user-level에서 최대한 머무르고 있기 때문에 오히려 throughput은 올라가는 것을 발견할 수 있었다.

 

2. 스레드 사이의 동기화

너무나 고전적인 문제이다. Win32에서는 user objectCRITICAL_SECTION이 있으며, 커널 객체로 semaphore, event, mutex가 존재한다. 아무 생각없이 세마포어로 Process A의 여러 thread들이 IPC 자원을 배타적으로 소유할 수 있도록 하였다. 돌려보았다. 퍼포먼스가 안습이었다 ㅠㅠ... 아차 싶어서 세마포어를 크리티컬 섹션을 바꾸었다. 성능이 훨씬 올라갔다! 책에서만 보았던 커널 객체의 오버헤드를 Core 2 Duo 프로세서에서도 여실히 확인하는 순간이었다.

따라서,
CRITICAL_SECTION으로 해결할 수 있는 경우에는 *무조건* 크리티컬 섹션만 사용하여야 한다. 불필요한 세마포어, 뮤텍스 사용은 최대한 자제해야할 것이다.

, CRITICAL_SECTION은 듀얼 코어 및 멀티 프로세서 환경에서는 위의 무식해보이는 spin lock을 지원을 한다. (InitializeCriticalSectionSpinCount) 그래서
스핀락 카운트만큼 무식하게 while 루프를 돌면서 공유 자원을 가지기 위한 시도를 한다. 그리고 이것도 놀랍게도 전체적인 throughput을 높여주었다.



출처 : http://minjang.egloos.com/579171

'-- VC++' 카테고리의 다른 글

익스플로어의 입력박스 후킹  (0) 2012.10.09
동기화객체 비교  (0) 2011.08.26
OutputDebugString  (0) 2010.08.06
VC++ 코드 실행 시간 측정 방법 정리  (0) 2009.11.19
APC(Asynchronous procedure call)에 대하여  (0) 2009.10.29
posted by 어린왕자악꿍