스레드란?
앞서 프로세스는 싱글 스레드로 도는 프로그램이라고 하였다. 하지만 만약 프로세스 안에서 또 작업을 여러 개로 나누어 작동시킨다면 어떨까? 훨씬 작업이 빨라지지 않겠는가? 이러한 발상에서 착안된 것이 바로 스레드이다. 스레드는 가벼운 프로세스로서 실제적으로로 CPU를 점유하는 단위이다. 이제는 여러 개의 스레드를 두어 프로세스를 실행시키기 때문에 실제로 CPU를 점유하는 가장 작은 단위는 스레드라고 할 수 있다. 이 또한 똑같이 고유의 TCB를 가지고 있다.
당연히 여기에는 여러 장점이 있다. 우선 프로세스가 다 끝날 때까지 기다리지 않고 비동기적으로 처리할 수 있고, 위의 그림처럼 기본적인 자원을 공유하기 때문에 훨씬 경제적이다. 또한 여러 개의 작업으로 병렬처리가 가능해진다. 사실 우리는 멀티 스레드를 배우기 위해 멀티 프로세싱을 배운 것이나 다름없다.
※ 아직 프로세스와 스레드가 제대로 이해가 안되는 느낌이라면 https://www.youtube.com/watch?v=1grtWKqTn50를 참고하자!
멀티코어 프로그래밍
멀티 스레딩은 멀티 코어에서 더 효율성이 좋아진다. 한 스레드당 한 코어씩 잡고 일을 하면 아무래도 병렬적으로 작업 할 수가 있어 효율이 좋아질 수 밖에 없다. 하지만 마냥 이상적이지만은 않다. 멀티 코어에서 멀티 스레드를 실행시키려면 다양한 문제가 생기게 된다. 가장 큰 문제는 어떤 태스크를 병렬 수행할 수 있을 지 식별하기 어렵다는 것이다. 덧셈은 병렬로 실행해도 결과가 달라지지만 나눗셈과 같은 연산은 결과가 바뀌게 된다. 이 밖에도 태스크를 균형있게 분배하거나 데이터 의존성을 고려하여 동기화를 신경써야 하는 문제, 테스트나 디버깅이 어렵다는 문제 등 해결해야 할 과제가 많다.
이러한 문제를 함께 고려한다면 코어가 무조건 많을수록 좋을까? 암달은 이러한 가설을 하나의 식으로 정의했다. 암달의 법칙은 코어의 개수가 많아져도 직렬처리해야 하는 작업의 수가 많아진다면 속도가 선형적으로 상승하지 못한다는 법칙이다.
$$speedup \leq \frac{1}{S+\frac{(1-S)}{N}}$$
여기서 S는 무조건 직렬로 처리해야하는 작업의 비율, N은 코어의 개수이다. S가 커지면 커질 수록 즉, 병렬로 실행하지 못하는 작업이 많아질수록 코어의 개수가 많아진다고 성능이 선형적으로 좋아지는 것이 아니다. 만약 2개의 코어를 가졌을 때 25%의 작업이 무조건 직렬로 실행되어야 한다면 speedup은 1.6으로 2배가 되어야 하지만 그보다 작은 향상률이 있다는 것을 살펴볼 수 있다.
멀티스레딩 모델
스레드에는 두 가지 타입이 존재한다. 운영체제의 스레드와 무관한 유저 스레드와 운영체제가 직접 제어하는 커널 스레드가 있다. 유저 스레드는 기본적으로 커널 위에서 동작하고 유저 모드 영역에서 작동한다.
유저 스레드와 커널 스레드 사이에는 여러 관계가 있을 수 있는데 DB 모델링과 비슷하다. 많은 유저 스레드가 하나의 커널 스레드와 관계를 갖는 다대일 관계, 하나의 유저 스레드가 하나의 커널 스레드와 관계를 갖는 일대일 관계, 여러 유저 스레드가 또 여러 스레드와 관계를 갖는 다대다 관계가 있다.
암묵적 스레딩
스레드를 만드는 것은 생각보다 골치아픈 일이다. 만든다고 하여도 이를 관리하는 것은 쉽지 않기 때문에 프로그래머가 직접 생성 및 관리하는 것이 아니라 컴파일러와 라이브러리에게 권한을 넘겨서 생성과 관리는 시스템에게 맡기고 어플리케이션에서는 작업에 맞는 스레드를 라이브러리를 통해 맵핑만 해주면 된다. 자바에서는 java.concurrent * 라이브러리를 이용할 수 있다. (API를 통해 자원에 직접적으로 접근하지 못하게 하는 시스템 콜과 비슷하다는 느낌을 받았다.)
암묵적 스레딩을 하는 메인 방법에는 여러 방법이 있지만 2가지 정도만 알아보도록 하자. 먼저 Thread Pool 방법은 프로그래머가 무분별한 new Thread를 하여 자원을 낭비하지 않게 일정 수의 스레드를 생성하여 Thread Pool에서 사용하지 않는 스레드를 가져오고 만약 모든 스레드가 사용중이라면 사용 가능한 스레드가 생길 때까지 대기하는 방법이다.
다른 방법으로는 OpenMP가 있다. 간단하게 설명하면 이 부분이 병렬적으로 작동해야 하는 부분이라고 컴파일러에게 알려주는 것이다. 아래 코드를 보면 4개의 스레드를 사용하여 해당 코드를 병렬 처리하라고 지정하고 있다.
#include <stdio.h>
#include <omp.h>
int main int argc char argv
{
omp_set_num_threads(4);
#pragma omp parallel
{
printf("OpenMP thread: %d\n", omp_get_thread_num());
}
return 0;
}
'CS 지식 > 운영체제' 카테고리의 다른 글
운영체제 Ch6 - 프로세서 동기화(1) (0) | 2021.12.12 |
---|---|
운영체제 Ch5 - CPU 스케줄링 (0) | 2021.12.08 |
운영체제 Ch3 - 프로세스(2) (0) | 2021.12.08 |
운영체제 Ch3 - 프로세스(1) (0) | 2021.12.02 |
운영체제 Ch1, 2 - Introduction & OS Structures (0) | 2021.12.01 |