Thread?
- A single unique execution context
- Mechanism for concurrency (동시성) + can also run in parallel (병렬성)
- Protection (보호) → 쓰레드 간 자원 접근을 제어하고 안전하게 관리하기 위한 메커니즘
MTAO (multiple things at once)
- 운영 시스템은 프로세스, 인터럽트, 백그라운드 시스템 유지 등 다양한 것들을 한 번에 핸들링할 수 있어야 함
- 네트워크 서버, 병렬 프로그램, UI를 지닌 프로그램, 그리고 network/disk bound 프로그램도 마찬가지
→ 이러한 것들을 가능하게 해주는 것이 바로 Thread
스레드는 동시성의 유닛이고, 각각의 쓰레드는 하나 혹은 한 태스크를 표현할 수 있음!
Multiprocessing vs Multiprogramming
여러 CPU가 일들을 하나씩 맡아서 작업하면, 같은 시각에 실행되는 프로세스가 여러 개
→ Mutiprocessing
한 CPU 내에서 일들이 서로 번갈아가면서 하거나 툭툭 끊어져서 진행, 즉 같은 시각에 실행되는 프로세스는 단 하나
→ Multiprogramming / Multithreading
Concurrency는 Parallelism이랑 같지 않다!
Concurrency는 여러 일을 한 번에 제어할 수 있는 것
Parallelism은 여러 일들이 동시에 실행하는 것
예를 들어, 싱글 코어 (CPU가 한 개인 경우)에서 두 스레드가 실행된다면, 이는 동시적인 것이지 병렬적인 것이 아님.
Thread의 3가지 상태
- Running : 현재 실행되고 있음
- Ready : 실행되기 위해 기다리고 있음
- Blocked : 입출력 작업을 수행, 동기화 등 cpu를 필요로 하지 않는 상황에서 자원을 낭비하지 않음
Blocked? 그냥 Running과 Ready만 있어도 되는 게 아닌지?
→ 예를 들어 입출력 완료를 첫 번째 스레드가 기다리고 있다고 해보자. 이 첫번째 쓰레드를 ready 상태로 바꾼다면 cpu 자원을 낭비하게 될텐데, 사실 입출력이 완료되길 기다리는 동안에는 굳이 cpu를 점유할 필요가 없다. 따라서 완료가 될 때가지 blocked 상태로 있는다면 다른 쓰레드가 먼저 cpu를 쓰면서 효율적인 이용이 가능할 것이다.
pthreads
병렬적으로 작동하는 소프트웨어의 작성을 위해서 제공되는 표준 API인 pthreads를 통해 스레드의 동작을 파악할 수 있다. pthreads 라이브러리 내장 함수를 알아보도록 하자.
1) 생성
include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
스레드를 생성하는 함수이다.
첫 번째 인자는 스레드를 가리키는 포인터,
두 번째 인자는 스레드의 특정 옵션을 설정(대부분의 경우 NULL 입력),
세 번째 인자는 스레드가 수행할 함수,
네 번째 인자는 세 번째 인자에 들어가는 함수가 필요로 하는 내부 인자이다.
2) 종료
void pthread_exit(void *value_ptr);
스레드를 종료할 때 쓰는 함수이다. 보통 인자에는 NULL이나 0이 들어간다.
3) 조인
int pthread_join(pthread_t thread, void **value_ptr);
타깃 스레드 (첫 번째 인자)가 종료될 때까지 기다리는 함수라고 생각하면 쉽다. 만약 메인 스레드에서 파생된 스레드들보다 메인 스레드가 먼저 종료된다면, 원하는 작업을 끝내지 못한 채 종료될 수 있기 때문이다. 두 번째 인자는 조인 함수의 반환값을 담을 포인터인데, 이 값을 통해 메인 스레드에서 다른 스레드의 상태를 확인할 수 있다. 또 이렇게 전체 업무를 여러 개의 작은 업무로 나누어 처리하고 합치는 과정을 Fork-Join Pattern이라고 한다.
pthread 실행 예제
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
int common = 162;
void *threadfun(void *threadid) {
long tid = (long)threadid;
printf("Thread #%lx stack: %lx common: %lx (%d)\n", tid, (unsigned long) &tid, (unsigned long) &common, common++);
pthread_exit(NULL);
}
int main(int argc, char *argv[]) {
long t;
int nthreads = 2;
// 인자를 입력하면 그만큼 쓰레드를 생성함. Default: 2
if (argc > 1) {
nthreads = atoi(argv[1]); //argument to integer
}
pthread_t *threads = malloc(nthreads*sizeof(pthread_t)); // 쓰레드의 개수만큼 위치 동적 할당
printf("Main stack: %lx, common: %lx (%d)\n", (unsigned long) &t, (unsigned long) &common, common);
for (t = 0; t < nthreads; t++) {
int rc = pthread_create(&threads[t], NULL, threadfun, (void *)t);
if (rc) { // 보통 쓰레드 생성에 에러가 발생한다 -> return value != 0
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
for (t = 0; t<nthreads; t++) {
pthread_join(threads[t], NULL);
printf("%ld\n", t);
}
pthread_exit(NULL);
return 0;
}
스레드의 전체 흐름을 알아볼 수 있는 예제 코드이다. 실행 파일을 만들어 생성할 쓰레드의 개수를 입력해 주면 어떤 순서로 스레드가 실행되고 종료되는지를 확인해 볼 수 있다.
Review Point
만약 위 코드를 통해 스레드를 4개 생성한다고 했을 때, 아래 질문에 대답해 보자.
1. 이 프로그램에서 총 몇 개의 스레드가 사용되었는가?
- 5개가 사용되었다. 한 개의 메인 스레드와 4개의 생성된 스레드.
2. 메인 스레드는 다른 스레드들의 생성 순서와 동일한 순서로 조인하는가?
- 위 코드에서는 for문을 통해 순차적으로 조인하고 있다. 그러하다.
3. 생성된 스레드들은 그들이 만들어진 순서대로 종료되는가?
- 아니다. 위 코드 상에서는 먼저 처리되는 스레드는 바로 종료된다.
4. 프로그램이 다시 실행된다면 결과가 달라지는가?
- 그러하다. 운영체제의 스레드 스케줄러의 스케줄링에 의해 결정된다.
'etc' 카테고리의 다른 글
[GitHub] master branch를 main branch로 변경하기 (0) | 2024.11.22 |
---|---|
[GitHub] 깃허브 기여하기 - Fork, Pull Request(PR) (0) | 2024.10.15 |
[GitHub] 깃허브 시작하기 - Repository 생성 (4) | 2024.09.19 |
[GitHub] MacOS 업데이트 후 Git 에러가 발생할 때 (xcrun: error) (0) | 2022.03.13 |