네트워크 프로그래밍 6주차

Hyun·2024년 10월 15일
0

6.1. 스레드 기초

지금까지 작성한 TCP 서버-클라이언트의 문제점:

  1. 동시에 둘 이상의 클라이언트 서비스 불가 (다중 처리 문제)

    해결책 1: 서버가 각 클라이언트와 통신 시간 줄임
    => 구현이 쉽지만 대용량에 적합하지 않음

    해결책 2: 각 클라이언트를 스레드 이용해 독립 처리
    => 소켓 입출력 모델에 비해 구현 쉽지만 서버의 시스템 자원 많이 사용

    해결책 3: 소켓 입출력 모델 사용
    => 소수의 스레드 이용해 다수의 클라이언트를 처리하지만 구현 어려움

  2. 교착 상태 발생 가능성이 있음

    교착 상태(deadlock): 영원히 일어나지 않을 사건을 두 프로세스가 기다리는 상황

    해결책 1: 데이터 송수신 부분 잘 설계
    => 곧바로 구현 가능하지만 모두 해결 불가능
    해결책 2: 소켓에 타임아웃 옵션 적용( setsockopt 함수)
    => 구현이 쉽지만 성능 떨어짐
    해결책 3: 넌블로킹 소켓 사용
    => 조건 만족하지 않더라도 소켓함수 즉시 리턴해 해결하지만 구현 복잡
    해결책 4: 입출력 모델 사용
    => 넌블로킹 단점 보완하며 해결하지만 구현이 어려움

프로그램: 코드, 데이터, 리소스들의 집합
프로세스는 이러한 파일들을 읽어들여 메모리 영역에 담고 있는 컨테이너
스레드란? CPU 시간을 할당 받아 프로세스 메모리 영역에 있는 코드를 수행하고 데이터를 사용하는 실행 흐름

  • 주 스레드(메인 스레드) : 응용 프로그램 실행 시 최초로 생성되는 스레드

  • main() 함수 또는 WinMain() 함수에서 실행 시작

  • 컨텍스트 전환: 스레드의 실행 상태의 저장과 복원 작업, 교대로 CPU를 사용하지만 지속적인 일을 가능하게 함

6.2. 스레드 API

스레드 생성시 필요 요소

  • 스레드 함수 이름이 f일 때 스레드 생성에 필요한 요소
  1. f() 함수의 시작 주소 (함수 이름 = 함수의 시작 주소)
  2. f() 함수 실행 시 사용할 스택의 크기
  • 스레드의 스택: 스레드마다 독립적으로 할당되는 메모리 영역,
    함수 호출 시 지역변수, 매개변수, 리턴 주소 등의 정보 저장
  • 스레드 실행시 필요한 스택 생성은 운영체제가 자동 할당,
    응용프로그램은 스택 크기만 알려주면 됨

CreateThread() 함수: 스레드 생성 후 스레드 핸들을 리턴

HANDLE CreateThread (
	LPSECURITY_ATTRIBURES lpThreadAttributes, // 보안 관련 옵션
    SIZE_T dwStackSize, // 스레드에 할당되는 스택 크기(바이트 단위 설정(0일 시 디폴크 크기 1M)
    LPTHREAD_START_ROUTINE lpStartAddress, // 스레드 함수의 시작 주소, 함수 이름
    LPVOID lpParameter, // 스레드 함수에 전달할 인자
    DWORD dwCreationFlags, 
    LPDWORD lpThreadId // 스레드 아이디 리턴
);

스레드 함수의 형태

DWORD WINAPI 스레드함수명(LPVOID lpParameter)
{
...
}

