TCP-IP 윈도우 소켓 프로그래밍

[TCP/IP] 6장. 멀티 스레드

민돌v 2021. 4. 26. 17:33

1. 스레드 기초

1) 소켓 응용 프로그램과 멀티 스레드

 

● TCP 서버 - 클라이언트의 문제점

  1. 클라이언트 2개 이상이 서버에 접속할 수는 있으나, "서버가 동시에 클라이언트 2개 이상에 서비스할 수 없다."
  2. 서버와 크라이언트의 send(), recv()함수의 호출 순서가 맞아야한다.
    • 데이터를 보내지 않은 상태에서 양쪽에서 동시에 recv()함수를 호출하면 교착 상태가 발생할 수 있다.

 

● 1번 문제의 해결방법들과 장단점

 

방법1. 서버가 각 클라이언트와 연결해 통신하는 시간을 짧게 줄인다.

  • 클라언트가 데이터를 전송하기 전에 매번 서버에 접속하고, 전송 후에는 곧바로 접속을 끊는 방식
  • 장점
    • 쉽게 구현 가능
    • 서버의 시스템 자원을 적게 사용
  • 단점
    • 파일 전송 프로그램과 같이 대용량 데이터를 전송하는 응용프로그램에는 적합하지 않음
    • 클라이언트 수가 많을 경우 처리 지연시간이 길어질 확률이 높다. (처리의 효율성 Down)

방법2. 서버에 접속한 각 클라언트를 스레드를 이용해 독립적으로 처리한다.

  • 장점
    • 소켓 입출력 모델(아래의 방법)에 비해 비교적 쉽게 구현 가능
  • 단점
    • 접속한 클라이언트 수에 비례해 스레드를 생성하므로 서버의 시스템 자원을 많이 사용한다.

방법3. 소켓 입출력 모델을 사용

  • 장점
    • 소수의 스레드를 이용해 다수의 클라이언트를 처리할 수 있다. 따라서 2번째 방법보다 서버의 시스템 자원을 적게 사용한다.
  • 단점
    • 위의 2방법보다 구현이 어렵다.

 

 

● 2번 문제의 해결방법들과 장단점(교착상태 발생 가능)

 

방법1. 데이터 송수신 부분을 잘 설계해 교착 상태가 발생하지 않게한다.

  • 장점
    • 특별한 기법을 도입하지 않고도 구현가능
  • 단점
    • 데이터 송수신 패턴에 따라 교착 상태가 발생할 수 있다. 따라서 이 방법을 모든 경우에 적용할 수는 없다.

방법2. 소켓에 타임아웃(timeout)옵션을 적용해, 소켓 함수 호출시 작업이 완료되지 않아도 일정 시간 후에 리턴하게 한다.

  • 장점
    • 비교적 간단한 구현
  • 단점
    • 기다려야 하기 때문에 다른 방법보다 성능이 떨어진다.

방법3. 넌블로킹(nonblocking) 소켓을 이용한다.

  • 논블로킹이란, 근본적으로 교착상태를 해결할 수 있는 방법이다.
  • 시스템에 교착상태가 일어났는지 안일어났는지를 계속해서 모니터링하는 방법이다.
  • 장점
    • 교착 상태를 막을 수 있다.
  • 단점
    • 구현이 복잡하다. 시스템 자원(특히 CPU 시간)을 불필요하게 방비할 가능성이 크다.

방법4. 소켓 입출력 모델을 사용한다.

  • 장점
    • 넌블로킹 소켓의 단점을 보완함과 더불어 교착 상태를 막을 수 있다.
  • 단점
    • 1,2 번 방법보다 구현이 어렵다. 그러나 3번째 방법보다는 쉬고 일관성 있게 구현할 수 있다.

 

 

 

2) 스레드 기본 개념

운영체제관점에서,

프로세스는 CPU시간을 할당받아 실행 중인 프로그램을 말한다.

프로그램이 저장 장치에 파일로 존재하는 정적인 개념이고

프로세스느 동적인 개념이다.

 

윈도우 운영체제관점에서는,

프로세스 개념을 프로세스와/ 스레드 두개로 구분한다.

 

● 프로세스와 스레드

  • 프로세스 
    • 코드, 데이터, 리소스를 파일에서 읽어들여 윈도우 운영체제 가 할당해놓은 메모리 영역에 담고 있는 일종의 컨테이너로 정적인 개념
  • 스레드 
    • CPU 시간을 할당받아 프로세스 메모리 영역에 있는 코드를 수행하고 데이터를 사용하는 동적인 개념
  • 주 스레드 or 메인 스레드 
    • 응용 프로그램 실행 시 최초로 생성되는 스레드 – WinMain() 또는 main() 함수에서 실행 시작
  • 컨텍스트 전환
    • CPU가 2개 이상의 스레드를 동시에 실행은 불가능하지만 교대로 실행이 가능하다.
    • 교대로 실행하는 간격이 짧다면 두 스레드가 동시에 실행되는 것 처럼 느껴진다.
    • 이렇게 하려먼 각 스레드의 최종 실행 상태를 저장하고 나중에 복원하는 작업을 해야한다.
    • 스레드 실행 상태의 저장과 복원잡업을 컨텍스트 전환이라고 한다.

 

 

 

