본문 바로가기

Operating System

[Ch 4] Thread: Benefits of Multithreading, <pthread> library

4.1 Overview

Thread는 CPU utilization의 기본 단위이다. Thread는 thread ID, PC, register, stack으로 구성되며, 같은 프로세스에 속한 다른 thread들끼리 코드, 데이터(global data), 파일과 같은 다른 OS resource들을 공유한다. Traditional process는 하나의 thread를 가졌지만, multiple thread를 가지면 동시에 여러 개의 task를 수행할 수 있다.

출처: 교재 Figure 4.1

4.1.2 Benefits

1) Responsiveness: User와의 interaction에 대한 responsiveness를 향상시킬 수 있다. User의 클릭을 받는 것과 time-consuming task를 서로 다른 thread로 만들어두면, time-consuming operation과 상관없이 user에게 responsive한 상태를 유지할 수 있다. 만약 이것을 single thread 프로그램으로 만든다면, time-consuming task를 실행하는 동안 user 클릭에 대한 처리는 할 수 없으므로 responsiveness가 낮아질 것이다.

2) Resource sharing: 같은 프로세스 내의 thread들은 코드와 데이터를 서로 공유한다. 하나의 어플리케이션 내에서 서로 다른 동작을 하는 여러 개의 thread를 만들어서 동작시킬 수 있다.

3) Economy: 프로세스에 비해 light-weight하므로, 생성이나 context switch가 가볍다.

4) Scalability: multi-threaded 프로그램은 여러 개의 core가 있는 시스템에서 병렬적으로 실행됨으로써 성능 향상을 이룰 수 있다.

 

4.4 Thread Library

Thread library는 프로그래머에게 thread를 생성하고 조작하는 API를 제공한다.

4.4.1 Pthreads

Pthreads는 POSIX standard (IEEE에 규정되어 있는)를 말하며, 이것은 thread behavior에 대한 specification이며, 구현에 관한 것은 아니다. 따라서 구체적인 구현 방법은 OS desinger에 따라 달라진다.

 

주요 API는 thread를 생성하는 pthread_create()와 parent thread가 child thread를 wait하는 pthread_join()인데, 이를 사용한 간단한 예제 코드는 아래와 같다. main 함수의 argument로 몇 개의 thread를 만들 것인지를 전달받아, 해당 개수만큼 thread를 만드는 것이다.

#include <stdio.h>
#include <stdlib.h>		// malloc
#include <pthread.h>	// pthread

void* thread_worker(void *arg) {
	int tid = *(int*) arg;
	printf("Hi, I am thread %d\n", tid);
}

int main(int argc, char* argv[]){
	pthread_t* tid;		// array of thread id
	int num_threads = atoi(argv[1]);
	tid = (pthread_t*) malloc (sizeof(pthread_t) * num_threads);
	int* param = (int*) malloc (sizeof(int) * num_threads);
	
	for (int i = 0; i < num_threads; i++) {
		param[i] = i;
		pthread_create(&tid[i], NULL, thread_worker, (void*)&param[i]);
	}

	for (int i = 0; i < num_threads; i++) {
		pthread_join(tid[i], NULL);
	}
	
	free(tid);
}

Thread 생성 시, 첫 번째 parameter로는 tid의 주소값을 넘겨준다. Thread가 생성되면서 tid가 할당되는데, 이 때 tid 변수 값을 바꿀 수 있어야 하기 때문에 포인터로 넘겨준다고 추측된다. 두 번째 parameter는 attribute라고 되어 있는데, NULL로 써도 동작이 정상적으로 되므로 넘어가기로 한다. 세 번째 parameter는 thread가 실행할 routine이며, 여기에는 void형 포인터로 선언된 함수가 들어간다. 네 번째 parameter는 thread에 넘겨줄 argument로, 이것도 일반적으로 void형 포인터로 넘겨준다. 다만, 값을 전달만 하려면 변수로 넘겨주고 thread 내에서도 변수로 받을 수 있다.

 

실행 결과는 아래와 같다. 참고로 컴파일 시 -lpthread를 붙여주지 않으면 pthread의 API가 undefined reference라고 뜬다.

Thread 간의 순서는 OS 스케쥴링에 따라 달라지므로, 생성된 이후로 각 thread의 실행의 순서는 프로그래머가 보장할 수 없다. 여기서도 대체로 thread 생성 순서와 비슷하나, 출력 순서가 정확히 일치하지는 않는다.

 

Reference

A. Silberschatz, Operating System Concepts 10th ed, WILEY (2019) Ch 4