본문 바로가기

운영체제

쓰레드(thread)

앞서 프로세스에서 각 프로세스는 각자의 PC, 레지스터, 주소 공간 이외의 많은 것들을 가진다고 했었다. 또한 각 프로세스는 서로 독립적이기에 상관 관계가 없을 때는 useful하다는 사실을 알고 있다. 


즉, 경우에 따라서 복사본 자원의 낭비가 생길 수 있게 된다. 이러한 문제점을 해결하기 위해 쓰레드(thread)를 사용한다.


쓰레드는 lightweight 프로세스이며 context는 PC, 레지스터 집합, 스택으로 구성된다.


다시 말해 프로세스를 생성하던 fork는 부모와 자식 프로세스를 똑같이 실행한 반면 프로그램의 서브루틴을 독립적으로 실행되게 해주는 것을 쓰레드라고 한다.



쓰레드로 생성할 함수의 원형은 다음과 같다.

1
void *function_name(void *parameters);


쓰레드를 생성하기 위한 함수의 원형은 다음과 같다.

1
2
#include<pthread.h>
int pthread_create(pthread_t *thread_id, pthread_attr *attr, void arg);

thread_id : 각 쓰레드를 구분하기 위한 쓰레드의 ID가 저장될 변수

attr : 쓰레드의 특징, 대개는 NULL.

arg : 쓰레드로 실행할 함수의 명칭과 매개 변수를 나열


간단히 표현하자면 pthread_create(주소, NULL, 함수 이름, 파라미터)이며 성공 시 0을, 실패 시 -1을 리턴한다.


마지막으로 컴파일 옵션을 확인해보면(gcc 기준이다.)

gcc -D_REENTRANT thread.c -o thread -lpthread

이며, -D_REENTRANT는 한 함수(쓰레드)가 두 개 이상의 함수에 의해 호출되었을 때, 호출 순서에 무관하게(또는 섞이더라도) 하나가 실행되고 난 다음 다른 함수가 실행된 것처럼 즉, 제대로 된 결과가 반환되도록 해준다.

간단한 예를 들어 thread_a()가 실행되고 있다가 interrupt가 발생하고, 다른 곳에서 thread_a()를 호출한 경우 PC가 순간적으로 2개 생기는데 다른 곳에서 사용하더라도 기존에 실행중이던 결과는 영향이 없게 해준다.

lpthread는 라이브러리이며 링크해주어야 한다.


그럼 간단한 예제를 통해 다중 쓰레드 프로그래밍을 실습해보자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <pthread.h> void *go(void *arg) { int i; for(i=0; i<100; i++) printf("i ⇒ %d\n", i); } void *come(void *arg) { int j; for(j=0; j<100; j++) printf("j ⇒ %d\n", j); } int main() { pthread_t goThreadID; pthread_t comeThreadID; int goThreadState = pthread_create(&goThreadID, NULL, go, NULL); if ( goThreadState !=0 ) printf("goThread 생성에러"); int comeThreadState = pthread_create(&comeThreadID, NULL, come, NULL); if ( comeThreadState !=0 ) printf("comeThread 생성에러"); printf("쓰레드 종료..\n\n"); return 0; }

길어보이지만 에러 핸들 빼고, 함수 빼면 정말 간단한 프로그램임을 알 수 있다.


어쨌거나 위 예제를 실행해보면 i와 j가 0~99까지 출력될 것이라 예상할 수 있다.


그러나 원하는대로 되면 굳이 예제를 쓸 리가 없다 !!

안타깝게도 이런 결과가 나온다. main 쓰레드가 go나 come 쓰레드보다 먼저 종료되기 때문이다. (꼭 이렇지 않더라도 100번의 메시지가 출력되지 않는다.)


따라서 자식 쓰레드가 종료될 때까지 부모 쓰레드가 기다려야만 한다. 당연히 선구자들이 만들어놨고 이런 기능을 하는 함수의 원형은 다음과 같다.

1
pthread_join(pthread_t *thread_id, void **thread_return);

thread_id는 유추할 수 있듯이 종료를 대기할 쓰레드의 ID이다.

thread_return은 쓰레드 종료 시 반환하는 값에 접근할 수 있는 포인터의 포인터이다. 뭐 어디에 쓰는건진 잘 모르겠는데 아무튼 그렇다고 한다.


pthread_join함수를 이용해 예제를 고쳐보면 다음과 같다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <pthread.h> void *go(void *arg) { int i; for ( i=0; i<100; i++ ) print f( "i ⇒ %d\n" , i); } void *come(void *arg) { int j; for ( j=0; i<100; j++ ) printf ( "j ⇒ %d\n" , j); } int main() { void * t_return; int state; pthread_t goThreadID; pthread_t comeThreadID; int goThreadState = pthread_create (&goThreadID, NULL,go, NULL); if ( goThreadState != 0) printf("goThread 생성에러"); int comeThreadState = pthread_create (&comeThreadID, NULL,come, NULL); if ( comeThreadState != 0) printf("comeThread 생성에러"); state = pthread_join ( goThreadID, &t_return ); state = pthread_join ( comeThreadID, &t_return ); return 0; }

이렇게 하면 제대로 된 결과가 나온다.


중간에 짤랐지만 아무튼 99까지 i, j 모두 나온다.


뒷부분도 간단하지만 오늘은 여기까지만..

빠르게 올렸어야 했는데 계절학기때문에 또 밀려버렸다.. ㅜㅜ

'운영체제' 카테고리의 다른 글

프로세스 생성  (0) 2020.07.12
프로세스  (0) 2020.07.11
시스템 콜  (0) 2020.07.07
OS Structure, Hardware Protection  (0) 2020.07.07
메모리  (0) 2020.07.06