6. 변수 함수 스레드
지역 변수 함수마다 독립 스레드마다 독립
정적 변수 모든 함수에서 공유 전체 스레드에서 공유
TLS 같은 스레드의 모든 함수에서 공유 스레드마다 독립
7. Thread 1 Thread 2
0 INUSE 0 data 1 0 …
1 INUSE 1 … 1 data 1
2 FREE 2 … 2 …
… … …
… … …
… … … … … …
… … …
… … …
n … n … n …
TLS Bit Flags TLS TLS
n : Windows 95/NT = 64
Windows 98/ME = 80
Windows 2000/XP = 1088
8. TLS Bit Flags 배열은 INUSE, FREE값 저장
◦ 사용중, 사용가능
◦ 실제 사용중인 것은 아니고 개념적인 것
초기 Bit Flags TLS 배열의 인덱스 할당
◦ TlsAlloc()
TLS Bit Flags 배열의 비트 플래그 해제
◦ TlsFree(dwTlsIndex)
TlsGetValue(index), TlsSetValue(index, value)
9. TLS 배열에서 FREE인 인덱스 반환
TLS는 n * 4Byte 배열 n :Windows 98/ME = 80
Windows 95/NT = 64
◦ sizeof(void *)? Windows 2000/XP = 1088
초기 TLS는 0으로 초기화되어있음
10. 반환/설정 값은 적절한 자료형으로 캐스팅
자료형이 4Byte를 넘어갈 경우 Boxing
◦ TLS 배열에는 포인터 형태로 저장
접근하는 값은 다른 스레드와 상호 배타
11. TLS Bit Flags를 INUSE에서 FREE로 설정
TLS Bit Flags 배열과 TLS 배열은 다릅니다
◦ 배열 항목 개수가 동일합니다
◦ TLS 배열은 스레드마다 할당
12. 컴파일러에서 키워드 형태로 지원
__declspec(thread) 자료형 변수명
__declspec(thread) int k;
컴파일 단계에서 .tls 섹션으로 통합
스레드 생성 시 .tls에서 동적 할당
◦ 컴파일러가 접근 코드를 삽입
선언만 다르고 나머지는 변수와 같습니다.
13. TLS가 필요없는 스레드도 TLS 할당
컴파일러의 TLS 접근 코드 삽입
◦ 추가 오버헤드 발생
LoadLibrary로 DLL을 로드하는 경우 사용 불가
14. TLS를 사용하는 클래스는 CNoTrackObject 클래
스 상속
THREAD_LOCAL 매크로를 사용하여 선언
접근은 항상 -> 연산자
◦ 연산자 오버로딩되어 있습니다
class CTls1 : public CNoTrackObjecct
{
public:
int x, y;
};
THREAD_LOCAL(CTls1, tls1);
tls1->x = 10;
15. 내부적으로 Win32 TLS를 사용
초기화는 내부에서 처리
◦ 최초 접근시 초기화
CNoTrackObject, CThreadLocalObject,
THREAD_LOCAL
◦ MSDN에 정식 등재된 클래스/매크로가 아님
16. 스레드 안전 함수
◦ 원자적 실행이 보장되는 함수
ANSI C의 스레드 안전하지 않은 함수
◦ strtok, strerror, tmpnam, tmpfile, asctime, …
스레드 안전하게 보강된 라이브러리 사용
◦ LibC.lib, LibCD.lib, LibCMt.lib, LibCMtD.lib,
MSCVRt.lib, MSCVRtD.lib
◦ ~D.lib는 디버그 모드용 버전
20. 원자적 연산 수행
◦ 변수에 대한 접근이 자유롭지만 특정 순간에는 하나의 스
레드만 접근하여 연산 수행
문맥 교환 시점에 따라 원자적 수행이 불가능
21. 전역(정적) 변수 자료형 동기화 방법
정수, 포인터 Interlocked- 함수, volatile
기타 동기화 객체
22. 정수형 자료형에 대한 원자적인 연산
◦ 프로그램에서 가장 많이 접근하는 자료형
◦ int, long, void *, …
LONG InterlockedIncrement(LONG volatile
*lpAddend)
LONG InterlockedDecrement(LONG volatile
*lpAddend)
◦ lpAddend 매개변수를 1 증가 혹은 감소
23. Long InterLockedExchangeAdd(LPLONG volatile
Addend, LONG Value)
◦ Addend로 받은 주소에 Value 값만큼 증감
Long InterLockedExchange(LONG volatile *Target,
LONG Value)
◦ Target으로 받은 주소에 Value 값 대입
24. Long InterLockedCompareExchange (LONG
volatile *Destination, LONG Exchange, LONG
Comperand)
◦ Destination의 실제 값과 Comperand의 값이 같은 경우
Exchange를 Destination의 실제 값에 대입
◦ if(*Destination == comperand)
*Destination = Exchange;
PVOID InterlockedExchangePointer
25. PVOID InterlockedExchangePointer(PVOID
volatile *Target, PVOID Value)
◦ Target의 포인터 값을 Value 포인터로 변경
PVOID
InterlockedCompareExchangePointer(PVOID
volatile *Destination, PVOID Exchange, PVOID
Comperand)
◦ Destination의 포인터 값이 Comperand값과 같다면
Destination의 포인터 값에 Exchange포인터로 변경
26. 휘발성 변수를 선언할 때 사용
◦ 코드 상에서 명시적으로 대입하는 것이 아닌 실제 CPU에
서 대입하게 하는 변수
컴파일러의 최적화 방지
◦ 컴파일러는 최적화 과정에서 불필요한 연산을 하는 코드
를 감지, 삭제
◦ volatile 키워드는 변수에 대한 연산 시 최적화에 의해 삭
제되는 코드를 삭제하지 못하게 막음
27. 모든 전역 변수와 하드웨어와 매핑되는 변수에 사용
기계상으로 특정 주소에 위치한 장치에 대해 항상
값을 읽어올 때 사용
volatile을 생략한 경우
◦ disassembly
28. 모든 전역 변수와 하드웨어와 매핑되는 변수에 사용
기계상으로 특정 주소에 위치한 장치에 대해 항상
값을 읽어올 때 사용
volatile을 명시한 경우
◦ disassembly
29. 동기화 객체 특징
• 작고 빠르다
critical section • 소유 개념이 있고 중복 소유가 가능하다
• 프로세스 사이에 공유되지 않는다
• 크리티컬 섹션에 비해 무겁다
• 시그널, 논 시그널 개념이 있다
event
• 자동과 수동 이벤트 개념이 있다
• 프로세스 사이에 공유될 수 있다
• 크리티컬 섹션에 비해 무겁다
semaphore • 시그널 카운트 개념이 있다
• 프로세스 사이에 공유될 수 있다
• 크리티컬 섹션에 비해 무겁다
mutex • 소유 개념이 있고 중복 소유가 가능하다
• 프로세스 사이에 공유될 수 있다
31. void EnterCriticalSection(lpcs)
◦ 크티티컬 섹션 진입, 최대 30일 대기
BOOL TryEnterCriticalSection(lpcs)
◦ 진입 시도를 하여 성공하면 TRUE를 반환
void LeaveCriticalSection(lpcs)
◦ 크리티컬 섹션 탈출
중복 진입 가능
◦ 탈출할 때에는 반드시 중복 진입한 수만큼 탈출
32.
33. 커널 객체는 프로세스간 동기화도 가능
◦ OpenEvent(), OpenMutex(), OpenSemaphore()
인자에 이름을 넣습니다
이름은 각 객체에 대한 설명 때 나옵니다
◦ 대신에 크고 무겁습니다
시그널 상태
◦ 실행 대기중인 상태(잠자고 있습니다)
논 시그널 상태
◦ 현재 실행중인 상태(깨어 있습니다)
34. DWORD WaitForSingleObject(HANDLE hHandle,
DWORD dwMilliseconds)
◦ 커널 객체 hHandle에 대해 dwMilliseconds만큼 대기
◦ 시그널에서 논 시그널이 될 때까지 대기
35. 크기가 nCount인 lpHandles 배열의 핸들들을
dwMilliseconds만큼 대기
bWaitAll이 TRUE면 모든 스레드가 시그널 될 때까
지 대기
36. 마지막 인자 dwMilliseconds를 통해 무제한
(INFINITE) 대기 가능
시그널 상태인지만 검사하는 경우 마지막 인자
dwMilliseconds에 0 대입
반환값
◦ WAIT_OBJECT_0 : 시그널로 전환됨
◦ WAIT_TIMEOUT : 시간 초과
◦ WAIT_ABANDONED_0 : 기다리던 스레드가 종료됨
mutex 전용
◦ WAIT_FAILED : 잘못된 hHandle 인자
37. 가장 원시적인 커널 객체
자동 리셋 이벤트
◦ 대기하던 스레드가 시그널 될 때 이벤트 객체가 자동으로
논 시그널 됨
수동 리셋 이벤트
◦ 대기하던 스레드가 시그널 되더라도 이벤트 객체는 유지됨
◦ BOOL ResetEvent(HANDLE hEvent)로 이벤트 객체를 논
시그널로 전환
38. // 생성된 이벤트 핸들
lpEventAttributes : 속성
bManualReset : 자동/수동
bInitialState : 시그널/논 시그널
lpName : 이벤트 이름
40. 자동 이벤트와 크리티컬 섹션을 비교합니다
자동 이벤트 크리티컬 섹션
CreateEvent(NULL, FALSE, TRUE, InitializeCriticalSection
NULL)
WaitFor- EnterCriticalSection
SetEvent LeaveCriticalSection
Closehandle DeleteCriticalSection
41. WaitForMultipleObject와 동일합니다
마지막 인자 dwWakeMask
◦ QS_ALLINPUT을 대입하면 메세지 큐의 모든 이벤트 대기
42. 뮤텍스 획득 = 논 시그널과 유사
뮤텍스 해제 = 시그널과 유사
두번째 인자 bInitalOwner가 TRUE면 생성 직후 뮤
텍스를 획득
43. BOOL ReleaseMutex(HANDLE hMutex)
◦ 뮤텍스 hMutex를 해제
WaitFor-함수의 반환값 WAIT_ABANDONED_0
◦ 뮤텍스를 소유하던 WAIT_ABANDONED_0(배열상의 +n
번째) 스레드가 종료되어 논 시그널 상태가 된 경우
44. 뮤텍스와 이벤트, 크리티컬 섹션을 비교합니다
뮤텍스 자동 이벤트 크리티컬 섹션
CreateMutex CreateEvent(NULL, FALSE, InitializeCriticalSection
TRUE, NULL)
WaitFor- WaitFor- EnterCriticalSection
ReleaseMute SetEvent LeaveCriticalSection
x
Closehandle Closehandle DeleteCriticalSection
45. 시그널 카운트
◦ 카운트 > 0 : 세마포어 객체는 시그널
◦ 카운트 == 0 : 세마포어 객체는 논 시그널
◦ 카운트 < 0 : 음수는 나오지 않습니다
◦ 카운트는 최대 카운트보다 작습니다
◦ 카운트에 의해 세마포어 객체가 시그널일 경우 스레드는
논 시그널
◦ WaitFor-함수에 의해 카운트가 감소합니다
46. 초기 카운트 lInitialCount와 최대 카운트
lMaximumCount는 설정 가능
47. 세마포어 카운트 증가
◦ 음수, 최대 카운트 이하값만 유효
lpPreviousCount
◦ 이전 카운트 값
◦ NULL 가능
48. 프로세스 내부의 여러 스레드가 전역 자원 하나를
두고 경쟁하는 경우 – 크리티컬 섹션
서로 다른 프로세스의 여러 스레드에서 전역 자원
하나를 두고 경쟁하는 경우 – 자동 이벤트, 뮤텍스
◦ 크리티컬 섹션에 비해 무겁습니다
서로 다른 스레드가 같은 함수를 공유하며, 전역 자
원을 두고 경쟁하는 경우 – 크리티컬 섹션, 뮤텍스
◦ 중복 소유를 인정하므로 교착 상태에 비교적 안전합니다
비동기 I/O, 스레드 타이밍 동기화 – 수동 이벤트
동기화 + 스레드 활동 수 제한 - 세마포어
49.
50. Windows의 기본 스케줄링 단위 : 스레드
◦ 프로세스는 그저 개념으로만 있습니다
비선점형 멀티태스킹
◦ 프로세스가 CPU 자원을 독점하여 사용
◦ 프로세스가 자원을 서로 양보해야 합니다
선점형 멀티태스킹
◦ 커널이 프로세스의 CPU 자원을 선점
우선순위 주도의 라운드 로빈 스케줄링 시스템
◦ 단위 시간(n * 퀀텀)동안 스레드에게 CPU 할당
◦ 우선순위가 낮은 스레드는 실행중 대기할 수도 있습니다
51. 스레드가 실행할 준비가 됨(논 시그널)
CPU를 모두 사용하거나 스레드가 종료되거나
SwitchToThread() 호출로 시그널 됨
Win32 API 호출로 우선 순위가 변경됨
◦ 실행중인 스레드의 우선 순위가 높아짐
52. 고정 우선순위 : 16~31, real-time
가변 우선순위 : 1~15, 실행 중 임의로 변동
0 페이지 : Windows에 0 페이지 스레드가 있습니다
◦ 0으로는 생성 불가
기본 우선순위 : 초기 스레드 생성시
실행 우선순위 : 스레드 수행중 임의로 변동
◦ 가변 우선순위 내에서 변동하며, 기본 우선순위도 가변
우선순위인 경우
53. 기본 우선순위 결정 : 프로세스 우선순위 클래스 +
스레드 우선순위(표는 임의의 약자입니다)
IDLE B_NOR NOR AB_NOR HIGH RT
T_CRT 15 15 15 15 15 31
HIGHEST 6 8 10 12 15 26
AB_NOR 5 7 9 11 14 25
NOR 4 6 8 10 13 24
B_NOR 3 5 7 9 12 23
LOWEST 2 4 6 8 11 22
IDLE 1 1 1 1 1 16
54. device 드라이버가 사용하는 우선순위
◦ 17, 18, 19, 20, 21, 27, 28, 29, 30
우선 순위에 상관없이 SetPriorityClass(),
SetThreadPriority() 호출 가능
◦ 반영 안 될수도 있습니다
55. 클럭 단위(10ms)
클럭 단위가 지날 때마다 3퀀텀 감소
할당된 퀀텀이 0이 되는 경우 문맥 교환 발생
◦ 운영체제가 선점
Windows 2000 커널 : 기본 6 퀀텀(20ms)
◦ 2000, 2003, XP, …
◦ 사용자의 요청에 대한 응답 속도 위주
Windows Server 커널 : 기본 36퀀텀(120ms)
◦ 여러 클라이언트의 요청을 단일 주기에 처리하기 위함
56. 스레드 생성과 초기화
재초기화 초기화
준비 큐에 큐잉
종료 대기 준비
커널스택
스레드가 교체방출
실행 객체 핸들 실행을 선점
완료 대기 변환 위해 선택
커널스택
교체입력
실행 실행대기
선점 또는 퀀텀시간 종료
문맥 교환후 실행 시작
57. 스레드가 실행을 위해 기다리고 있는 상태로 준비
상태의 스레드들은 실행 우선순위별 리스트로 구
성되어 있다. 윈도우의 스케줄러는 실행할 스레드
를 찾을 때, 준비 상태에 있는 스레드 리스트들 중
에 가장 높은 실행 우선순위 리스트를 찾고 이 중
에 맨 처음 스레드를 찾아 실행 대기 상태로 가져
간다.
58. 윈도우의 스케줄러에 의해 다음에 실행되기 위해
선택된 스레드의 상태가 실행 대기 상태이다. 스케
줄러는 상황이 맞을 때 스레드에 대해 문맥 교환을
수행하여 스레드를 실행 상태로 가져간다. 하나의
코어에는 하나의 스레드만이 실행 대기 상태가 될
수 있다. 어떤 스레드가 실행 대기 상태에 있다 하
더라도 우선순위가 높은 스레드가 등장하면 실행
대기 상태의 스레드는 다시 준비 상태로 간다.
59. 실행 대기 상태의 스레드에 대해 문맥 교환이 수행
되면 스레드는 실행 상태로 들어가게 된다. 이렇게
실행되고 있는 스레드는 더 높은 우선순위를 가진
스레드에 의해 선점되거나, 할당된 퀀텀을 다 소모
하거나, 종료되거나, 자발적으로 대기 상태에 들어
갈 Eor까지 스레드를 계속 실행한다.
60. 스레드는 몇 가지 방법(동기화 대기, Sleep,
Suspend, 메시지 대기)으로 대기 상태로 들어갈
수 있다. 스레드가 대기 상태로 들어가는 가장 일
반적인 경우는 스레드가 자발적으로 동기화 객체
(event, semaphore, mutex, …)에 대해서
WaitFor- 함수를 호출할 때 대기 상태로 들어간다.
또 Windows는 스레드를 대신해서 대기 상태로 들
어갈 수 있다(I/O). 스레드는 대기 상태가 끝났을
때 준비 상태로 들어간다.
61. 스레드가 대기 상태에 있을 때 커널 스택이 메모리
밖으로 페이징되어 나간다면 스레드는 변환 상태
로 들어간다. 커널 스택이 다시 메모리상으로 돌아
오면 스레드는 준비 상태로 들어간다.
62. 스레드가 실행을 마쳤을 때스레드는 종료 상태로
간다. 커널은 스레드 객체를 다시 초기화하여 다시
사용할 수 있다.
스레드가 생성될 동안 Windows 시스템이 내부적
으로 사용하는 스레드 상태이다.
63. 스레드 생성과 초기화
재초기화 초기화
준비 큐에 큐잉
종료 대기 준비
커널스택
스레드가 교체방출
실행 객체 핸들 실행을 선점
완료 대기 변환 위해 선택
커널스택
교체입력
실행 실행대기
선점 또는 퀀텀시간 종료
문맥 교환후 실행 시작
64. 실행 우선순위는 필요에 따라 변동
◦ I/O
◦ 실행 이벤트 혹은 세마포어에 대한 대기가 끝난 후
◦ foreground 프로세스에 있는 스레드가 대기 작업을 수행한 후
◦ Windows 활동 때문에 UI 스레드가 깨어난 후
◦ 준비 큐에 있는 스레드가 기아 상태에 빠질 때
65. I/O
◦ 장치에서 IoCompleteRequest() 함수 호출
◦ 위의 함수에서 우선순위가 증가
◦ 퀀텀이 지날 때마다 1씩 감소
기본 우선순위가 될 때까지 감소합니다
이벤트/세마포어 대기 후
◦ 우선순위 1 증가
◦ 퀀텀이 지날 때 1 감소
67. 앞의 경우를 포함하여 우선순위 4 증가
퀀텀이 지날 때마다 1씩 감소
빠른 응답을 위해 변동됩니다
68. 우선순위가 15가 됩니다
할당받은 퀀텀이 2배로 증가
퀀텀을 모두 사용한 경우 원래대로 복귀
우선순위 11
우선순위 4
스레드의 우선순위 7
응답을 대기 오래 걸리는 작업
수행중
우선 순위에
우선순위 4인 밀려 대기중
스레드 우선순 (기아 상태)
우선순위 4
위 증가