1. Thread란?
a) 표준 thread API
- 리눅스의 스레드는 POSIX 스레드 또는 Pthread라고 부른다.
b) Pthread
- C언어로 구현된 유닉스 시스템의 핵심 스레딩 라이브러리다.
- 저수준 API로 100여개의 함수를 제공한다.
- 다른 스레드 솔루션 또한 Pthread를 기반으로 구현되어 있으므로, 반드시 익혀야 하는 개념이다.
2. Pthread 라이브러리
a) 기본적인 사용법
- <pthread.h> 헤더 파일을 정의한다.
- pthread 라이브러리의 모든 함수는 pthread_ 로 시작한다.
- pthread 라이브러리의 함수는 두가지 핵심기능으로 나뉜다.
▶ 스레드 관리 - 스레스 생성 / 종료 / 조인 / 디태치 함수 등
▶ 동기화 - 뮤텍스 등 동기화 관련 함수
b) Pthread 컴파일하기
- Pthread는 기본 라이브러리(glibc)에 포함되어 있지 않은 독립적인 라이브러리다.
- 그러므로 컴파일 과정에 있어서 다음과 같이 옵션을 붙여주어야한다.
gcc -pthread test.c -o test
3. pthread 라이브러리 주요함수
a) 스레드 생성
- 스레스 생성에 사용되는 함수를 살펴보자.
// thread: 생성된 스레드 식별자
// attr: 스레드 특정 설정(기본 NULL)
// start_routine: 스레드 함수(스레드로 분기해서 실행할 함수)
// arg: 스레드 함수 인자
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg);
// 예시
pthread_t thread1;
void *thread_function(void *ptr);
ret = pthread_create(&thread1, NULL, thread_function, (void*)message1);
- 위의 예시코드를 기반으로 각 매개변수에 대해서 알아보자.
▶ 첫번째 인자(&thread1)
- 스레드를 가리키는 포인터
- 반드시 pthread_t 타입으로 선언이 되어야 한다.
▶ 두번째 인자(NULL)
- 스레드의 특정한 설정옵션
- 일반적으로 NULL을 사용한다.
▶ 세번째 인자(thread_function)
- 해당 스레드가 수행할 함수의 이름
▶ 네번째 인자(message1)
- 세번째 인자로 입력된 실행할 함수가 필요로하는 매개변수(인자)
▶ 반환값
- 0은 스레드를 성공적으로 생성
- 0이 아닌 경우 에러가 발생
b) 스레드 종료
- 스레드 종료에 사용되는 함수를 살펴보자.
// exit과 유사하다
// NULL 또는 0을 매개변수로 입력하여 종료한다.
void pthread_exit(void *retval);
// 예시
pthread_exit(NULL);
c) 스레드 조인
- 리눅스에서 프로세스를 생성하면, 기본적으로 메인 스레드가 생성된다.
- 메인 스레드는 pthread_create()를 통해 추가적으로 생성된 스레드와 구분하기 위해 메인 스레드라고 지칭한다.
▶ 스레드 조인을 사용하는 이유
- 스레드 조인은 스레드의 동기적 처리를 구현하기 위해서 사용한다.
- 메인 스레드에서 pthread_create() 함수가 실행되어 추가 스레드가 생성된다.
- 추가적으로 2개의 스레드를 만든다고 할 때, 잠시 메인 스레드에서 실행할 코드는 멈추고 생성된 스레드의 작업을 진행한다.
- 둘 중 하나의 스레드가 종료되면 다른 스레드가 종료되지 않아도 메인 스레드는 다음 코드를 실행하게 된다.
- 즉, 비동기적 실행으로 인해 메인 스레드가 추가 스레드보다 빨리 종료될 수 있다.
- 이러한 비정상적인 동작을 막기위해서 스레드 조인을 사용해 동기적 처리를 구현한다.
- 스레드 조인에 사용되는 함수를 살펴보자.
// thread: 기다릴 스레드의 식별자
// thread_return: 스레드의 반환값을 가져올 포인터
int pthread_join(pthread_t thread, void **thread_return);
// 예시 - pthread 식별자를 가진 스레드의 종료를 기다리고, status 포인터로 종료값을 가져온다.
pthread_join(p_thread, (void *)&status);
printf("thread join: %d\n", status);
4. 스레드 생성/종료/조인 예제
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void *thread_function(void *ptr) { // 생성된 스레드에서 실행될 함^_
char *message;
message = (char *) ptr; // ptr 매개변수를 message 변수에 넣는다.
printf("%s \n", message); // message를 화면에 출력한다.
pthread_exit((void *)100); // 스레드를 강제종료 후 상태값을 100으로 설정한다.
}
int main() {
pthread_t thread1, thread2; // 스레드 포인터 변수 생성
const char *message1 = "Thread 1";
const char *message2 = "Thread 2";
int ret, status;
ret = pthread_create(&thread1, NULL, thread_function, (void*)message1);
printf("%d\n", ret);
if (ret == 0) { // 스레드1 생성 성공
printf("pthread_create returns %d\n", ret);
} else { // 스레드1 생성 실패
printf("pthread_create returns error: %d\n", ret);
exit(1);
}
ret = pthread_create(&thread2, NULL, thread_function, (void*)message2);
if (ret == 0) { // 스레드2 생성 성공
printf("pthread_create returns %d\n", ret);
} else { // 스레드2 생성 실패
printf("pthread_create returns error: %d\n", ret);
exit(1);
}
pthread_join(thread1, (void **)&status); // 스레드 1이 끝날 때까지 대기 (무엇이 먼저 끝날지 모름)
printf("thread1 returns: %d\n", status);
pthread_join(thread2, (void **)&status); // 스레드 2가 끝날 때까지 대기 (무엇이 먼저 끝날지 모름)
printf("thread2 returns: %d\n", status);
return 0;
}
- 위의 결과화면을 보면, 오히려 스레드 2가 스레드 1보다 먼저 실행된 것을 볼 수 있다.
- 스레드 2가 먼저 실행되었고, 만약 먼저 끝났다 하더라도 스레드 1의 상태값이 먼저 출력된다.
- 그 이유는 마지막 부분에 작성된 스레드 조인으로 인해 스레드 1이 끝날때 까지 기다려야하기 때문이다.
- 몇번 스레드가 먼저 종료되는지 궁금해서 마지막 부분의 스레드 조인 부분의 순서를 바꿔보았다.
- 스레드 2를 스레드 조인하여, 스레드 2가 끝날 때까지 기다리도록 하였다.
- 스레드 2가 먼저 실행되었고, 먼저 종료된다는 것을 확인할 수 있다.
'컴퓨터공학기초 개념 > 시스템 프로그래밍' 카테고리의 다른 글
35. 시스템 프로그래밍 - 메모리 & mmap (3) | 2021.09.27 |
---|---|
34. 스레드 - detach와 mutex (0) | 2021.09.25 |
32. 쉘 스크립트 - 현업 예제(backup, log, tar, find) (0) | 2021.09.24 |
31. 쉘 스크립트 - 조건문과 반복문 (0) | 2021.09.24 |
30. 쉘 스크립트 - 조건문 (0) | 2021.09.24 |
댓글