전공공부/운영체제 (Operating System)

[운영체제] Thread (1) - Definition, Implementation, and Multicore

jooona 2020. 12. 18. 18:12
반응형

지금까지 살펴보았던 process는 모두 process 하나가 하나의 thread를 가지고 있다고 가정하고 공부를 하였습니다. 즉 하나의 process는 한 번에 하나의 작업만 수행한다고 가정을 했었다는 것이죠. 하지만 실제로는 하나의 process는 여러 개의 thread를 가질 수 있으며 이 thread들을 통해 process 하나로도 병렬적인 작업 수행이 가능합니다.

 

1. Thread란?

우선 thread의 정의를 알아보자면 process내에서 실행되는 흐름의 단위를 말합니다. 즉, 여러 개의 thread가 있다면 여러개의 작업의 흐름을 가질 수 있는 것이죠. 이렇게 말하면 너무 추상적인데, 예를 들어 설명하자면, 지금까지 배운 process의 개념에서는 하나의 thread만 가지고 있다고 가정했기 때문에, 메세지를 주고받을 때 메세지를 보내는 작업을 수행한 뒤에 메세지를 받는 작업을 수행할 수 있었다면, 두 개의 thread를 이용하면 한 process 내에서 메세지를 보내는 작업과 메세지를 받기 위해 대기하는 작업을 동시에 수행할 수 있다는 것입니다. 

 

Thread는 Lightweight process라고도 불리며, 어떤 작업을 실행할 수 있는 가장 작은 단위입니다.  Thread는 thread id, pc, register set, 그리고 stack을 독립적으로 가집니다. 하지만 code section과 data section 등은 같은 process 내의 다른 thread들과 공유합니다. 아래의 그림을 보면 더 쉽게 이해하실 수 있을 것 같습니다.

 

Single Thread Process / Multi Thread Process

2. Thread를 사용하는 이유

그렇다면 thread를 사용하면 어떤 장점이 있을까요? 당연히 한 process내에서 병렬적인 작업을 수행할 수 있기 때문에 성능적으로 이득을 볼 수 있습니다. 한 공장 안에서 한 명이 일하는 것과 두 명, 세 명이 같이 일하는 것은 실행 속도에서 엄청난 차이가 나겠죠? 공장을 process, 직원을 thread라고 생각하면 이해하기 편할 것 같습니다. 또한, thread를 새로 만드는 작업은 process를 새로 만드는 작업보다 훨씬 간단하기 때문에 생성에서 발생하는 overhead도 줄일수 있죠. 바로 위의 그림에서도 알 수 있듯이 process를 만들려면 code, data, file, registers, stack 모두를 새로 만들어야 하는 반면, thread를 만들 때는 registers와 stack만 만들어주면 되기 때문입니다. 

 

그리고 바로 지난 번에 interprocess communication을 공부했었는데요, 한 process 내의 thread들끼리는 소통을 위해 이런 번거로운 과정을 거칠 필요도 없습니다. 바로 메모리를 공유하고 있기 때문입니다. 그래서 thread들끼리 메세지를 주고받을 때 kernel의 도움을 필요로 하지 않습니다. 이것 역시 성능적으로 큰 도움이 됩니다.

 

Thread의 장점은 다음 4가지로 요약할 수 있습니다

 

1. 응답성 (Responsiveness): 프로그램 실행 시 어떤 작업이 오랜 시간을 필요로 하거나 또는 어떤 이유에서 block되더라도 다른 thread가 사용자에게 응답을 줄 수 있습니다. Thread를 하나만 사용할 때는 아무리 오래 걸리는 작업이라도 이 작업이 종료되어야 다음 작업을 통해 사용자에게 응답할 수 있겠죠.

2. 자원 공유 (Resource Sharing): Process 내부의 thread들 끼리는 process의 여러 resource들과 메모리를 공유합니다. Code와 data의 공유를 통해 한 프로그램이 같은 주소 공간 내에서 여러 개의 다른 작업을 하는 thread를 가질 수 있습니다.

3. 경제성 (Economy): 한 process 내의 thread들 간의 context switch나 thread 생성에서 process에 비해 훨씬 적은 overhead를 가집니다. Thread를 생성하는 것은 process를 생성하는 것에 비해 30배가량 빠르며, context switch는 5배가량 빠르다고 합니다.

4. 규모 적응성 (Scalability): Multiprocessor 구조에서 thread의 이점은 더욱 증가합니다. Multiprocessor 구조에서는 각각의 thread들이 다른 processor에서 병렬적으로 수행될 수 있기 때문입니다.

 

3. Thread의 구현

앞에서 process는 각각의 PCB를 가지고 있어서 자신의 정보를 저장해 둔다는 내용을 공부한 적이 있었습니다. 이와 유사하게 thread 역시 TCB (Thread Control Block)을 가집니다. 기본적인 아이디어는 원래의 PCB를 PCB와 TCB 두 파트로 나눈다는 것입니다.

 