호출 규약

  • 리턴값: DWORD, 0을 리턴하면 종료
  • 함수 이름 앞에 WINAPI 키워드 작성 (ex. #define WINAPI__stdcall)
  • 파라미터: LPVOID lpParameter : CreateThread 함수의 네번째 파라미터와 같은 자료형

한 프로세스에서 생성할 수 있는 스레드의 최대 개수?

ex. 32비트 윈도우? 표현할 수 있는 범위 = 4GB

스레드 종료 방법

  1. 스레드 함수가 리턴
  2. 스레드 함수 안에서 ExitThread() 함수를 호출
  3. 다른 스레드가 TerminateThread() 함수를 호출해 스레드 강제 종료
  4. 메인 스레드가 종료하면 프로세스 내 다른 모든 스레드가 강제 종료
void ExitThread(
	DWORD dwExitCode // 종료 코드
); 

BOOL TerminateThread(
	HANDLE hThread, // 종료할 스레드를 가리키는 핸들
    DWORD dwExitCode, // 종료 코드
);

BOOL GetExitCodeThread(
	HANDLE hThread, // 종료 코드를 확인할 Thread 의 핸들
    LPDWORD lpExitCode // 종료 코드를 받아올 변수의 주소 값
);
  • CreateThread(), ExitThread() API : 문제점은 스레드 구문 내부에 c/c++ 런타임 함수 사용시 사용하던 정적 변수가 다른 스레드에서 초기화될 수 있음
  • _beginthereadx(), _endthreadx()의 사용
    : 내부적으로 CreatThread(), ExitThread 사용하여 새로운 스레드 생성하거나 종료,
    메모리 블록을 할당하여 스레드 마다 따로 관리, 종료 후 메모리 정리,
    #include <process.h> 필요,

6.2.1. 스레드 우선순위 변경하기

  • CPU 스케줄링(스레드 스케줄링): 윈도우 운영체제가 각 스레드에 CPU 시간을 적절히 분배하기 위한 정책
  • 우선순위 결정 요소: 우선순위 클래스 + 우선순위 레벨
  • 우선순위 클래스 : 프로세스 우선순위 => 각 프로세스가 생성한 스레드는 우선순위 클래스가 모두 같다
  • 우선순위 레벨: 스레드 우선순위 => 같은 프로세스에 속한 스레드 간 상대적인 우선순위를 결정할 때 사용



우선순위 레벨 관련 API 함수

  • SetThreadPriority() 함수는 우선순위 레벨 변경 시 사용
  • GetThreadPriority() 함수는 현재의 우선순위 레벨을 얻을 때 사용
BOOL SetThreadPriority (
	HANDLE hThread, // 스레드 핸들
    int nPriority // 우선순위 레벨
);

int GetThreadPriority)
	HANDLE hThread // 스레드 핸들
);

6.2.2. 스레드 종료 기다리기

  • WaitForSingleObject() 함수
    : 특정 스레드가 종료할 때까지 기다리기
DWORD WaitForSingleObject (
	HANDLE hHandle, 
    DWORD dwMillieseconds // 타임아웃 설정, INFINITE => 무한대기
);

// 사용 예시
HANDLE hThread = CreateThread(...);
DWORD retval = WaitForSingleObject(hThread, 1000);
if(retval == WAIT_OBJECT_0) { ... } // 스레드 종료
else if(retval == WAIT_TIMEOUT) { ... } // 타임아웃(스레드는 아직 종료 안 함)
else { ... } // 에러 발생
  • WaitForMultipleObjects() 함수
    : 둘 이상의 스레드가 종료할 때까지 기다리기
DWORD WaitForMultipleObjects (
	DWORD nCount,
    const HANDLE* lpHandles,
    BOOL bWaitAll, // True=모두 기다림, FALSE= 하나만
    DWORD dwMilliseconds
);

// 사용 예시 1: 모든 스레드의 종료를 기다린다
HANDLE hThread[2];
HANDLE hThread[0] = CreateThread(...);
HANDLE hThread[1] = CreateThread(...);
WaitForMultipleObjects(2, hThread, TRUE, INFINITE);

// 사용 예시 2: 스레드 하나의 종료를 기다린다
HANDLE hThread[2];
HANDLE hThread[0] = CreateThread(...);
HANDLE hThread[1] = CreateThread(...);
DWORD retval = WaitForMultipleObjects(2, hThread, FALSE, INFINITE);
switch(retval){
case WAIT_OBJECT_0: // hThread[0] 종료
break;
case WAIT_OBJECT_0 + 1: // hThread[1] 종료
break;
case WAIT_FAILED: // 오류 발생
break;
}

6.2.3. 스레드 실행 일시 중지와 재시작하기

// 실행 일시 중시 함수 1
DWORD SuspendThread (
	 HANDLE hThread // 스레드 핸들
);

// 재실행 함수
DWORD ResumeThread (
	HANDLE hThread // 스레드 핸들
);

// 실행 일시 중지 함수 2
void Sleep(
	DWORD dwMilliseconds // 밀리초 (ms)
);

중지 횟수가 관리됨: SuspendThread(): +1, ResumeThread(): -1

빠르게 컨텍스트 전환하기

  • Sleep(0): 스레드는 자신에게 할당된 CPU 시간을 포기하고 우선순위가 같은 다른 스레드에게 CPU 제어권을 넘겨준다.

6.3. 멀티스레드 TCP 서버

0개의 댓글

관련 채용 정보