본문 바로가기
운영체제/[ecourse] Windows Programming

6-1. Thread Basic

by 헛둘이 2022. 9. 20.
쓰레드란?
  • 프로세스 안에서 코드를 실행하는 실행 흐름
  • 프로세스를 만들면 기본적으로 주 스레드 1개가 만들어짐
  • 스레드도 관리 대상이므로 TKO (쓰레드 커널 오브젝트)가 만들어짐

 

※ 프로세스에 대한 부연설명

- 프로세스 가상 주소공간에는 다양한 모듈(실행파일, DLL)이 올라와 있음
- 프로세스를 만들면 OS가 PKO (프로세스 커널 오브젝트)로 관리
- 프로세스는 가상 주소공간 + PKO로 구성됨
- 프로세스는 정적인 존재

 

쓰레드를 만들면?
  • 스택과 TKO가 만들어짐
  • 쓰레드는 동적인 존재 (왜냐하면 직접 코드를 실행하는 실행 흐름이므로)

 

 

쓰레드와 병렬 실행
  • 쓰레드는 주 쓰레드 외에 추가로 더 만들 수 있다.
  • CPU가 2개가 있을 땐 2개의 쓰레드를 동시에 실행 가능
  • CPU가 2개 있을 때 4개의 쓰레드를 실행하려면?
  • 완전히 동시는 아니고 조금씩 시간을 할당하고 그 시간만큼 실행함
  • 그 시간이 매우 짧아서 거의 동시에 실행하는 것처럼 보임

 

※ 퀀텀 타임

쓰레드가 CPU에서 점유하는 시간을 뜻함
예전에는 20ms 정도였다고 함

 

컨텍스트 스위칭
  • 그런데 이렇게 바꾸려면 이전에 어디까지 실행했는지 저장해야 한다
  • EIP는 실행 주소이고 ESP는 스택의 주소를 가리키는 레지스터
  • 이 주소를 저장해놓고 다음 쓰레드로 턴을 넘기면 그 스레드의 EIP와 ESP로 실행
  • TKO에는 컨텍스트라는 항목이 있는데 여기서 CPU의 레지스터 값을 보관함
  • 컨텍스트 스위칭은 두 개의 컨텍스트를 교환하는 것
  • GetThreadContext라는 함수로 TKO의 컨텍스트에 접근할 수 있다.

 

 

 

 

 


쓰레드 생성
  • 쓰레드 생성은 CreateThread 함수를 사용하면 된다.
#include <Windows.h>
using namespace std;

DWORD __stdcall foo(void* p)
{
    return 0;
}

int main()
{
    DWORD tid;

    HANDLE handle = CreateThread(0, 0, foo, 0, 0, &tid);

    getchar();
 
    return 0;
}

  • lpThreadAttributes - 커널 오브젝트의 공통적인 특성인 보안 속성, 관례상 0
  • dwStackSize - 쓰레드는 각각 스택을 가지는데 그 스택의 사이즈, 0을 주면 기본 1MB, 관례상 0
  • lpStartAddress - 쓰레드가 실행할 함수, 함수 형식을 맞춰줘야 함 (DWORD (__stdcall *)(LPVOID))
  • lpParameter - 3번째 인자인 함수의 매개변수
  • dwCreationFlags - 쓰레드 생성 시 옵션, 관례상 0 전달 - 즉시 실행
  • lpThreadId - TID값을 받을 변수의 주소, DWORD의 주소, 필요 없으면 0

 

CreateThread의 반환값은?

프로세스가 만들어지면 오브젝트 테이블이 같이 생김
쓰레드가 만들어지면 쓰레드를 관리하기 위해 TKO가 생기는데
그 쓰레드를 만든 건 프로세스이므로 프로세스의 오브젝트 테이블에 TKO가 들어감
CreateThread의 반환값은 핸들이며, 오브젝트 테이블의 인덱스임
TKO의 참조 계수

쓰레드를 생성하면 그 쓰레드의 참조 계수는 항상 2이다(만든 프로세스와 자기 자신)
반환된 쓰레드 핸들을 사용하면 만든 쓰레드를 제어할 수 있다.
그럴 일이 없다면 CloseHandle을 통해 쓰레드를 닫아야 한다.

 

 

 

 

 


_beginthreadex
  • CreateThread로 만들어진 쓰레드 함수 안에서 C 표준 함수를 사용하면 문제가 생김
  • 메모리 누수가 있을 수 있음(이후 후술)
  • 이 점 때문에 CreateThread보다 _beginthreadex가 더 안전하다.
  • _beginthreadex는 인자 중 3번째 함수 원형이 다름 ( unsigned int (__stdcall *)(void*))
  • tid를 꺼내오는 것도 DWORD의 주소가 아닌 unsigned int의 주소로 받아옴
  • _beginthreadex의 반환값은 unsigned long인데 HANDLE로 변환해서 사용함

 

 

 

 

 


쓰레드 종료
쓰레드를 종료하는 방법

1. 쓰레드 함수가 종료 (가장 좋음)
2. ExitThread() 또는 _endthreadex
3. TerminateThread

2번째와 3번째는 C++ 코드와 같이 사용할 경우 소멸자가 불리지 않음
자원 해제에 문제가 생김

 

주 쓰레드의 종료
  • 주 쓰레드가 main 함수에서 return되면 프로세스가 종료된다.
  • 프로세스 내의 모든 프로세스가 강제 종료
  • ExitThread(0)을 메인 함수에서 쓰면 메인 함수에서 주 쓰레드만 죽일 수 있음
  • 그러면 추가로 만든 쓰레드는 함수 스스로 반환할 때까지 실행한다.

 

쓰레드 종료를 대기하는 방법
  • TKO에는 시그널 필드와 종료 코드가 있다.
  • 쓰레드가 종료되면 종료 코드에 코드를 집어 넣고
  • 시그널 필드가 시그널 상태가 된다.
  • WaitForSingleObject 함수에 쓰레드 핸들과 INFINITE를 주면
  • 해당 쓰레드가 시그널 상태가 될 때까지 무한히 대기하겠다는 뜻

'운영체제 > [ecourse] Windows Programming' 카테고리의 다른 글

6-2-1. Semaphore 실습  (0) 2022.09.20
6-2. 동기화 개념과 Critical Section  (1) 2022.09.20
5-4. Stack Memory  (0) 2022.09.19
5-3. Heap Memory  (0) 2022.09.18
5-2. Virtual Memory Allocation  (0) 2022.09.18

댓글