원래 PCB가 가지고 있던 정보 중 thread들이 각각 가지는 정보들, 즉 thread ID나 thread의 상태, PC값, register 등은 TCB에 저장하고, 나머지 정보들과 자신에게 소유되는 thread들의 포인터 값만 기존의 PCB에 남기는 것입니다. 이렇게 만들어진 TCB는 PCB에 비해 작고 resource도 적게 사용하기 때문에 더 유용하게 사용될 수 있습니다. Linux의 경우에는 TCB가 PCB에 비해 5배 정도 작은 크기를 가집니다.

 

앞에서 context switch는 PCB를 활용해서 수행한다고 공부를 했지만, 이렇게 TCB를 활용하는 것이 PCB를 활용하는 것보다 훨씬 쉽기 때문에 실제로는 context switch를 수행할 때 PCB가 아닌 TCB로 수행하게 됩니다. Context switch가 수행될 때 바뀌는 TCB들이 만약 같은 process 내의 TCB들이라면 주소 공간을 아예 바꿔줄 필요가 없기 때문에 훨씬 쉽게 context switch가 가능하게 됩니다. 물론 다른 process에게 속한 thread 간에 context switch가 일어나는 경우라면 기존에 공부했던 context switch처럼 모든 정보를 교체해 주어야 합니다. 

 

4. Multicore Programming

Multicore 라는 것은 CPU 칩에 여러 개의 core가 존재하는 것을 뜻합니다. 비슷한 용어로 multiprocessor가 있는데 이는 시스템 안에 여러 개의 CPU가 존재하는 것을 뜻해서 서로 뜻이 조금 다릅니다. 아무튼 이러한 Multicore를 사용하면 프로그램을 더 효율적으로 병렬 수행할 수 있습니다. Core 하나가 하나의 thread의 작업을 수행할 수 있기 때문이죠. 이러한 multicore에 관한 내용에서는 Concurrency(동시성, 병행 실행)Parallelism(병렬성, 병렬 시행)의 차이를 잘 숙지하고 있어야 합니다.

 

Concurrency란 여러 개의 작업을 쪼개어 순차적으로 진행하여 마치 함께 실행되고 있는 것 같은 효과를 나타내는 것을 뜻합니다. 아래의 그림과 같이 하나의 core에서 여러 개의 작업을 쪼개어 차례로 실행하여 사용자의 입장에서는 세 개의 작업이 함께 실행되고 있는 것처럼 느끼게 됩니다.

Concurrency

Parallelism은 말 그대로 병렬적으로 실행된다는 뜻입니다. 두 개 이상의 core를 사용하여 실제로 동시에 다수의 작업을 처리할 수 있습니다.

Parallelism

이러한 multicore를 사용하기 위해서는 원래의 프로그램을 여러 thread를 사용할 수 있도록 수정을 해줘야하고, 이러한 multi-core 시스템에 부합하는 스케쥴링 알고리즘도 작성해야 합니다. Multicore programming에는 일반적으로 다섯 가지의 도전 과제가 존재합니다.  

 

1. Identifying tasks (테스크 인식): 어떤 부분을 독립적으로, 그리고 병렬적으로 실행할지를 결정해야 한다.

2. Balance (균형): 모든 작업이 균등하게 실행되도록 잘 배정되었는지를 확인해야한다. 

3. Data Splitting (데이터 분리): 작업 뿐 아니라 데이터도 균형 있게 잘 배분되었는지를 확인해야 한다.

4. Data Dependancy (데이터 종속성): 한 작업을 마쳐야 시작할 수 있는 작업이 있는지(종속적인지)를 확인해야 한다.

5. Testinig and debugging (시험 및 디버깅): 병렬적으로 실행될 경우 다양한 실행 경로가 존재할 수 있기 때문에, 테스트와 디버깅이 굉장히 어려울 수 있다.

 

Parallelism에는 두 가지 타입이 존재합니다. 바로 data parallelism과 task parallelism입니다. 아마 그림으로 먼저 확인하는 것이 이해하기에 더 편할 것 같습니다. 

Data Parallelism
Task Parallelism

Data Parallelism은 데이터를 분할하여 같은 함수에 서로 다른 데이터들을 넣음으로써 병렬 수행을 가능하게 합니다. 예를 들면 100개의 정수가 들어있는 배열에서 0의 개수를 카운트하는 프로그램이라면 1번 core에서는 0부터 33까지의 배열에서 0의 개수를 확인하고, 2번 core에서는 34번부터 66번까지를 확인하고 3번 core에서 나머지를 확인하는 느낌입니다.

 

Task Parallelism은 데이터는 모두 같은 데이터를 사용하지만 core마다 서로 다른 함수를 실행시켜 병렬 수행을 가능하게 하는 방법입니다. 예를 들면 1번 core는 모든 데이터의 합을 구하고 2번 core는 모든 데이터의 곱을 구하고 3번 core는 모든 데이터의 평균을 구하는 형태가 있겠습니다.

 

 

 

 

 

오늘은 thread에 관해서 알아보았습니다. 아마 시스템 프로그래밍이나 자바 프로그래밍과 같은 다른 수업들에서 배웠던 개념이라 그렇게 어렵지 않게 이해할 수 있을 것이라 생각합니다. 다음 글에서는 Multithread model부터 공부해 볼 예정입니다. 읽어주셔서 감사합니다.

반응형