스레드란?
- 프로세스 내의 제어 흐름
- 일반적으로 우리가 작성하는 코드는 단일 스레드 단일 프로세스
- 다중 스레드 프로세스는 하나의 프로세스에 여러 컨트롤이 존재함
쉽게 말해 스레드란 우리가 프로그램을 실행할 때 코드가 실행되는 흐름이라고 할 수 있다.
특징
- 동일 프로세스에서 동작하는 여러 개의 스레드는 코드 영역, 데이터 영역, 리소스(파일 디스크립터, 시그널 등)를 공유한다.
- 스레드는 각각 Program Counter(PC)를 가지고 있다 - 이는 생각해보면 당연하다. 각 스레드는 실행하는 코드의 위치가 다르다. 즉 실행하는 명령어가 다르다. 따라서 각각 PC를 가지고 있어야한다.
- 스레드는 스택 영역, 레지스터 집합, 스레드 ID를 각자 가지고 있다.
예시
그러하면 실제로 다중 스레드는 어디에 이용될까? 우리가 사용하는 웹 브라우저에서도 다중 스레드는 사용된다. 예를 들어 이미지, 텍스트 등 화면에 홈페이지를 표시해주는 스레드가 1개 존재한다면 백엔드로부터 데이터를 읽어오는 역할을 수행하는 스레드가 따로 동작하고 있을 수 있다. 이렇게 동시에 여러 기능을 수행하려면 다중 스레드를 이용하여야 한다.
함수
리눅스에서 제공하느 pthread 시스템 호출 함수를 이용하여 다중 스레드를 사용할 수 있다. 그럼 스레드를 제어할 수 있는 함수는 어떤 것들이 있는지 간단히 살펴보겠다.
- pthread_create()
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
- 새로운 스레드를 생성하는 함수이다. 함수의 원형은 위의 코드와 같다.
- 첫 번째 인자로 새로 생성할 스레드의 ID가 저장될 주소 값을 넘겨준다.
- 두 번째 인자는 기본 특성을 가지는 스레드의 경우 NULL을 넣어주고 pthread_attr_init()으로 pthread_attr_t 구조체를 초기화하여 사용자 모드 스레드나 커널 모드 스레드를 사용할 수 있다.
- 세 번째 인자는 새로 생성되는 스레드가 실행할 함수의 포인터를 준다.
- 네 번째 인자는 세 번째 인자의 함수에게 넘기고 싶은 정보를 구조체에 저졍하여 그 포인터를 넘겨주어 스레드가 실행하는 함수에서 그 정보를 사용할 수 있게 한다.
- pthread_exit()
void pthread_exit(void *rval_ptr);
- 스레드를 종료시키는 함수. 인자로 준 rval_ptr 값은 pthread_join의 두번 째 인자에서 받을 수 있다. 만약 pthread_detach()를 하지 않은 경우에는 스레드가 종료되었어도 스레드의 자원은 회수되지 않는다.
- pthread_join()
int pthread_join(pthread_t thread, void **rval_ptr);
- main스레드가 생성한 스레드가 종료될 때까지 기다리는 함수. 종료된 스레드의 자원을 회수하는 역할을 한다.
- pthread_join()을 호출한 스레드는 그 스레드가 pthread_exit()을 호출할 때까지 대기한다.
- main스레드의 종료로 인해 다른 스레드들이 강제로 종료되는 것을 방지한다.
- 첫 번째 인자는 종료를 기다리는 스레드의 id이다.
- 두 번째 인자는 pthread_exit()에서 넘겨주는 인자와 같은 값이다. 만약 스레드가 정상적으로 종료되었다면 두 번째 인자에 리턴 코드가 저장되고 스레드가 취소되면 rval_ptr이 가리키는 곳이 PTHREAD_CANCELED가 설정된다.
- 만약 pthread_create의 두 번째 인자인 pthread_attr_t의 인자로 NULL을 준 경우는 스레드를 join해야한다.
- pthread_detach()
int pthread_detach(pthread_t tid);
- pthread_join과 다르게 메인에서 스레드를 기다리지 않는 함수.
- 스레드가 종료될 경우 자동으로 자원이 회수된다. (pthread_join을 할 필요 없다)
- phtread_detach한 경우 pthread_join으로 스레드를 기다릴 수 없다. 즉 joinable하지 않다.
예제
실제로 다중 스레드를 이용하여 모두 공유하는 전역변수를 2개의 스레드가 1씩 증가시키며 출력하는 프로그램이다.
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
int var = 0;
void *func1(void *arg) { //1번 스레드 실행 함수
while(var < 10) {
printf("thread1 : %d\n", ++var);
sleep(1);
}
pthread_exit(NULL); //1번 스레드 종료
}
void *func2(void *arg) { //2번 스레드 실행 함수
while(var < 10) {
printf("thread2 : %d\n", ++var);
sleep(1);
}
pthread_exit(NULL); //2번 스레드 종료
}
int main() {
pthread_t tid1, tid2;
if(pthread_create(&tid1, NULL, func1, NULL) != 0) { //1번 thread 생성
fprintf(stderr, "thread create error\n");
exit(1);
}
if(pthread_create(&tid2, NULL, func2, NULL) != 0) { //2번 thread 생성
fprintf(stderr, "thread create error\n");
exit(1);
}
pthread_join(tid1, NULL); //1번 스레드 자원 회수
pthread_join(tid2, NULL); //2번 스레드 자원 회수
return 0;
}
결과
위 그림처럼 두 개의 스레드가 각각 공유하는 전역 변수를 1씩 증가시키며 출력하는 것을 볼 수 있다. 하지만 이상한 점이 있다. 스레드가 실행되는 순서가 일정하지 않다는 것이다. 이렇게 스레드가 실행되는 순서는 생성되는 순서와 전혀 관계가 없다. 스레드의 실행 순서를 제어하려면 동기화가 필요하다. 동기화는 이후에 다뤄보겠다.
개발환경
OS : wsl2 Ubuntu-20.04
gcc : Ubuntu 9.3.0-17ubuntu1~20.04
'리눅스 시스템 프로그래밍' 카테고리의 다른 글
[Linux] 조건 변수(condition)와 mutex를 이용한 스레드 동기화(리눅스 시스템 프로그래밍) (0) | 2021.08.12 |
---|---|
[Linux] Mutex를 이용하여 스레드(Thread) 동기화하기 (0) | 2021.08.11 |
[Linux] 저수준 파일 입출력과 고수준 파일 입출력 (0) | 2021.07.20 |
[Linux] Bash shell script programming 2021년도 요일 구하기 (0) | 2021.07.19 |
[Linux] Bash shell script programming 소수 합 구하기 (0) | 2021.07.19 |