오늘은 thread에 대해 이야기 해볼까 합니다. Process에 대해 복습하고 넘어갈 필요가 있는데요. 프로세스 이야기를 하면서 더 이상 프로그램이라고 표현하지 않겠다고 이야기드렸습니다.(하나의 프로그램에서 많은 프로세스가 생길 수 있기 때문입니다.) 프로세스는 execution unit(스케쥴링 단위)와 protection domain(자원 보호)를 위한 abstraction입니다. 프로그램과 프로세스의 관계는 executing program with a single thread of control 지금까지의 프로세스 개념이고 하나의 실행 흐름을 가지고 실행중인 프로그램이였습니다. Control flow와 single thread라고 표현하는데요. 이때 1개의 실행 flow를 여러 개로 만들면 어떨까 생각하게 되었습니다.
26.01. Thread
Thread의 성질부터 살펴보겠습니다.
Execution unit, 프로세스 내의 실행 흐름, 프로세스보다 작은 단위, Protection domain 미지원
왜 스레드가 필요할까요?
실행흐름을 여러 개로 만들면 무엇이 더 좋아지는지 생각해보시면 됩니다. 하나의 프로세스는 하나의 control만 존재하므로 한번에 하나의 일만 처리할 수 있었지만 프로세스에서 할 작업을 여러 개로 나눈 후 스레드화한다면 병렬적으로 작업을 처리할 수 있게 됩니다. Cooperative process와의 차이점이라면 협력 프로세스는 IPC가 필요해 cost가 더 듭니다. 또한 프로세스 간 context switching cost도 있습니다. 프로세스 내 협력하는 스레드로 만든다면 프로세스보다는 적은 비용으로 협력 프로세스와 유사한 역할을 할 수 있습니다.(Protection domain을 미지원하여 프로세스 A의 thread 1, thread 2가 있으면 서로 data를 읽고 쓰는 것이 가능합니다.)
컴퓨터구조 교과목 관점에서 본다면 multicore로 가면서 프로세스만 있어서는 효율을 최대로 끌어올리지 못했다는 점도 스레드가 필요한 이유였습니다. 하나의 프로세스는 하나의 control이어서 하나의 core에서만 돌아갈 수 있기 때문입니다.
한번 생각해볼까요. 만약 4 core 1 GHz와 1 core 8 GHz가 있을 때 중요한 application 중 하나인 game을 실행하기에 무엇이 더 좋을까요?
만약 게임 프로그램이 multicore를 사용할 수 있게 프로그램되어 있지 않다면(즉, 하나의 control만 가진다면) 여러 개의 core를 가진 것이 성능 개선에 도움이 되지 않습니다. 이럴 때는 single thread로 CPU clock이 빠른 1 core가 더 좋습니다. 하지만 많은 포스팅에서도 언급했듯이 CPU clock을 향상시키는 것에는 한계가 있습니다.
이제 여러 개의 control(스레드화)을 통해 여러 개의 core(하드웨어)에서 하나의 프로세스를 실행할 수 있게 되었습니다.
26.01.1. Thread & CPU utilization
위 graph에서 Number of CPU를 core라고 생각하시면 됩니다. 스레드의 수가 증가할 수록 CPU utilization이 증가하다가 임계치를 넘어서면 다시 감소하는데요. 이는 thread switching cost가 증가하기 때문입니다. 또한 core 수가 많을 수록 thread를 이용하는 것이 유리하다는 것을 위 graph를 통해 알 수 있습니다. 하나의 프로세스의 여러 스레드를 parallel하게 수행할 수 있기 때문입니다.
뒤에서 배울 내용인 User thread와 Kernel thread 중 이 graph의 thread가 무엇인지 말씀드리자면 커널 스레드입니다. Many to Many model이라면 커널이 스레드 수준을 조절하기 때문에 유저 스레드는 많아져도 성능 증가가 core수만큼 증가하지 않습니다. 따라서 위 graph에서 스레드는 커널 스레드입니다. 어느 point에서 throughput이 감소하는 것도 사용할 수 있는 커널 자원에 한계가 있어 스레드를 무한히 생성하는 것이 도움이 되지 않는다는 분석이 가능합니다. 이 설명은 유저 스레드와 커널 스레드, mapping 설명을 보고 오시면 이해가 되실겁니다.
26.01.2. Process & Thread
프로세스와 스레드를 찬찬히 비교해보는 시간을 가지도록 하겠습니다. 스레드를 왜 사용하는지에 대한 설명은 앞서 설명을 충분히 드린 것 같고 스레드를 어떻게 만들고 어떠한 요소들이 필요한지에 대해 설명하기전에 프로세스와 스레드 비교를 먼저 하고 넘어가겠습니다.(비교를 통해 어떠한 요소들이 추가되어야 할지 생각할 수 있습니다.)
Process | Thread | |
실행 흐름 | 하나의 스레드(실행 흐름) | 하나의 프로세스 안에 여러 개의 스레드 |
Protection domain | 프로세스 간 메모리는 독립적. 접근 불가 | 프로세스의 code와 data section은 thread간 공유 |
Switching cost | 스레드들은 같은 메모리 영역을 공유해 switching cost가 프로세스보다 가볍습니다. | |
Called lightweight process(LWP) |
Memory management에서 배우겠지만 스레드로 동작하면 code와 data section을 공유하기 때문에 같은 page table을 사용할 수도 있습니다.
26.01.3. Thread의 구성 요소
스레드도 프로세스의 PCB와 같이 실행을 control하기 위한 data structure가 필요합니다.
Thread ID, Program counter, Register set, Stack per thread
Stack은 execution할 수 있는 temporary한 메모리를 제공하므로 스레드 별로 필요합니다. 반면 동일한 프로세스 내에서 스레드가 공유할 수 있는 data도 있습니다.
Code section, Data section, File
위와 같이 공유하는 data가 있어서 스레드 전환 비용이 프로세스의 문맥 전환 비용보다 작습니다.(구체적으로 아래 그림을 보면 switching할 때 registers와 stack만 신경을 써주면 된다는 것을 알 수 있습니다.)
26.02. Multithread
26.02.1. Multithread program
Multithread program은 프로그램 내의 스레드 중 몇 개가 block 되거나 긴 시간이 소요되는 작업을 하더라도, 다른 스레드들은 실행되고 있어 user 입장에서 프로그램이 interactive합니다.(Responsiveness) 스레드간에는 프로세스의 메모리와 다른 자원들을 공유한다는 것도 장점인데요. 스레드들은 단일 프로세스 메모리 영역에서 실행되기 때문에 새로운 프로세스를 생성하는 것 : fork보다 스레드를 생성하는 것이 cost가 적게 들어갑니다.(Resource sharing, Economy) 여러 개의 스레드가 여러 프로세서에서 동시에 실행 가능한 parallelism을 지원해주기도 합니다.(Scalability)
Resource sharing 관점에서 덧붙이자면 socket을 가지고 file을 read하면 동기화가 필요해 보통 read가 끝날 때까지 sleep을 합니다. File을 기다리면서 socket도 읽고 싶다면 single thread에서 구현할 때 여러 기법이 필요하다는 거죠. Multithread에서는 하나의 스레드는 file을 읽고 다른 스레드가 network를 읽으면 됩니다. 두개 이상의 스레드가 독립적으로 동작하되 code와 data가 공유되고 있다는 것을 알고 있으면 됩니다. Code는 only read라 괜찮을테고 write 영역인 data는 consistency를 유지해주어야 합니다. 이는 동기화 포스팅에서 다루도록 하겠습니다.
26.02.2. Multicore programming
최근 프로세스 설계 동향은 multicore processor라고 말씀드린 적 있는데요. Multicore processor는 multithread에 아주 적합한 하드웨어입니다. Multithread programming이 multicore system에서 아주 효율적이라는 거죠. Multicore processor는 OS에서 각각의 core를 하나의 프로세서로 인식하고 스케쥴링합니다. 따라서 core에 스레드를 각각 할당하여 실행이 가능합니다. 더군다나 multicore는 cahce를 공유하기 때문에 data와 code 등 프로세스 자원을 공유하는 multithreaded programming에 보다 효과적입니다.
컴퓨터 구조 과목에서 배웠던 내용을 잠깐 되짚고 가겠습니다. Multiprocessor가 되면 cache가 달려서 cache coherence protocol을 동작시켜야 했습니다. 여러 개의 프로세스들이 동시에 같은 메모리를 쓰게 되면 그 값을 invalidation 시켜줘야 하는 문제가 있었습니다. Multicore는 이런 문제가 없습니다. 최근 GPU를 보면 core수가 몇 천개가 넘어서는 것도 있는데요.(그만큼 스레드를 사용하는 것이 굉장히 유익하다는 거겠죠.)
26.03. User & Kernel threads
스레드를 지원하는 주체에 따라 유저와 커널 스레드로 나뉩니다. User thread는 커널 영역 위에서 지원되어 일반적으로 user level의 라이브러리를 통해 구현됩니다. 라이브러리에서 스레드를 생성하고 스케쥴링 관리를 해줍니다. 동일한 메모리 영역에서 스레드가 생성되고 관리된다는 점에서 가볍기때문에 속도가 빠릅니다. 다만 여러 개의 유저 스레드 중 하나의 스레드가 시스템 콜에 의해 block 된다면 나머지 스레드 역시 block 됩니다. 이는 커널이 여러 개의 유저 스레드를 하나의 프로세스로 간주하기 때문입니다.(즉, 커널입장에서 유저 레벨의 스레드를 볼 수 없다는 것입니다. 왜냐하면 스레드에 할당되는 자원이 커널에서는 할당되어 있지 않고 PCB를 보기 때문입니다.) Reference에 이해하기 쉬운 그림이 있어 가져와 보았습니다.
Kernel thread는 OS에서 스레드를 지원합니다. 커널이 스레드의 생성과 스케쥴링 관리를 해줍니다. 스레드가 시스템 콜을 호출하여 block이 되면 커널은 다른 스레드를 실행하여 전체적인 스레드 blocking이 없고 multiprocessor 환경에서 커널이 여러 개의 스레드를 각각 할당할 수 있다는 장점이 있습니다. 다만 유저 스레드보다 생성과 관리가 느립니다. 그럼 몇가지 질문에 답해보겠습니다.
커널 스레드가 왜 필요할까요?
장점을 되짚어보시면 됩니다. Multicore 환경에서 여러 개의 스레드를 core에 할당하기 위해서는 커널 자체가 multithreading이 되어야 합니다. Linux의 초기 BSD version을 보면 munltithread를 지원하지 않았는데요. 실제로 물리적인 하드웨어를 사용하려면 커널 스레드가 필요했습니다.(커널 스레드를 통해 실제 하드웨어 core에 스케쥴링 가능합니다.)
유저 스레드보다 생성과 관리가 왜 느릴까요?
커널 내의 스레드는 커널 state를 가집니다. 즉, 커널의 data structure를 update(저장, 읽기)를 해야 합니다. 따라서 유저 스레드보다 많은 생성 시간과 overhead가 존재합니다.
커널이 유저 스레드를 구별하지 못한다는 것은?
커널 입장에서 만약 커널 스레드가 하나라면 커널 입장에서는 프로세스가 넘어왔구나 생각합니다. 유저 스레드 없이 프로세스가 있다면 하나의 control이 user context에서 동작하다가 커널 context로 동작하는 것을 의미합니다. Context가 다르지만 하나로써 동작한 것이죠. 유저 스레드라는 것을 설명하고 multiple context가 존재하니 커널 스레드가 비록 하나일지라도 그런 표현을 사용하는 것입니다. 즉, 유저 스레드만 있고 커널 스레드가 하나만 있는 경우에서는 스레드인지 아닌지 구분하지 못합니다. 즉, 커널 자체는 그 프로그램이 multiple user thread가 있는지 없는지 알 수 없습니다. 실제로 프로세스를 아는 것도 PCB라는 datastructure를 통해서 아는 것이지 그런 것이 없으면 구별하기 어렵습니다.
26.04. Mapping of User & Kernel Thread
이제는 유저 스레드와 커널 스레드를 어떻게 mapping 하는지 알아보겠습니다. 여기서 커널 스레드를 VCPU 개념으로 생각해도 좋습니다.
26.04.1. Many to One
Many to One은 위에서 보여드린 그림과 같은 구도입니다. 유저 스레드 여러 개가 커널 스레드 1개와 관계를 맺는 경우입니다. 스레드 관리는 유저 레벨에서 이루어집니다. 커널 스레드를 지원하지 못하는 시스템에서 사용되며 한번에 하나의 스레드만 커널에 접근이 가능하다는 단점이 있습니다. 즉, 하나의 스레드가 커널에 접근(System call)하면 나머지 스레드들은 대기 해야한다는 의미입니다. 따라서 진정한 concurrency를 지원한다고 보기 어렵고 동시에 여러 개의 스레드가 시스템 콜을 사용할 수 없습니다. 커널 입장에서 여러 개의 스레드는 하나의 프로세스이기 때문에 multiprocessor이더라도 여러 개의 프로세서에서 동시에 수행이 불가능합니다.(Many to One model에서는 멀티스레드 프로그램이 실행되면 스레드 라이브러리에서 여러 개의 스레드들을 스케쥴링합니다. 이렇게 하여 사용자가 하나의 프로그램이 여러 개의 스레드로 동작하는 것처럼 착각하게 합니다.)
커널 스레드를 지원하는 것이 생각보다 복잡합니다. 커널 안에서 preemption이 생기기 때문입니다. 이전에 CPU 프로세스 time quantum에서 다루었던 preemption은 user level이였습니다. 여기서의 전제는 커널에서는 preemption이 특별한 일을 하지 않는 것이였습니다. 즉, 시스템 콜이 발생하면 커널에서 수행하는 동안은 preemption이 발생하지 않는다는 것이죠. 그래서 linux 2.6정도까지만 하더라도 커널 스레드를 지원하지 않았습니다. 물론 현재 대부분 OS는 커널 스레드를 지원합니다.
실제로 커널이 멀티스레드가 되고나서는 커널에서 수행하다 preemption이 발생해 state를 저장할 필요가 생겼습니다. 커널이 multithread화 되어야 한다는 거죠.(커널 안에서 data structure들 예를 들면 커널이 실행하면 single 스레드를 실행하여 protect하지 않아도 되었던 PCB도 이제는 protect하여야 합니다.) 이는 동기화의 main issue가 될 예정입니다. PCB를 예로 들어 계속 설명하자면 PCB control block에 엉뚱한 값이 들어갈 수 있으니 관리가 필요하다는 겁니다. 지금은 커널 스레드를 지원하지 못하는 시스템에서 Many to One model이 사용되었다는 것만 기억하고 동기화에 대한 내용은 다른 포스팅에서 자세히 설명드리도록 하겠습니다.(Many to One model에서는 PCB 값이 동시에 update 될 일이 없으므로 protect를 고민하지 않아도 됩니다. 커널 안에서 하나의 스레드만 동작하기 때문입니다.)
26.04.2. One to One
말 그대로 일대일 mapping입니다. 유저 스레드와 커널 스레드에 구분이 희미해졌는데요. 유저 스레드가 생성되면 커널 스레드가 생성되고 Many to One model의 block 문제도 해결할 수 있습니다. 여러 개의 스레드를 multiprocessor에서 동시에 수행할 수 있게 되었습니다. 다만 유저 스레드는 이론적으로 무한생성이 가능하지만 커널 스레드는 커널의 자원을 사용하게 때문에 무한생성이 불가능합니다. 따라서 유저 스레드를 생성할 때 이러한 고려가 필요하다는 한계점이 있습니다.
26.04.3. Many to Many
여러 개의 유저 스레드를 여러 개의 커널 스레드로 mapping시키는 model입니다. 이전에 살펴보았던 두 model의 문제점을 모두 해결하는데요. 스레드 수에 대해 고민할 필요도 없고 block 현상에 대한 걱정도 덜었습니다.(One to One model과는 다르게 어떠한 메커니즘을 통해 커널 스레드 생성을 조절할 여지가 존재한다는 것입니다. 무조건 만드는 것과 request base로 처리하는 것에는 분명한 차이가 있습니다.) 커널은 스스로 적절히 mapping을 조절하여 위의 장점을 보장할 수 있습니다. 중간에서 user level thread 라이브러리가 multiplexing해주는 동작을 해줄 수 있습니다. 가상화에서 배우는 VCPU 개념으로 생각해보면 VM이 있으면 VCPU 하드웨어를 할당하여 여러 처리를 할 수 있게 되는데 VM에 몇 개의 core를 사용할 수 있는지 결정하는 것입니다.
오늘은 스레드에 대해 알아보았습니다. 사실 프로세스에 대한 이해로도 충분히 스케쥴링과 IPC에 대한 idea를 이해할 수 있고 또 발전도 그런 track으로 해왔기 때문에 프로세스 > IPC, 스케쥴링 > 스레드 순으로 포스팅하였습니다. 스레드를 배웠기 때문에 이전에 배웠던 스케쥴링과 IPC에서 프로세스를 기준으로 다루었던 issue들을 다시 점검해 볼 필요가 생겼는데요. 이를 다음 포스팅에서 다루도록 하겠습니다. 질문과 오류지적은 언제나 환영이니 많이 댓글 남겨주시면 감사하겠습니다.
'학부공부 > OS_운영체제' 카테고리의 다른 글
TP1.1. 05.13.BREAKOUT MEETING [OS Scheduling simulator] (2) | 2022.06.06 |
---|---|
27. Thread(2) (17) | 2021.12.10 |
25. OS Scheduling case [Interesting topic to study] (0) | 2021.12.07 |
24. CPU Scheduling(4) (0) | 2021.12.07 |
23. CPU Scheduling(3) (0) | 2021.12.06 |
댓글