[운영체제] 스레드(thread)
Threads
thread : 실 이라는 뜻이다. 하나의 제어 흐름. control flow
웹 서버 - 클라이언트같은 경우 여러 개의 제어 흐름을 가져야 하는데, 이 경우 프로세스보다 스레드를 이용하는 것이 유리하다.
스레드는 글로벌 변수, 코드, 파일 디스크립터 등을 공유하기 때문에 만드는 데 걸리는 비용이 적고 컨텍스트 스위칭의 오버헤드도 적게 든다. 스레드는 힙 영역은 공유하고 레지스터와 스택 영역은 따로 가진다.
유저 레벨 스레드 : 라이브러리 형태로 구현되어 있다.
POSIX 의 pthread 가 대표적이다. 윈도우랑 자바에서 사용한다. 유저 레벨 스레드라고 해서 커널 스레드를 갖지 않는 것이 아니라 커널 스레드와 매핑 된다.
커널 스레드 : 대부분 운영체제에서는 스레드를 지원하고 잇다. 유저 레벨에서 pthread 를 생성하면 커널 스레드를 이용하는 것이다.
결국 유저 스레드 - 커널 스레드가 매핑 되는 것인데 다음 과 같이 나눌 수 있다.
- many to one
- one to one
- many to many
1. many to one
커널 스레드 하나에 여러 개의 유저 레벨 스레드가 매핑 되는 형태이다.
커널 입장에서는 하나의 프로세스밖에 보이지 않고 내부적으로 여러 개의 스레드를 사용하는 구조이다. 하지만 커널 입장에서는 하나라고 인식하기 때문에 여러 개의 CPU 코어를 이용하지 못 하고(아마도) 하나의 스레드에서는 CPU 를 이용하고 다른 스레드에서 I/O 요청을 한다고 했을 때, 커널 입장에서는 해당 프로세스를 waiting queue 로 넣어버리기 때문에 모든 스레드가 멈춰버리는 문제가 발생한다. 그래서 현대 CPU 에서는 이런 모델을 사용하지 않는다.
2. one to one
가장 간단한 모델이다. 하나의 유저 레벨 스레드가 생기면 커널 스레드를 하나 만들어 매핑해 준다. 리눅스와 윈도우에서 사용한다. 커널 스레드 여러 개를 묶어서 하나의 프로세스로 만들면 되지만, 완전한 light weight process 는 아니다. 그래도 프로세스보다 오버헤드가 적게 들고 메모리 영역을 공유하는 스레드를 만들 수 있다.
pthread 하나를 만들면 커널 스레드 하나를 생성하여 매핑하도록 구현되어 있다.
3. many to many
유저레벨 스레드가 커널 스레드보다 같거나 많다.
유저레벨 스레드를 이용하겠다고 하면 가용한 커널스레드를 매핑해 준다.
4. two level
3번과 동일하다. 특정 유저레벨 스레드에 커널 스레드를 명시적으로 매핑이 가능하다. 2번과 3번을 혼합한 형태이다.
POSIX 의 pthread
API 의 명세일 뿐 구현과는 관계가 없다. pthread 의 구현은 운영체제에 따라 다르다. 윈도우에서는 pthread 하나에 커널 스레드 하나를 매핑해 주는데, API 명세만 맞으면 다르게 구현할 수도 있다.
init, create, exit, join 등이 있는데, init 은 라이브러리 초기화, create 는 fork 와 비슷하지만 메모리 영역은 카피X, exit 는 스레드 종료이다. join 은 wait 와 비슷하며, 다른 스레드가 종료될 때까지 기다린다.
스레드 프로그래밍을 할 때 join 을 이용하여 스레드를 만드는 스레드는 다른 스레드가 종료될 때까지 기다리는 형태로 구현한다.
리눅스의 경우 프로세스와 스레드의 명확한 구분이 없다. 리눅스는 task 라고 부르는 것이 유의하자.
프로세스를 만드는 것은 fork(), 스레드를 만드는 것은 create() 를 호출한다. (사실 task 이다.) 이 task 를 만들 때 fork() 를 호출하면 메모리 영역까지 복사하고, create() 를 호출하면 메모리 영역은 복사하지 않는다. 코드 영역은 복사하는 것 같다. 그래서 스레드 개념과는 조금 다른 것 같다.
스레드 관련해서 이상한 생각을 해보자.
100개의 스레드가 존재하는 프로세스에서 fork() 를 호출하면 스레드는 200개가 될 것인가?
댓츠 논노 그렇지 않다. fork() 를 호출한 스레드만 복사하기 때문에 스레드는 101개가 된다.
멀티 스레드 프로세스에서 exec() 을 호출하면 어떻게 되는가?
exec() 은 코드 영역을 새로운 프로그램으로 덮어버리기 때문에, 코드 영역을 공유하는 멀티스레드의 경우 하나의 스레드만 남기고 모든 스레드가 사라진다.
advanced threading
진보된 스레드... 가 아니라 스레드 풀(pool)을 미리 만들어 두고 유저 스레드를 하나 만들면 남는 스레드와 매핑해 준다. 처음에 미리 만들어 두기 때문에 새 스레드를 만드는 데 걸리는 시간이 적게 들고, 최대 스레드의 개수가 정해져 있기 때문에 응답시간을 보장해야하는 경우 사용하기 좋다. 임베디드에서 주로 사용한다.
이 데이터는 나만 쓸 거야!!
스레드의 경우 글로벌 데이터 영역을 공유한다. 하나의 스레드에서만 사용할 수 있는 데이터가 없다. 만약 함수의 변수를 이용하면 이것은 지역변수이기 때문에 함수가 실행될 때마다 초기화되는 문제점이 있다. 한 스레드에서만 사용하고 싶은 전역 변수를 어떻게 사용할까? 메모리 영역에 특별한 공간을 만들어서 사용한다. 이것을 TLS 라고 부르고 static 변수와 비슷하다.
병렬 프로그랭은 어려워요!!
우리학교는 병렬 컴퓨팅이라는 과목이 개설되었지만, 운영체제라는 과목에서 멀티 스레드만 떼어내서 한 과목을 만들 수 있을 정도로 멀티 스레드는 어렵다. 그러면 유능한 프로그래머가 병렬 컴퓨팅을 자동화 시키면 되지 않겠느냐? 근데 그것도 어렵다고 한다. 그래서 프로그래머가 어느 정도 멀티스레드 프로그래밍을 이용하면서 자동화도 어느 정도 지원하는 라이브러리가 OpenMP 이다. gcc 표준이므로 #include <omp.h> 로 추가하면 된다.
#pragma omp parallel for
for(i = 0; i < N; ++i)
...
#pragma를 이용하면 자동으로 멀티스레드를 이용하여 for 안의 내용을 실행해 준다. 단, N개 만큼 스레드가 생기는 것은 아니다.
스레드의 장단점
스레드의 장점이 프로세스의 단점이 되고, 프로세스의 장점이 스레드의 장점이 된다.
스레드는 메모리를 적게 사용하고 오버헤드가 적으며, 멀티 코어 CPU를 100% 이용할 수 있다.
멀티 프로세스는 process protection 이 된다. 하나의 프로세스에서 문제가 생겨도 다른 프로세스에 거의 영향을 미치지 않는다.
크롬 vs 인터넷 익스플로러
크롬은 멀티 프로세스를 사용하고 인터넷 익스플로러는 멀티 스레드를 이용한다. 크롬은 메모리를 많이 차지하고 한 탭에서 문제가 생기면 해당 탭만 죽게 되지만, 인터넷 익스플로러의 경우 하나의 탭에서 문제가 생기면 전체 탭이 죽게 된다.
- 인천대학교 컴퓨터공학부 3학년 전공필수 과목인 박문주 교수님의 운영체제 강의를 듣고 작성한 글입니다.