[web proxy server] Thread

해롱그·2023년 9월 22일

네트워크

목록 보기
3/3

thread?

프로세스의 컨텍스트 내에서 돌아가는 논리 흐름

쓰레드는 커널에 의해서 자동으로 스케줄된다. 각 쓰레드는 고유의 정수 쓰레드 ID(TID), 스택, 스택 포인터, 프로그램 카운터, 범용 레지스터, 조건 코드를 포함하는 자신만의 쓰레드 컨텍스트를 가진다.
한 개의 프로세스에서 돌고 있는 모든 쓰레드는 이 프로세스의 전체 가상주소를 공유한다.

쓰레드에 기초한 논리흐름은 프로세스와 I/O 다중화에 기초한 흐름의 품질을 결합해준다. 프로세스와 같이 쓰레드는 커널에 의해 자동으로 스케줄되고, 커널에 정수 ID로 알려진다. I/O 다중화에 기초한 흐름에서와 같이 다수의 쓰레드는 한 개의 프로세스의 컨텍스트에서 돌아가며, 그러므로 이 프로세스 가상 주소공간의 전체 내용을 공유한다. 여기에는 코드, 데이터, 힙, 공유 라이브러리, 오픈한 파일들이 포함된다.

쓰레드 실행 모델

메인 쓰레드는 항상 프로세스에서 돌아가는 첫 번째 쓰레드라는 의미에서만 다른 쓰레드와 구별된다.

Posix 쓰레드

C 프로그램에서 쓰레드를 조작하는 표준 인터페이스
Pthreads는 데이터를 peer thread와 안전하게 공유하기 위해서, 시스템 상태의 변화를 피어들에게 알리기 위해서 프로그램이 쓰레드를 생성하고, 죽이고, 청소하도록 하는 약 60개의 함수를 정의한다.

첫 번째 쓰레드 프로그램을 살펴보자.

#include "csapp.c"

void *thread(void *vargp);

// main thread
int main() {  
    pthread_t tid;  // peer thread의 thread ID 저장 변수
    Pthread_create(&tid, NULL, thread, NULL);   // 새로운 peer thread 생성 -> 매인+피어 동시에 돌고있음
    Pthread_join(tid, NULL);    // 메인 쓰레드는 피어 쓰레드가 종료하기를 기다림
    exit(0);    // 현재 프로세스에서 돌고 있는 모든 쓰레드(이 경우에는 main)를 종료
}

// peer thread
void *thread(void *vargp) {
    printf("Hello, World!\n");
    return NULL;    // peer thread 종료
}

메인쓰레드는 피어쓰레드를 생성하고, 이것이 종료하기를 기다린다. 피어쓰레드는 Hello, World!를 출력하고 종료한다. 메인쓰레드가 피어쓰레드의 종료를 검출하면(Pthread_join), 이 프로세스를 exit 호출해서 종료한다.

쓰레드 생성

쓰레드는 pthread_create() 를 호출해서 다른 쓰레드를 생성한다.

pthread_create() : 새 쓰레드를 만들고, 쓰레드 루틴 arg를 새 쓰레드의 컨텍스트 내에서 입력 인자 attr를 가지고 실행된다. 인자는 새롭게 만들어진 쓰레드의 기본 성질을 바꾸기 위해 사용될 수 있지만, 책의 예제는 항상 pthread_create를 NULL attr인자로 호출한다.
pthread_create가 리턴할 때, 인자 tid는 새롭게 만들어진 쓰레드의 ID를 가진다.
새 쓰레드는 pthread_self 함수를 호출해서 자신의 쓰레드 ID를 결정할 수 있다.

쓰레드 종료

쓰레드는 다음의 한 가지 방법으로 종료한다.

  • 쓰레드는 자신의 최상위 쓰레드 루틴이 리턴할 때 묵시적으로 종료한다.
  • 쓰레드는 pthread_exit()을 호출해서 명시적으로 종료한다. 만일 메인 쓰레드가 pthread_exit()를 호출하면 다른 모든 쓰레드가 종료하기를 기다리고, 그 후에 메인 쓰레드와 전체 프로세스를 thread_return 리턴 값으로 종료한다.
  • 일부 peer thread는 리눅스 함수를 호출하며, 이것은 프로세스와 이 프로세스에 관련된 쓰레드 모두를 종료한다.
  • 다른 peer thread는 pthread_cancle()를 현재 쓰레드의 ID로 호출해서 현재 쓰레드를 종료한다.

종료한 쓰레드 삭제

쓰레드는 pthread_join() 을 호출해서 다른 쓰레드가 종료하기를 기다린다.
pthread_join()는 쓰레드 tid가 종료할 때까지 멈춰있으며, 쓰레드 루틴이 리턴한 기본(void*) 포인터를 thread_return이 가리키는 위치로 할당하고, 그 후에 종료된 쓰레드가 가지고 있던 모든 메모리 자원을 삭제한다.
리눅스 wait함수와는 달리 pthread_join함수는 특정 쓰레드가 종료하기를 기다려야 한다. 이 임의의 쓰레드가 종료하는 것을 기다리도록 지정할 수 있는 방법은 없다.

쓰레드 분리

언제나 쓰레드는 연결 가능하거나 분리(detach)되어 있다. 연결 가능한 쓰레드는 다른 쓰레드에 의해 청소되고 종료될 수 있다. 자신의 메모리 자원들(스택과 같은)은 다른 쓰레드에 의해 청소될 때까지는 반환되지 않는다. 반대로, 분리된 쓰레드는 다른 쓰레드에 의해서 청소되거나 종료될 수 없다. 자신의 메모리 자원들은 이 쓰레드가 종료할 때 시스템에 의해 자동으로 반환한다.

기본적으로 쓰레드는 연결 가능하도록 생성된다. 메모리 누수를 막기 위해서 각각의 연결 가능 쓰레드는 다른 쓰레드에 의해 명시적으로 소거되거나, pthread_detach() 를 호출해서 분리되어야 한다.

pthread_detach() : 연결 가능한 쓰레드 tid를 분리한다. 쓰레드들은 pthread_detach 인자로 pthread_self()를 사용해서 자기 자신을 분리할 수 있다.

쓰레드 초기화

pthread_once()는 쓰레드 루틴에 관련된 상태를 초기화할 수 있도록 한다.
once_control 변수는 전역 또는 정적 변수로 항상 PTHREAD_ONCE_INIT으로 초기화된다. pthread_once를 once_control 인자로 처음 호출하면 init_routine을 호출하며, 이 함수는 아무것도 리턴하지 않으며 인자도 없는 함수다. pthread_once()는 동적으로 다수의 쓰레드에 의해 공유된 전역 변수들을 초기화할 필요가 있을 때 유용하다.

쓰레드에 기초한 동시성 서버

메인 쓰레드는 반복해서 연결 요청을 기다리며, 그 후에 요청을 처리하기 위한 peer thread를 생성한다.
pthread_create를 호출할 때 어떻게 연결 식별자를 전달하는지 잘 살펴보자! 식별자를 가리키는 포인터를 아래와 같이 보내면 확실히 알 수 있다.

connfd = Accept(listenfd, (SA *) &clientaddr, &clientlen);
Pthread_create(&tid, NULL, thread, &connfd);

출처 Randal E. Bryant David R. O’Hallaron - Computer Systems A Programmer’s Perspective-Pearson (2015)

profile
사랑아 컴퓨터해 ~

0개의 댓글