Intro to Threads

What is a thread?

쓰레드는 thread-of-execution의 줄임말입니다. 쓰레드는 CPU가 실행해야 할 연속된 instruction들을 나타냅니다. 쓰레드는 function call로부터 어떻게 return하는지 기억하고, automatic variable과 parameter를 저장하기 위해 스택을 사용합니다.


What is a Lightweight Process(LWP)? How does it relate to threads?

쓰레드의 모든 의도와 목적은 프로세스를 제외하고 copy on write가 불가능게 하는 것입니다. (쓰레드를 생성하는 것은 fork과 유사함) 이렇게 함으로써 프로세스는 같은 메모리 공간과 변수, 힙, FD등을 공유할수 있습니다.
쓰레드를 만드는 실제 System call은 fork와 유사합니다. 쓰레드를 생성할 때는 clone을 이용합니다. 상세한 내용은 여기서 설명하는 과정을 벗어나지만 man page를 이용해 알아볼 수 있습니다. 

LWP 또는 쓰레드는 만드는 overhead가 훨씬 적기 때문에 많은 시나리오를 forking할 때 선호되어집니다. 하지만 몇몇 경우에 multiple process가 코드를 더 빠르게 만드는 방법일 수도 있습니다(주로 파이썬 유저)

How does the thread's stack work?

main function 또는 호출되어지는 다양한 function에서는 automatic variable들이 존재합니다. 그들을 메모리에 저장할 때 stack을 이용하고, stack이 얼마나 증가했는지를 stack pointer라는 simple pointer를 이용해 추적합니다. 만약 쓰레드가 다른 function을 호출했다면 stack pointer는 아래로 이동하게 되어 parameter나 automatic variable을 저장하기 위한 공간이 증가하게 됩니다. 일단 이 function이 return되고나면, stack pointer도 이전 값으로 돌아가게 됩니다. 예전 stack pointer의 값을 stack에 가지고 있기 때문에 function에서 return하는 것은 매우 빠릅니다. 또한 stack pointer의 위치를 바꿔줌으로써 그 function안에서 automatic variable을 저장하는데 사용한 메모리를 free시켜주는 것이 가능합니다.

멀티쓰레드 프로그램에서 여러개의 스택이 있어도 하나의 어드레스 공간만 존재합니다. pthread library의 스택 공간을 할당하고(이 스택 공간은 힙이나 main program의 스택에 할당 될 수 있습니다), clone 함수를 호출해 그 stack address에 있는 쓰레드를 수행합니다.그림으로 보면 아래와 같습니다.




How many threads can my process have?

프로세스 안에서 동작하는 하나 이상의 쓰레드가 존재합니다. 첫번째 쓰레드는 그냥 얻을 수 있습니다. 이 쓰레드는 main안에 쓰여진 코드를 동작시킵니다. 만약 쓰레드가 더 필요하다면 pthread library의 pthread_create를 호출해 새로운 쓰레드를 생성할 수 있습니다. 쓰레드가 어디서 시작해야 할 지 알려주기 위해서 function의 포인터를 넘겨줄 필요가 있습니다.

생성한 쓰레드들은 한 프로세스의 일부분이기 때문에 같은 가상 메모리 안에 존재합니다. 그러므로 이 쓰레드들은 힙을 볼수도 있고, 전역 변수나 프로그램 코드를 볼 수도 있습니다. 그러므로 프로그램의 한 프로세스를 동시에 2개이상의 CPU들이 동작할 수 있습니다. 쓰레드들을 CPU에 할당해 동작하게 하는 일은 OS에 달렸습니다. 만약 active thread가 현재 CPU보다 더 많다면 커널은 thread에게 CPU를 짧은 간격으로 할당해줄 것입니다.(또는 모든 일이 끝날 때까지 할당해줌) 그리고 CPU를 다른 쓰레드로 switch해줄 것입니다. 예를 들면, 한 CPU가 그래픽의 결과를 계산하고 있을 때 또 다른 CPU는 게임 AI를 처리하고 있을 수 있습니다.


Simple Usage

Hello world pthread example

pthread를 사용하기 위해 pthread.h를 include해주어야 합니다. 그리고 컴파일 시 -pthread(-lpthread)옵션을 사용해야 합니다. 이 옵션은 컴파일러에게 이 프로그램이 Threading support가 필요하다는 것을 알려줍니다. 

쓰레드를 생성하려면 pthread_create를 이용합니다. 이 함수의 argument는 다음과 같습니다.

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);


첫번째 포인터는 새롭게 생성될 쓰레드의 id를 저장할 변수입니다.

두번째 포인터는 pthread의 attribute의 포인터입니다. 이 attribute는 pthread의 고급 기능중 일부를 조정하는 데 사용됩니다.

세번째는 수행하고 싶은 함수의 포인터입니다.

네번째는 함수에게 주어질 포인터입니다.


void *(*start_routine) (void *) 인자는 읽기 어려울 수도 있습니다. 이것은 void * pointer를 받아 void * pointer를 반환하는 포인터를 의미합니다.  이 부분은 함수의 이름이 (*....)으로 둘러싸여져있다는 점을 제외하면 함수의 선언과 유사합니다.


다음에 가장 간단한 예가 존재합니다.

#include <stdio.h>
#include <pthread.h>
// remember to set compilation option -pthread

void *busy(void *ptr) {
// ptr will point to "Hi"
    puts("Hello World");
    return NULL;
}
int main() {
    pthread_t id;
    pthread_create(&id, NULL, busy, "Hi");
    while (1) {} // Loop forever
}



만약 쓰레드가 종료되는 것을 기다리고 싶다면 다음과 같이 pthread_join을 사용합니다.

void *result;
pthread_join(id, &result);


위의 예에서 busy 함수가 NULL을 리턴하므로 result는 null이 될것입니다. pthread_join이 포인터의 contents에 쓸 것이므로 result의 주소를 넘겨줄 필요가 있습니다.


질문. 아래의 while문이 없으면 출력이 되지 않는데, 그 이유는??




Posted by 몰랑&봉봉
,