● 멀티스레드 동작 원리

 

(a) 스레드 (1) 실행

(b) 컨텍스트 전환 (1~2)

(c) 스레드 (2) 실행

(d) 컨텍스트 전환(2->1)

(e) 스레드 (1) 실행

 

 

3) 스레드의 생성과 종료

 

● 스레드 생성에 필요한 요소

 

프로세스가 생성되면  main()함수를 실행 시작점으로 하는 주 스레드가 자동으로 생성된다.

이때 또 다른 함수인 f()를 실행 시작점으로 하는 스레드를 생성하려면 다음과 같은 정보를 운영체제에 제공해야 한다.

 

  • 스레드 함수의 시작주소 ( f( ) 함수의 시작 주소)
    • 운영체제는 f()함수의 시작 주소를 알아야 한다.
    • f( ) 함수와 같이 스레드 실행 시작점이 되는 함수를 스레드 함수라 한다.
  • 스레드 함수 실행 시 사용할 스택의 크기
    • c/c++ 프로그램의 모든 함수는 실행 중 인자 전달과 변수 할당을 위해 스택이 필요하다.
    • f( )함수를 실행 시작점으로 하는 스레드 2개를 생성하고자 한다면, 서로 다른 메모리 위치에 스택 두개를 할당해야 한다.
    • 스레드 실행에 필요한 스택 생성은 자동으로 생성되므로, 스택의 크기만 알려주면 된다.

● 스레드 생성 함수

 

CreateThread() 함수

: 스레드 생성 후 스레드 핸들을 리턴

HANDLE CreateThread (
    LPSECURITY_ATTRIBUTES lpThreadAttributes, // NULL
    SIZE_T dwStackSize, // 0
    LPTHREAD_START_ROUTINE lpStartAddress, // 스레드 함수
    LPVOID lpParameter, // 스레드 함수에 전달할 인자
    DWORD dwCreationFlags, // 0 또는 CREATE_SUSPENDED
    LPDWORD lpThreadId // 스레드 ID
) ;


성공: 스레드 핸들, 실패: NULL

 

스레드 함수 형태

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	...
}

 

● 스레드 종료 방법

  1. 스레드 함수가 리턴
  2. 스레드 함수 내에서 ExitThread() 함수를 호출
  3. 다른 스레드가 TerminateThread() 함수를 호출
  4. 주 스레드가 종료하면 모든 스레드가 종료

 

스레드 종료 함수

 

void ExitThread (
	DWORD dwExitCode // 종료 코드
) ;


BOOL TerminateThread (
    HANDLE hThread, // 종료할 스레드를 가리키는 핸들
    DWORD dwExitCode // 종료 코드
) ;
						
성공: 0이 아닌 값, 실패: 0

 

 

 

4) 스레드 제어

스레드는 윈도우 운영체제의 실행 단위므로, 우선순위를 변경하거나 실행을 중지하고 재시작하는 등의 제어 기능을 윈도우 API수준에서 지원한다.

 

 

● 스레드 우선순위 변경

  • 스레드 스케줄링 or CPU 스케줄링
    • 윈도우 운영체제가 각 스레드에 CPU 시간을 적절히 분배하 기 위한 정책
  • 우선순위 클래스
    • 프로세스 속성으로, 같은 프로세스가 생성한 스레드는 우선 순위 클래스가 모두 같음
  • 우선순위 레벨
    • 스레드 속성으로, 같은 프로세스에 속한 스레드 간 상대적인 우선순위를 결정할 때 사용
  • 기본 우선순위
    • 우선순위 클래스와 우선순위 레벨을 결합한 값으로, 스레드 스케줄링에 사용

● 우선순위 클래스

REALTIME_PRIORITY_CLASS(실시간)
HIGH_PRIORITY_CLASS(높음)
ABOVE_NORMAL_PRIORITY_CLASS(높은 우선순위; 윈도우 2000 이상)
NORMAL_PRIORITY_CLASS(보통)
BELOW_NORMAL_PRIORITY_CLASS(낮은 우선순위; 윈도우 2000 이상)
 IDLE_PRIORITY_CLASS(낮음)

 

● 우선순위 레벨

THREAD_PRIORITY_TIME_CRITICAL
THREAD_PRIORITY_HIGHEST
THREAD_PRIORITY_ABOVE_NORMAL
THREAD_PRIORITY_NORMAL
THREAD_PRIORITY_BELOW_NORMAL
THREAD_PRIORITY_LOWEST
THREAD_PRIORITY_IDLE

 

● 윈도우의 스레드 스케줄링 방식

  • 기본 우선순위 : 우선순위 클래스와 우선순위 레벨을 결합하면 스레드의 기본 우선순위가 결정된다.
  • 이 값이 스레드 스케줄링에 사용되는데, 우선순위가 가장 높은 스레드에 CPU시간을 할당한다.
  • 우선순위 같은 스레드가 여러 개 있을 경우 CPU 시간을 번갈아 가며 할당한다.