스레드란?

  • 프로세스 내의 제어 흐름
  • 일반적으로 우리가 작성하는 코드는 단일 스레드 단일 프로세스
  • 다중 스레드 프로세스는 하나의 프로세스에 여러 컨트롤이 존재함

쉽게 말해 스레드란 우리가 프로그램을 실행할 때 코드가 실행되는 흐름이라고 할 수 있다.

 

특징

  • 동일 프로세스에서 동작하는 여러 개의 스레드는 코드 영역, 데이터 영역, 리소스(파일 디스크립터, 시그널 등)를 공유한다.
  • 스레드는 각각 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

+ Recent posts