1. 유저 모드에서의 스레드 동기화
Thread Synchronization in User Mode
아꿈사
http://cafe.naver.com/architect1
최성기
florist.sk@gmail.com
2. 1. 원자적 접근 : Interlocked 함수들
2. 캐시 라인
3. 고급 스레드 동기화 기법
4. 크리티컬 섹션
5. 슬림 리더-라이터 락
6. 조건변수
3. 1. 원자적 접근 : Interlocked 함수들
2. 캐시 라인
3. 고급 스레드 동기화 기법
4. 크리티컬 섹션
5. 슬림 리더-라이터 락
6. 조건변수
4. 어떤 스레드가 특정 리소스를 접근할 때
다른 스레드는 동일 시간에 동일 리소스에
접근할 수 없는 것을 말한다.
a thread's ability to access a resource with the guarantee
that no other thread will access that same resource
at the same time.
5. int g_x = 10;
스레드 1 스레드 2
void func()
{
assert( g_x != 0 );
...
g_x = 0;
...
g_x = 10;
}
병렬실행을 고려하지 않은 코드가 동시에 실행되면서
공유자원의 값이 예측 불가능한 것도 문제긴 하지만
6. int g_x = 0;
스레드 1 스레드 2
void func()
{
g_x++;
}
= 원자적으로 보이는 명령
코드상으로는 괜찮아 보이는 경우에도
병렬 실행시 이상해 지는 경우가 있다.
= 원자적이지 않다는 소리
7. C++ 코드 x86 어셈블리 코드
MOV EAX, [g_x]
g_x++; INC EAX
MOV [g_x], EAX
C++에서 한 줄의 명령어로 표현 되더라도
컴파일된 어셈블리 코드는 여러 단계로 나뉘어져
동시에 실행됨을 보장할 수 없는 경우가 있다.
9. int i = 0;
int j = 20; vs2010, x86, debug
i = i + 1; mov eax,dword ptr [i]
add eax,1
i++; mov dword ptr [i],eax
++i; mov eax,dword ptr [j]
mov dword ptr [i],eax
i = j;
mov dword ptr [i],15h
i = 21;
다음 중 원자적인 연산은?
10. int i = 0;
int j = 20; vs2010, x86, release (/O2)
i = i + 1;
inc dword ptr [ebp-4] ??
i++;
++i; mov eax,dword ptr [ebp-8]
push esi
i = j;
mov dword ptr [ebp-4],15h
i = 21;
x86 명령어셋은 메모리값을 1증가시키는 단일명령어 inc도 지원
하나의 기계어라도 CISC 명령어가 내부 파이프라인으로 들어가면
결국 레지스터로 load – add – store하는
마이크로 명령어로 분해될 수 있음을 유의해야 한다.
337p.
11. int g_x = 0;
void func()
{
lock();
g_x++;
unlock();
}
아니 그럼 멀티 스레드 에서는
고작 정수형 변수 하나 건드릴 때에도
매번 락을 걸어야 하나요?
12. 이런 절차로 어린 코더가 니르고저 할빼이셔도
비로서 제 뜻을 시러 펴디 못할 노미 하니라.
InterlockedExchangeAdd( 변수주소, 값 );
InterlockedExchangeAdd64( 변수주소, 값 );
InterlockedExchange( 변수주소, 값 );
InterlockedExchange64( 변수주소, 값 );
InterlockedExchangePointer( 변수주소, 값 );
InterlockedIncrement( 변수주소 );
InterlockedDecrement( 변수주소 );
…
13. int g_x = 0; int g_x = 0;
void func() void func()
{ {
lock();
g_x++; InterlockedExchangeAdd( &g_x, 1 );
unlock();
} }
정수형 변수의 값을 원자적으로 다뤄야 할 때
인터락 함수를 이용하면 된다.
빠르게 수행됨 (50사이클 미만)
유저모드 – 커널모드의 전환을 일으키지 않음.
14. // 공유 리소스의 사용 여부를 나타내는 전역변수
BOOL g_fResourceInUse = FALSE; ...
void Func1()
{
// 리소스의 접근을 기다림
while (InterlockedExchange(&g_fResourceInUse, TRUE) == TRUE)
Sleep(0);
// 리소스에 접근함
...
// 리소스에 더 이상 접근할 필요가 없음
InterlockedExchange(&g_fResourceInUse, FALSE);
}
InterlockedExchange는 특별히 스핀락spinlock을
구현해야 하는 경우에 유용하게 사용될 수 있다.
15. 값의 비교와 변경을 원자적으로 해주는 연산.
멀티스레드 프로그래밍에 필수적인 뮤텍스, 세마포등의
동기화 객체synchronization object를 만드는 데 쓰인다.
InterlockedCompareExchange( 변수주소, 변경값, 비교값 );
InterlockedCompareExchange64( … );
InterlockedCompareExchangePointer( … );
17. 스레드 1 스레드 2
x == A
x = B
x = A
x == A
CAS 연산이 다른 스레드의 접근을 감지하지 못하는 현상.
Interlocked Singly Linked List 사용시 주의점. - http://devnote.tistory.com/207
18. 1. 원자적 접근 : Interlocked 함수들
2. 캐시 라인
3. 고급 스레드 동기화 기법
4. 크리티컬 섹션
5. 슬림 리더-라이터 락
6. 조건변수
19. 정말 최적화된 프로그램을 작성하고 싶다면
캐시 미스(cache miss)가 적게 일어나도록 신경써야 한다.
http://goo.gl/7j5yP
인접한 메모리 데이터는 같은 캐시라인으로 구성되어
코어마다 분리된 캐시에 적재되기 때문.
20. 1. CPU1이 메모리 1000번지 값을 읽는다.
234p.
• 값을 읽어 캐시에 보관
2. CPU2도 메모리 1000번지 값을 읽는다.
• 값을 읽어 캐시에 보관
3. CPU1이 메모리 1000번지 값을 변경한다.
• 캐시의 값 변경. 아직 RAM에 쓰기 전.
4. CPU2가 다시 1000번지 값을 읽는다.
• 자신의 캐시에 값이 있지만 최신 내용이 아님.
cache coherence miss 발생.
21. 1. 읽기 전용의 데이터와 읽고쓰는 데이터를 분리
2. 동일 시간에 접근하는 데이터를 묶어서 구성
3. CPU의 캐시라인 크기를 알아내서,
데이터 정렬을 캐시라인 크기별로 구성.
캐시라인 크기 알아내기 : GetLogicalProcessorInformation()
- http://goo.gl/MVZPH
구조체 정렬 임의로 조절하기 : __declspec(align(#))
- http://pmguda.com/378
- http://goo.gl/w8SyL
22. 1. 원자적 접근 : Interlocked 함수들
2. 캐시 라인
3. 고급 스레드 동기화 기법
4. 크리티컬 섹션
5. 슬림 리더-라이터 락 스핀락 vs 대기모드
전역변수 스핀락(?)
6. 조건변수 volatile 키워드.
23. 1. 원자적 접근 : Interlocked 함수들
2. 캐시 라인
3. 고급 스레드 동기화 기법
4. 크리티컬 섹션
5. 슬림 리더-라이터 락
6. 조건변수
24. 공유 리소스에 대해 배타적으로 접근해야 하는
작은 코드의 집합을 의미한다.
공유 리소스를 다루는 여러 줄의 코드를
원자적으로 수행하기 위한 방법이다.
25. 인터락 함수로 동기화를 해결할 수 없다면
크리티컬 섹션을 사용하라.
장점
• 사용이 쉽고 내부적으로 인터락 함수를 사용해 빠르다.
단점
• 서로 다른 프로세스의 스레드 간에는 사용할 수 없다.
26. int g_x = 0;
void func()
{
EnterCriticalSection( &g_cs );
for( int i = 0; i < 1000; i++ )
{ Critical
g_x += i; Section
}
LeaveCriticalSection( &g_cs );
}
크리티컬 섹션으로 둘러쌓인 코드는
동시에 하나의 스레드밖에 진입할 수 없다.
27. 1. 이미 Critical Section에 진입한 스레드가
다시 Enter 해도 괜찮다.
단, Enter 횟수만큼 Leave 해주어야 한다.
2. 다른 스레드가 진입해 있다면 뒤에 호출한 스레드는
이벤트 커널 오브젝트를 이용해 대기 상태로 전환.
28. 스레드를 절대 대기 상태로 진입시키지 않는다.
호출한 스레드가 진입해 성공했다면 TRUE 반환,
다른 스레드가 이미 진입해서 실패했다면 FALSE 반환.
29. 스레드가 스핀락spinlock을 돌면서 대기하면
CPU 시간을 소모하면서 대기하는 방식.
대기 시간이 길어지면 CPU자원의 낭비가 된다.
스레드가 운영체제의 대기상태wait state로 전환되면
CPU 자원을 낭비하지 않는다.
대기 시간이 길어질 수록 효율적인 대기방식.
30. 스레드가 스핀락spinlock을 돌면서 대기하면
CPU 시간을 소모하면서 대기하는 방식.
대기 시간이 길어지면 CPU자원의 낭비가 된다.
대기 시간이 짧다면 바로 자원 획득이 가능.
스레드가 운영체제의 대기상태wait state로 전환되면
CPU 자원을 낭비하지 않는다.
대기 시간이 길어질 수록 효율적인 대기방식.
커널모드로 전환되어야 함.
대기 시간이 짧을 때는 오히려 상당한 CPU 낭비.
31. 그래서 이 둘을 합쳐,
EnterCriticalSection이 호출되면
1. 일정 횟수 스핀락으로 루프를 돌아 대기.
2. 지정된 횟수 동안 리소스 획득에 실패했다면
커널 모드로 전환해 스레드를 대기 상태로 전환.
하는 방식으로 사용할 수 있게 되어있다.
32. 크리티컬 섹션에 스핀락을 사용하기 위한 초기화
InitializeCriticalSection(…)
BOOL InitializeCriticalSectionAndSpinCount(
PCRITICAL_SECTION pcs,
DWORD dwSpinCount );
스핀 횟수를 중간에 변경
VOID SetCriticalSectionSpinCount(
PCRITICAL_SECTION pcs,
DWORD dwSpinCount );
33. 주의 :
InitializeCriticalSection 는 가끔 예외를 던진다.
시스템 가용메모리가 모자라 블록 할당이 실패하면
STATUS_NO_MEMORY 예외가 발생.
초기 설계시에 리턴값을 VOID로 해버려서.. 방법이 없다능..
..AndSpinCount 함수는 동일 상황에 FALSE를 리턴한다.
해결책 :
1. exception handling 처리를 하거나
2. …AndSpinCount 함수를 쓸 것.
34. 주의 :
EnterCriticalSection 도 가끔 예외를 던진다.
시스템 메모리가 모자라면 이벤트 객체 생성에 실패하면서
EXCEPTION_INVALID_HANDLE 예외가 발생.
예외가 생뚱맞아서 막상 해당 상황을 디버깅 하려면 헤맬 수 있다.
단, XP 이전의 운영체제에서만 예외가 발생한다.
XP부터는 키 이벤트key event라는 객체가 생겨서 이를 사용함.
해결책 :
1. XP 이전 OS를 버리세요.
2. 스핀카운트 최상위비트를 설정해 초기화.
초기화 시점에 미리 이벤트 객체가 생성됨.
35. 책에서는 CRITICAL_SECTION 구조체의 변수 값은
알 필요 없다고 하고 설명도 없는데
데드락을 디버깅하는 데 많은 도움이 됩니다.
MSDN 매거진 자료 링크 : http://goo.gl/OGY0M
36. 1. 원자적 접근 : Interlocked 함수들
2. 캐시 라인
3. 고급 스레드 동기화 기법
4. 크리티컬 섹션
5. 슬림 리더-라이터 락
6. 조건변수
37. Read/Write lock은 고전적인 동기화 기법의 하나.
http://devnote.tistory.com/177
Write 작업은 한 번에 하나밖에 실행될 수 없지만
Read 작업은 동시에 여러 개가 실행될 수 있게 처리.
http://goo.gl/u9TUN
이런 동기화 방식을 Windows Vista / 2008 Server부터
Win32 API 자체적으로 지원한다.
38. 사용 방식은 CriticalSection과 매우 흡사하다.
동작 CriticalSection SRWLock
초기화 InitializeCriticalSection InitializeSRWLock
릴리즈 DeleteCriticalSection 없음
Lock EnterCriticalSection AcquireSRWLockExclusive (Write)
ActuireSRWLockShared (Read)
Unlock DeleteCriticalSection ReleaseSRWLockExclusive (Write)
ReleaseSRWLockShared (Read)
TryLock TryEnterCriticalSection TryAcquireSRWLockExclusive (Write)
TryAcquireSRWLockShared (Read)
40. 스레드/ volatile volatile Interlocked 크리티컬 SRWLock SRWLock 뮤텍스
밀리초 변수 읽기 변수 쓰기 Increment 섹션 Shared Exclusive
1 8 8 35 66 66 67 1060
2 8 76 153 268 134 148 11082
4 9 145 361 768 244 307 23785
벤치마크에 사용된 코드를 보면
크리티컬 섹션은 스핀락을 사용하지 않은 경우.
- 크리티컬 섹션과 뮤텍스 모두 커널모드로 전환이 발생하나
뮤텍스는 커널모드 전환비용 이상의 코스트가 발생한다.
41. 1. 원자적 접근 : Interlocked 함수들
2. 캐시 라인
3. 고급 스레드 동기화 기법
4. 크리티컬 섹션
5. 슬림 리더-라이터 락
6. 조건변수
42. http://goo.gl/T8uBM
Slim Reader/Writer Lock과 함께
Vista / 2008 Server부터 지원.
조건변수condition variable 역시
병렬처리 이론적으로는 예전부터 정립되어 있는 개념이며
자바나 C#의 Monitor 클래스가 이를 구현하고 있고
POSIX 스레드에도 들어있던 기능.
boost::thread에도 구현되어 있다.
ACE에도 있는 것 같..지만 제가 ACE를 제대로 본 적이 없네요..
43. 조건변수를 사용하면 스레드가 리소스에 대한 락을
해제하고 SleepConditionVariable*함수에서 지정한
상태가 될 때까지 스레드를 블로킹 해준다.
BOOL SleepConditionVariableCS(
PCONDITION_VARIABLE pConditionVariable,
PCRITICAL_SECTION pCriticalSection,
DWORD dwMilliseconds );
BOOL SleepConditionVariableSRW(
PCONDITION_VARIABLE pConditionVariable,
PSRWLOCK pSRWLock,
DWORD dwMilliseconds,
ULONG Flags ); // 읽기, 쓰기 구분 플래그
44. CONDITION_VARIABLE cond;
InitializeConditionVariable( &cond );
EnterCriticalSection( &cs );
…
while( !프로세스 종료상황 )
{
if( 지금 작업 가능한 상황 )
{
… 씐나게 작업을 한다.
}
else // 작업 불가능.
{
SleepConditionVariableCS( &cond, &cs, INFINITE );
}
}
LeaveCriticalSection( &cs );
45. 조건변수를 이용해 Sleep한 스레드를 깨워야 할 때는
Wake*ConditionVariable 함수로
하나 혹은 모든 스레드를 wake 시킬 수 있다.
VOID WakeConditionVariable(
PCONDITION_VARIABLE pConditionVariable );
VOID WakeAllConditionVariable(
PCONDITION_VARIABLE pConditionVariable );
46. 1. 원자적으로 관리되어야 하는 오브젝트 집합당
하나의 락만을 사용하라.
2. 다수의 논리적 리소스에 동시에 접근하려면
다수의 리소스 락을 원자적으로 설정해야 한다.
스레드1 스레드2
Enter..( &cs1 ); Enter..( &cs2 );
Enter..( &cs2 ); Enter..( &cs1 );
3. 락을 장시간 점유하지 말 것.
장시간 작업이 필요할 땐 리소스를 복사해서 사용
47. 1. 원자적 접근 : Interlocked 함수들
2. 캐시 라인
3. 고급 스레드 동기화 기법
4. 크리티컬 섹션
5. 슬림 리더-라이터 락
6. 조건변수
48. 유저 모드에서의 스레드 동기화
Thread Synchronization in User Mode
끝♥