Operating System 4 - Process & Thread
Thread & Concurrency
Process는 Resource Ownership & Scheduling/Execution 의 큰 두 가지 characteristic을 가진다. 따라서 자원할당과 실행 단위로 움직이는 것 두개로 구성된다고 볼 수 있다.
이 때 두 단위를 구분하여 자원 할당과 관련된 것을 process(task)라 하고, 이를 dispatch하고 실행하는 것을 thread라 나누어 부르게 된다.
- Single Thread : 각 프로세스에 실행 단위가 하나씩 존재하는 것. MS-DOS에서 실행 되었으며 이 경우, 멀티 프로세스일 때도 각 프로세스에서는 실행 단위 스레드는 하나씩만 존재했다.
- Multiple Thread : 프로세스 내부에 실행 단위가 여러 개 존재하여 병렬 처리를 할 수 있는 것. 현대의 OS는 모두 멀티 프로세스 & 멀티 스레드를 지원하고 있다.
이를 통해 여러 개를 동시에 실행할 수 있게 함으로써 대기 시간을 획기적으로 줄일 수 있다. 또한 여러 기기에 접근할 때 메모리 할당 및 부팅동도 훨씬 빨리 수행될 수 있다. (시스템 전체의 속도가 비약적으로 상승)
이런 방식은 클라이언트-서버 간의 통신에서도 서버가 들어오는 요구를 스레드로 하나씩 만들어 처리하고, 서버는 계속적으로 들어오는 요구를 처리하게 하면 동시 접속 시의 대기 시간을 줄일 수 있다.
이런 스레드 관점에서 보면, 프로세스는 자원 할당의 단위가 되고, 해당 자원, 파일, I/O 들이 다른 것들에 의해 침입당하지 않도록 보호해준다.
스레드는 각 스레드 자신의 실행단위의 상태(running, ready 등등), PC, register 값, stack & local variable 등등이 저장될 공간이 있어야 한다. 단, code, data, file 등등은 같은 프로세스 내에서 스레드 간에 공유된다. 이런 자원들은 모두 프로세스에 의해 공유받으므로 해당 자원에 대한 access 권한이 있어야 한다.
프로세스는 무겁고, 스레드는 가볍다. PCB(process control block)는 많은 정보를 가진다. 반면, TCB(thread control block)는 어짜피 프로세스의 자원이 공유되므로 PC, register 정도만 저장해도 된다. 따라서 훨씬 적은 비용으로 생성될 수 있다.
각 TCB는 생성될 때 PCB를 가리키는 포인터를 가지고 있어 할당 받은 자원을 공유하며, dispatch는 TCB가 하게 된다.
스레드로 나누었을 때 각 스레드 별로 block/run이 따로 수행되므로 응답 속도가 빨라진다. 이를 통해 UI가 발전할 수 있다.
또한 통신에 있어서도 shared memory 등을 만들 필요 없이 스레드 끼리 할당된 자원을 공유하므로 바로 사용할 수 있다는 장점이 있다.
context switch의 오버헤드가 줄어들고, 자원 할당도 경제적으로 할 수 있다. (커다란 PCB를 바꿀 필요 없이 TCB만 바꾸면 되므로)
그리고 멀티 스레드 방식의 일처리를 멀티 코어 프로세서에 확장하여 사용하기도 쉽다. 또 들어오는 작업들을 여러 개의 스레드로 나누어 확장하기도 쉽다.
Thread base OS에서 Scheduling, dispatching은 모두 스레드 단위로 실행된다. 또한 process의 suspend나 termination은 해당 프로세스 내부의 스레드 모두에게 영향을 미친다.
State of thread
- running
- ready
- spawn : 상황에 따라 스레드가 생성된다.
- Block & unblock : event 대기 시 block된다.
- finish
앞서 context switch, interleaving을 하는 단위가 프로세스였다면, 이제는 해당 작업들이 모두 스레드 단위로 일어나게 된다. 이 때 같은 프로세스 내의 스레드가 다음 running으로 들어온다면 TCB만 변환하므로 오버헤드가 훨씬 줄어든다.
Thread Synchronization
같은 프로세스 내의 스레드들이 모두 데이터에 접근하게 되므로 스레드 간의 연산 순서에 따라 잘못된 결과값이 저장될 수 있다. (스레드들 끼리 경쟁 상태가 된다) 따라서 동기화를 해주어야 한다.
Type of Thread
- User level thread(ULT) : application 내에서 돌아가는 스레드
- Kernel level thread(KLT) : 이제껏 설명한 내용 (dispatch, schedule 등등) 은 모두 커널 스레드
초기 싱글 프로세스의 경우 커널 레벨에서 어플리케이션의 스레드 상황을 알 수 없기 때문에 many-to-one 방식으로 작동했다. 이 경우 커널이 하나씩 받아서 실행하므로 시스템 프로세스와 스레드의 스케쥴링이 따로 노는 문제점이 생긴다. (스레드는 러닝이고 프로세스는 블락 상태, 혹은 그 반대의 상태가 발생할 수 있다.)
이 경우 context switch의 개념이 없고 그냥 kernel이 주는 것을 순서대로 실행하는 것이 된다. 따라서 시스템은 좀 더 빨리 실행되고 스케쥴링은 애플리케이션 단에서 자신의 작업에 효율적인 방식으로 이루어진다. 그러나 스레드는 system block에 의한 대기시간이 길어진다. 그리고 kernel은 하나 뿐이라 병렬적으로 작업을 처리할 수 없다.
* Jacketing I/O : 스레드 단에서 I/O가 가능한지 확인하고, 불가능하다면 해당 I/O를 block하고, 가능하면 실행하고 스레드가 block된다. 즉, 스레드를 blocking 할 수 있는 기능을 제공해준다. 이를 통해 전체 시스템 콜의 블락 시간을 줄일 수 있다.
이런 비효율을 해결하기 위해 현대에는 ULT 하나에 KLT 하나를 매핑하는 방식으로 작동한다. 그래서 어플리케이션이 ULT를 만들면 이와 연결된 KLT가 생성되고, 스레드 스케줄링은 모두 커널 단에서 이루어지게 된다. (어플리케이션은 API interface를 사용하여 생성만 함)
이전 경우는 하나의 스레드가 막힐 경우 다른 스레드도 실행이 안되었지만, 이렇게 하면 프로세스 단위가 아니므로 해당 커널 스레드만 막히고 나머지는 정상적으로 작동할 수 있다.
따라서 병렬적으로 작업을 할 수 있지만 커널 프로세스 단에서 스레드를 context switch 하는 것의 오버헤드는 더 커진다. 그러나 전반적인 작업 속도는 더 빨라지므로 이런 one-to-one 방식을 사용한다.
또 다른 방식으로 many-to-many로 어플리케이션 보다 적은 수의 커널 스레드를 만들어 연결하여 사용하는 것도 존재한다.
Multicore Programming
스레드를 사용한다는 것은 멀티 코어를 사용한다는 것이다. 그러나 순서대로 진행해야 하는 작업을 바로 병행 수행할 수는 없다. 앞서 RPC의 response를 기다리는 등 분리해 실행할 수 있는 파트를 찾아 이를 분류하고, 따로 실행하여야 한다. 또한 이런 작업의 할당에 있어서 밸런싱을 잘 해야 한다.
Concurrency(병행성) & parallelism(병렬성)
프로세스(스레드)가 많아지면 병행성이 좋아지고, 코어가 많아지면 병렬성이 좋아진다. 다만 순서대로, 병행성이 좋아져야 병렬성도 좋아진다. (병행되는 작업이 많아야 병렬적으로 수행할 수 있다)
Type of parallelism
- Data parallelism : 숫자를 더하는 작업을 절반씩 나누어 두 개의 코어에 할당하는 것. (다른 데이터로 같은 작업을 수행하는 경우)
- Task parallelism : 사용하는 알고리즘을 서로 달리 하여 서로 다른 job을 할당하는 것 (같은 데이터로 하는 작업이 다를 경우)
Amdahl's law
코어가 많아진다고 반드시 성능이 좋어지지는 않는다. 전체 작업을 순서대로 해야 하는 것(serial)과 병렬화 할 수 있는 것(1-S)을 분리하여 생각해야 한다. 전체 작업 중에 병렬화 할 수 있는 것이 늘어날 수록 코어 수에 비례하여 성능이 좋아지지만, 그렇지 않다면 성능 향상은 코어 수보다 적을 수 밖에 없다.
댓글
댓글 쓰기