2. 스레드의 기본
1. 스레드를 생성해야 하는 경우
2. 스레드를 생성하지 말아야 하는 경우
3. 처음으로 작성하는 스레드 함수
4. CreateThread 함수
5. 스레드의 종료
6. 스레드의 내부
7. C/C++ 런타임 라이브러리에 대한 고찰
8. 자신의 구분자 얻기
3. • 스레드(thread)
• 프로세스 내에서 실행되는 흐름의 단위로, 실제로 작업을 수행하는 주체. 일반적으로 한 프로그램은 하나의 스
레드를 가지고 있지만, 프로그램 환경에 따라 둘 이상의 스레드를 동시에 실행할 수 있다. 이러한 실행 방식을
멀티스레드(multithread)라고 한다.
• 멀티프로세스와 멀티스레드
• 양쪽 모두 여러 흐름이 동시에 진행된다는 공통점.
• 하지만 멀티프로세스에서 각 프로세스는 독립적으로 실행되며 각각 별개의 메모리를 차지하고 있는 것과 달
리 멀티스레드는 프로세스 내의 메모리를 공유해 사용할 수 있다.
• 또한 프로세스 간의 전환 속도보다 스레드 간의 전환 속도가 빠르다.
• 멀티스레드의 다른 장점은 CPU가 여러 개일 경우에 각각의 CPU가 스레드 하나씩을 담당하는 방법으로 속도를
높일 수 있다는 것이다. 이러한 시스템에서는 여러 스레드가 실제 시간상으로 동시에 수행될 수 있기 때문이다.
• 멀티스레드의 단점에는 각각의 스레드 중 어떤 것이 먼저 실행될지 그 순서를 알 수 없다는 것이 있다.
스레드(thread)
출처: 위키백과, 스레드(컴퓨팅)
4. • 스레드의 구성
• 스레드 커널 오브젝트
• 운영체제가 스레드를 다루기 위한 커널 오브젝트. 시스템이 스레드에 대한 통계 정보를 저장하는 공간.
• 스레드 스택
• 코드를 수행할 때 함수의 매개변수와 지역변수를 저장하기 위한 스레드 스택
• 스레드와 프로세스
• 스레드는 항상 프로세스의 컨텍스트 내에 생성되며, 프로세스 안에만 살아 있을 수 있다.
• 프로세스 내에 둘 이상의 스레드가 존재하는 경우, 스레드들은 단일 주소 공간을 공유.
• 프로세스의 커널 오브젝트 핸들 테이블 또한 공유하게 된다.
스레드와 프로세스
6. 1. 스레드를 생성해야 하는 경우
프로세스
초기화
주 스레드
생성
시작 코드 수행
(C/C++ 런타임
라이브러리 )
진입점 함수
(_tmain) 호
출
진입점 함수
반환
시작 코드의
ExitProcess
호출
수행 종료
스레드: 프로세스 내의 수행 흐름
7. 1. 스레드를 생성해야 하는 경우
• 계속해서 CPU가 작업을 수행하도록 할 때(유휴 상태가 되지 않게)
• 윈도우 운영체제의 인덱스 서비스(Index Service)
• 디스크 드라이브에 있는 파일에 대해 인덱싱을 수행.
• 검색 속도 향상
• 운영체제의 디스크 조각 모음 백그라운드로 수행
• Visual Strudio IDE 는 C# 등 코드 자동으로 컴파일
• 웹 브라우저는 백그라운드로 서버와 통신을 수행
• 웹 사이트 전부 로드되기 전 작업 수행 가능
• 멀티 스레딩
• 사용자 인터페이스를 단순화
• 자동 컴파일, 문법 검사 등
• I/O를 분리된 스레드로 수행해 사용자 인터페이스 응답성 향상
8. 2. 스레드를 생성하지 말아야 하는 경우
• 한 스레드가 다른 스레드의 자료를 잘못 변경하는 경우
• 예) 프린트 작업 중 문서 수정
• 높은 우선 순위의 단일 사용자 인터페이스 스레드를 이용해 응답성 개선
• 반면, 윈도우 탐색기는 각각의 폴더 윈도우를 다른 스레드로 생성하여 응답성 개선
• 다수의 스레드로 사용자 인터페이스를 구성하는 방식은 신중하게 사용해야
9. 3. 처음으로 작성하는 스레드 함수
• 모든 스레드는 진입점 함수를 반드시 가진다.
• 주 스레드의 진입점 함수 이름은 main, wmain, WinMain, 또는 wWinMain이어
야 하지만, 스레드 함수는 어떠한 이름이라도 사용될 수 있다.
• 애플리케이션에 여러 개의 스레드 함수가 필요하다면 각각은 서로 다른 이름
으로 명명되어야만 한다.
10. 3. 처음으로 작성하는 스레드 함수
• 스레드 함수는 반드시 값을 반환 한다. 이 값은 스레드의 종료 코드가 된다.
• 스레드 함수는 가능한 한 함수로 전달된 매개변수와 지역 local 변수만을 사용
하도록 작성
• 정적 변수나 전역 변수를 사용하게 되면 다수의 스레드 가동시 에 변수에 접근할 수 있게
되며 이는 변수의 값이 잘못 변경되는 원인이 되기도 한다.
11. 4. CreateThread 함수
• 주 스레드가 아닌 두 번째 스레드 생성시 CreateThread 호출
• 시스템은 스레드 커널 오브젝트를 생성 및 스레드가 사용할 스택을 확보
• 새로운 스레드는 스레드를 생성한 프로세스와 동일한 컨텍스트에서 수행
• 프로세스의 커널 오브젝트 핸들 및 메모리, 다른 스레드의 스택에 접근이 가능
• 동일 프로세스 내의 스레드들은 손쉽게 상호 통신 가능
12. CreateThread 함수의 각 매개변수
• PSECURITY ATTRIBUTES psa
• SECURITY ATTRIBUTES 구조체 포인터. NULL 전달하면 기본 보안 특성 사용
• DWORD cbStackSize
• 스레드 스택에 얼마만큼의 공간을 사용할지를 지정
• 매개변수로 0을 전달하면 실행 파일 내에 포함된 커밋된 물리적 저장소의 초기 크기를 따름
• pfnStartAddr과 pvParam
• pfnStartAddr: 새로이 생성되는 스레드가 호출할 스레드 함수의 주소
• pvParam: 위 쓰레드 함수의 매개변수로 그대로 전달
• 윈도우는 선점형 멀티스레딩 시스템: 기존 스레드와 새로운 스레드 동시 수행 가능
• 이미 반환되어 스택이 파괴된 스레드에 다른 스레드가 접근할 가능성 주의
13. CreateThread 함수의 각 매개변수
• dwCreateFlags
• 스레드를 생성할 때 세부적인 제어를 수행하기 위한 추가적인 플래그 지정
• CREATE SUSPENDED를 전달하면 CPU에 바로 스케줄되지 않고 일시 정지 상태를 유지
• pdwThreadlD
• 새로운 스레드에 할당되는 스레드 ID 값을 저장할 DWORD 변수를 가리키는 주소
14. 5. 스레드의 종료
• 스레드는 4가지 방법으로 종료
• 스레드 함수가 반환(추천)
• 스레드 함수 내에서 ExitThread 함수를 호출
• 동일한 프로세스나 다른 프로세스에서 TerminateThread 함수를 호출
• 스레드가 포함된 프로세스가 종료
15. 스레드 함수 반환
• 스레드 함수 내에서 생성한 c++ 오브젝트들은 파괴자를 통해 적절히 제거
• 운영체제는 스레드 스택으로 사용하였던 메모리를 반환
• 시스템은 스레드의 종료 코드를 스레드 함수의 반환 값으로 설정
• 스레드 커널 오브젝트 내에 저장
• 시스템은 스레드 커널 오브젝트의 사용 카운트를 감소
16. 스레드의 종료
• ExitThread 함수
• 스레드를 강제로 종료하고 운영체제가 스레드에서 사용했던 운영체제 리소스를 정리
• 하지만 c/c++ 리소스(c++ 클래스 오브젝트와 같은)는 정리되지 않는다.
• ExitThread 함수의 dwExitCode 매개변수를 이용하여 스레드의 종료 코드를 설정 가능
17. 스레드의 종료
• TerminateThread 함수
• hThread 매개변수는 종료할 스레드의 핸들을 전달해 어떠한 스레드라도 종료 가능
• 스레드의 종료 코드는 dwExitCode 매개변수로 전달한 값으로 설정
• 스레드 커널 오브젝트의 사용 카운트는 감소
• 이 함수를 호출하면 종료될 스레드는 자신이 종료될 것이라는 사실을 전달받지 못하기
때문에 적절한 정리 작업을 수행할 수도 없고, 종료를 회피할 수 있는 방법도 없다.
18. 스레드의 종료
• 프로세스가 종료
• ExitProcess와 TerminateProcess 함수를 호출하는 경우에도 스레드는 종료
• 프로세스가소유하고 있던 모든 스레드가 종료
• 프로세스가 사용하던 리소스 및 스레드들이 사용하던 스택들도 정리
• 프로세스 내에 남아 있는 스레드들에 대해 각각 TerminateThread 함수 호출
• 물론 C++ 파괴자가 호출되지도 못하고, 적절한 정리 작업도 수행되지 못한다.
19. 스레드가 종료되면
• 스레드 소유의 모든 유저 오브젝트 핸들이 삭제
• 윈도우와 윈도우 혹 두 개의 사용자 오브젝트는 스레드에 의해 소유
• 스레드가 종료되면 시스템은 해당 스레드가 생성한 윈도우를 파괴하고 설치한 윈도우
혹을 제거
• 스레드의 종료 코드는 STILL_ACTIVE 에서 ExitThread나 TerminateThread에서 지정한
종료 코드로 변경
• 스레드 커널 오브젝트의 상태가 시그널 상태로 변경
• 종료되는 스레드가 프로세스의 마지막 스레드라면 시스템은 프로세스도 같이 종료
• 스레드 커널 오브젝트의 사용 카운트가 1 만큼 감소
20. 6. 스레드의 내부
• CreateThread 함수가 호출되면 시스템은 스레드 커널 오브젝트를 생성한다.
• 이 오브젝트는 초기 사용 카운트 2
• 스레드 커널 오브젝트의 다른 속성들도 초기화
• 정지 카운트는 1, 종료 코드는 STILL ACTIVE(Ox103), 오브젝트의 상태는 non-signal로 각각 초기화
• 시스템은 스레드 스택으로 활용할 메모리 공간을 할당
• 스레드 스택으로 활용할 메모리는 프로세스의 주소 공간으로부터 할당
• 스택의 첫 번째 값은 pvParam 매개변수
• 두 번째 값은 pfnStartAddr 매개변수의 값이다.
21. 6. 스레드의 내부
• 스레드의 초기화가 완료되고 CREATE SUSPENDED 플래그가 전달되지 않았으면, 시스템은 스레드의 정
지 카운트를 0으로 감소시켜 스레드가 프로세서에 스케줄
• 스레드가 CPU 시간을 얻으면 시스템은 스레드 컨텍스트에 마지막으로 저장된 값을 CPU 레지스터로 로
드한다. 그러면 스레드는 프로세스 주소 공간 내에 있는 코드를 수행
• 새로운 스레드의 인스트럭션 포인터가 RtlUserThreadStart로 설정, 이 함수는 스레드가 실질적으로 수
행하는 최초 위치
22. 6. 스레드의 내부
• RtlUserThreadStart 함수는 C/C++ 런타임 라이브러리의 시작 코드를 호출하여 각종 초기화를 진행
• _tmain이나 tWinmain과 같은 진입점 함수를 호출
• 진입점 함수가 반환되면 C/C++ 런타임 라이브러리 시작 코드는 ExitProcess를 호출
• 따라서 C/C++ 애플리케이션의 주 스레드는 RtlUserThreadStart 함수로 절대 반환되지 않는다.
23. 7. C/C++ 런타임 라이브러리에 대한 고찰
• 멀티스레드 기반의 C/C++ 프로그램이 정상적으로 동작하려면 C/C++ 런타임 라이브러리 함수들을 사
용하는 각 스레드별로 적절한 구조의 데이터 블록을 생성해야 한다.
• 또한 C/C++ 런타임 라이브러리 함수는 다른 스레드들로부터 영향을 받지 않도록 자신을 호출한 스레
드의 데이터 블록에만 접근 가능해야 한다.
• 새로운 스레드를 생성할 때는 운영체제가 제공하는 CreateThread 함수를 절대로 호출하지 말고 대신
C/C++ 런타임 라이브러리가 제공하고 있는 _beginthreadex 함수를 호출해야만 한다.
24. _beginthreadex 함수 특징
• 각 스레드는 C/C+ 런타임 라이브러리 협에 tiddata 메모리 블록을 가진다 .
• _beginthreadex 함수에 전달된 스레드 함수의 주소는 tiddata 메모리 블록 내에 저장된다.
• 스레드 함수에 전달할 매개변수 또한 tiddata 메모리 블록에 저장된다 .
• _beginthreadex는 내부적으로 CreateThread를 호출한다. 이것은 운영체제에게 새로운 스레드를 생성하
도록 명령하는 유일한 방법이다 .
• CreateThread가 호출되면 _beginthreadex의 pfnStartAddr 매개변수로 전달한 스레드 함수가 아니라
threadstartex라는 함수가 수행
• 또한 스레드 함수의 매개변수도 _beginthreadex에 전달한 pvparam이 아니라 tiddata 구조체의 주소
• 정상적인 경우 _beginthreadex는 CreateThread와 동일하게 스레드 핸들을 반환한다. 만일 문제가 발생
하면 0을 반환한다.