I. Process의 단점
A. Process는 Address Space, OS Resources (Open File), Hardware Execution State (PC, SP, Register) 등등을 포함하고 있어 무겁다. (PC == Program Counter, CPU 내부의 Register 중 하나이며 프로세스 Address Space의 Code 영역에 존재하는 Instruction을 가르킨다) (SP == Stack Pointer, CPU 내부의 Register 중 하나이며 Function Call이 발생할 때마다 관련 정보가 프로세스 Address Space의 Stack 영역에 차게 되는데 그 해당 정보를 가르키는 것이 SP이다)
B. OS가 관리하는 IPC (Inter Process Communication) 작업의 오버헤드가 존재한다.
II. Thread란
프로세스의 단점 때문에 프로세스를 새로 생성하지 않고 프로세스 내에서 작업을 수행할 수 있는 여러 갈래의 Path를 만들었고 이는 처음에 Lightweight Process라고 불리다가 이후 Thread로 명명되었다. 또한 프로세스 내에서 Thread간의 통신에 의해 발생하는 오버헤드는 Process간의 통신에 의해 발생하는 오버헤드보다 적다. (자원 일부를 공유하기 때문이다)
III. Multithreaded Process의 구조
여러 Thread들은 Process Address Space의 Code 영역, Static Data 영역, 접근 중인 Files들을 공유하지만, 각각의 Thread들은 각각의 함수를 Parallel 하게 처리 해야 하기 때문에 독립적인 Register와 Stack 영역을 갖는다. 하지만 Code 영역, Static Data 영역, 접근 중인 Files들을 공유하기 때문에 IPC에 의해 발생하는 오버헤드보다는 적은 오버헤드가 발생한다.
IV. Multicore Programming이란
CPU 안에 코어가 여러 개여서, 이 여러 개의 코어들이 Context Switching (CPU Switch)를 빠르게 수행하면서 병렬적으로 여러 Task를 처리하는 것을 의미한다.
V. Data Parallelism VS Task Parallelism
Data Parallelism은 분할된 각각의 Data들을 각각의 Core들이 처리하는 구조이며, 대표적인 예로는 배열내의 모든 원소의 합을 구할 때가 있다. arr[0]부터 arr[10]까지의 합은 core1이, arr[11]부터 arr[20]까지의 합은 core2가 하는 식이다.
Task Parallelism은 통합된 Data를 여러 개의 Core들이 처리하는 구조이다. 대표적인 예로는 대표적인 예로는 한 반의 성적 데이터를 가지고 두개의 코어가 평균과 분산을 각각 구하는 경우이다.
VI. Conditional Compilation이란
부모프로세스가 fork() 시스템 콜을 통해 자식프로세스를 만들었을 때 wait() 시스템 콜을 쓰는지 안 쓰는지에 따라 두 프로세스간 동기, 비동기 적용을 결정할 수 있다. 그래서 코드상에서 wait() 앞에 주석을 달거나 지우기도 하는 데, 이것은 테크니컬 하지 않다. 따라서 # if 1과 같이 Conditional Compilation 문법을 사용한다.
VII. User Level Thread란
커널은 프로세스 내의 Multithreaded 구조를 알지 못하는 상태이고 Process Table을 통해 Process들만 관리하는 형태이다. Multithread 기능을 제공하는 라이브러리가 Application 단에서 동작하게 된다.
VIII. Kernel Level Thread란
커널이 Process Table, Thread Table을 통해 Process들과 Thread들을 동시에 관리하는 형태이다. 이는 OS의 역할이 많아져서 오버헤드가 증가한다는 단점이 존재한다.
IX. User Level Thread 시스템의 단점
User Level Thread 시스템에서 Process 내에 Thread가 3개 존재하고, 세번째 Thread가 I/O 작업을 위해 시스템 콜에 요청을 했다고 가정(소프트웨어 인터럽트, Trap 발생) 그럼 Process 내에 Thread가 있는 것을 모르는 Kernel은 해당 Process를 Waiting State로 상태 변환을 시킨다. 이 때 1번 Thread, 2번 Thread는 다른 작업을 병렬적으로 처리할 수 있지만 작업 처리가 중단 되어 버린다.
하지만 Kernel Level Thread 시스템을 이용할 경우 Thread들이 Thread Table로 관리되기 때문에 I/O 작업을 요청한 3번 Thread만 중단된다.
X. Multithreading Models
A. Many-to-One: 여러 개의 User Thread들을 하나의 Kernel Thread와 대응되는 구조 (User Level Thread), 장점은 Thread 관리가 User mode에서 이루어지기 때문에 속도가 빠르다는 것이고 단점은 하나의 Thread가 Block 되면 전체의 Thread가 Block 된다는 것이다.
B. One-to-One: 하나의 각각의 User Thread가 하나의 각각의 Kernel Thread와 대응되는 구조 (Kernel Level Thread), 장점은 하나의 Thread가 Block 되어도 나머지 Thread들은 동작이 가능하다는 것이며 단점은 비용이 비싸다
C. Many-to-Many: Kernel Thread들 보다 수가 많은 User Thread들이 다대다 관계로 대응 되는 구조 (Kernel Level Thread), 장점은 비용이 One-to-One 모델보다 저렴하다는 것이고 Many-to-One 모델과 달리 Thread가 동시 수행이 가능하다는 것이다.
D. Two-level: Many-to-Many 구조와 One-to-One 구조를 합친 구조
XI. Thread의 복제 방법
Process 내에 Thread가 여러 개 일 때 하나의 특정 Thread가 복제를 수행할 때 이 작업 수행에 대한 결과는 두가지로 나뉜다.
A.프로세스 자체를 복사하지 않고 스레드만 복제된다.
B.프로세스 자체가 복제된다.
하지만 암묵적으로 Multithreaded Process를 이용하는 경우 fork() 시스템 콜을 수행하지 않는 것이 암묵적인 룰로 받아들여지고 있다.
XII. Mutex란
Multiprocess 혹은 Multithread를 이용할 때 자원에 대한 동기화(동시성) 문제를 해결하기 위해 Mutex가 존재한다.
즉, Mutex는 동시 프로그래밍에서 자원의 동시 접근을 피하기 위해 사용하는 알고리즘이며 임계구역(Critical Section)을 가진 Thread들의 실행시간(Running Time)이 서로 겹치지 않고 각각 단독으로 실행(상호배제, Mutual Exclusion)되도록 하는 기술이다. 또한 한 프로세스에 의해 소유될 수 있는 Key를 기반으로 한 상호배제 기법이며 Key에 해당하는 어떤 객체(Object)가 있고, 이 객체를 소유한 Thread/Process만이 공유자원에 접근할 수 있다.
[개인 공부, 출처: https://chelseashin.tistory.com/40]
XIII. Java의 Thread 생성 방법
A. Thread Class를 상속받은 새로운 Class를 만들고 run() function을 overriding
B. Runnable Interface를 implements한 Class를 만들기
플랫폼에 따라서 Multithread를 구현하는 방법이 제각기이며 Java 같은 경우는 VM위에 Process가 올라가서 동작하는 구조이다.
XIV. Concurrency vs Parallelism
Concurrency는 CPU의 Core가 빠르게 Context Switching(CPU Switch)를 수행하면서 여러 Task들을 동시에 수행하는 것처럼 동작하는 것을 의미한다.
Parallelism은 CPU의 2개 이상의 Core가 병렬적으로 동작하면서 각각의 Core는 빠르게 Context Switching(CPU Switch)를 수행하여 여러 Task들을 동시에 수행하는 것처럼 동작하는 것을 의미한다.
XV. Threading Issue
A. Semantics of fork( ) and exec( ) system calls: 만약 fork( ) 이후에 exec( )을 바로 호출한다면 exec( )으로 인해 스레드를 포함한 전체 프로세스가 대체되기 때문에 모든 스레드를 복제하는 것은 불필요할 것이다. 그렇지 않으면 모든 프로세스를 복제해야 한다. 따라서 몇몇 UNIX 시스템은 두 버전의 fork( )를 가진다.
B. Signal Handling: 시그널 (Signal)은 특정한 사건이 발생했다고 프로세스에게 알려주기 위해 UNIX 시스템에서 사용하는 것이다. 자원이나 시그널의 원인에 따라 두 종류로 나뉜다.
1. Synchronous signals: 시그널을 일으킨 작업을 수행한 프로세스에 전달된다. (ex. division by 0, illegal memory access)
2. Asynchronous signals: 수행중인 프로세스의 외부 사건에 의해 만들어진다. (ex. Ctrl+C과 같은 특정 키 입력으로 인한 종료, 타이머 종료)
C. Thread Cancellation: 스레드가 끝나기 전에 종료 시키는 두 방식이 있다.
1. Asynchronous cancellation: 목표 스레드(Target thread)를 즉시 종료 시킨다.
2. Deferred cancellation: 목표 스레드가 종료 되어야 하는지 주기적으로 체크한다.
D. Thread Pools: 스레드를 요청할 때마다 매번 새로운 스레드를 생성, 수행, 삭제를 반복하면 성능이 저하된다. 따라서 미리 스레드 풀(Thread pools)에 여러 스레드를 만들어 두고 요청이 오면 스레드 풀에 기존에 존재하던 스레드를 할당해주는 방법을 사용한다. 새 스레드를 만드는 것보다 기존에 존재하는 스레드를 사용하는 것이 약간 더 빠르고, 많은 양의 스레드를 일정한 크기의 pool 안에 묶어둘 수 있는 장점이 있다.
E. Thread Local Storage: 각각의 스레드들이 자신의 영역을 만들어 관리할 수 있도록 해주는 것이다. static data와 유사하다. 로컬 변수(local variable)와 혼동하면 안 된다. 로컬 변수는 한 함수가 수행되는 동안만 visible 하다.