해당 게시글은 kocw에서 제공하는 금오공과대학교 최태영 교수님의 무료 강의를 공부하고 정리하기 위해서 만들어졌습니다.
Implicit Thread
- Pthread, window thread, java thread는 모두 명시적으로 스레드를 작성하여 사용하게 제공된다.
- 그렇지만 프로그래머 입장에서는 스레드는 하나가 아니라 수백, 수천개 이기 때문에 아주 번거로운 작업이라고 할 수 있다.
- 최신 프로그래밍 언어에서는 이 점을 해결하기 위해 Implicit thread 기능들을 제공한다.
- 대표적으로 아래와 같은 것들이 있다.
- Thread Pools
- 일정한 수의 스레드를 미리 생성해 두어 필요할 때 스레드를 가져다 쓰게 하는 것이다.
- Thread Pools의 장점
- 속도가 빠르다.
- 만들어진 스레드를 가져다 쓰는 형식이기 때문이다.
- 어플리케이션 메모리의 사용이 제한되기 때문에 메모리 관리가 효율적이다.
- 주기적으로 스레드를 생성해서 쓰는 방법을 사용할 수 있다.
- OpenMP(Multiple Programming)
- 스레드를 생성하고 사용하는 어느 한 부분을 병렬적으로 처리하는 프로그래밍
- 기본적으로 프로그래밍 언어를 통해 수행된다.
- 스레드가 생성되고 수행되는 부분을 언어 차원에서 지원하기 때문에 프로그래머는 해당 로직을 작성하거나 신경쓰지 않아도 된다.
- 특정 키워드만 작성하면된다.
- C언어 ex) #pragma omp parallel
- Grand Central Dispatch
- 맥에서 쓰이는 스레드 관리 기법이다.
- OpenMP를 확장시켜 더 많은 기능을 제공한다.
- 기본 기능을 수행시키는 메인 흐름과 메인 흐름에서 만드는 태스크를 담아둘 큐를 제공한다.
- 필요할 때마다 큐를 생성해서 태스크를 담아두고, 필요할 때마다 큐에 태스크를 추가해준다.
- 이 큐를 dispatch queue라고 하고 이것을 통해 태스크가 병렬적으로 처리된다.
- 큐는 두가지가 제공된다.
- serial queue
- 직렬적으로 처리되는 큐
- 기본적으로 하나가 있지만 필요에 따라 더 만들 수 있다.
- concurrent queue
- 큐는 여러 개의 cpu를 사용할 수 있다.
- task의 순서는 의미가 없다.
Thread Issues(Multi thread system issues)
- fork(), exec()
- fork를 사용하여 부모 프로세스를 복사하여 그대로 자식 프로세스를 생성한다고 할 때,
- 내부에 있는 스레드를 모두 복사해야 한다.
- 단순히 스레드를 복사하는 것이 아니라 스레드 하나마다 PCB같은 자료구조와 스택, 레지스터가 필요하고, 이러한 컨텍스트를 담은 공간(메모리)도 필요하다.
- 시간이 오래 걸리는 작업 들이다.
- 이러한 과정을 수행중에 exec()를 호출하게 되면,
- 새로운 실행코드로 어드레스 스테이터스를 오버라이딩 하게 된다.
- 이렇게 되면 기껏 만들어 둔 것이 쓸모 없어지게 되어 문제가 생긴다.
- 문제를 해결하기 위해,
- 필요한 스레드를 계속 만들어 주는 방법이 있고,
- 좀 더 성능을 생각하여 개선한다고 하면, fork를 수행중에 exec같은 코드를 실행하는 명령어가 들어오면 스레드를 생성하지 않도록 하는 방법이 있다.
- Signal Handling
- signal
- 특정 상황(인터럽트 같은)을 처리 하기 위한 신호이다.
- kill 명령어로 처리된다.
- sigint, ^-c(control + c), ^-z(control + z) 등
- bash이 받아서 어플리케이션에 signal을 보낸다.
- 시그널은 하나의 프로세스가 다른 프로세스와 통신하는 방법이라고 할 수 있다.
- 아주 짧지만 몇 가지 통신을 보낸다.
- 시그널을 받은 프로세스는 어떤 응답을 하는 것이 아니고 바로 리액션을 취한다.
- 어떤 프로그램을 짠다고 했을 때, 컴파일러는 프로그래머 몰래 몇가지 시그널 핸들러 루틴을 생성한다.
- 그 중 하나가 kill(종료) 시그널 핸들러이다.
- 물론 프로그래머가 이 시그널 핸들러를 덮어 씌울수도 있다.
- 어떤 프로세스가 여러 스레드를 쓰는 상황에서 시그널이 온다고 하면,
- 어떤 스레드가 시그널을 처리할지 정해야 한다.
- 이 것을 처리하기 위한 옵션을 네 가지로 분류할 수 있다.
- 스레드가 만들어질 때마다 담당할 시그널을 정하는 방법
- 모든 스레드에게 시그널을 복사해서 전달하고 모든 스레드가 시그널 핸들러를 실행하는 방법
- 모든 스레드가 핸들러를 수행하기 때문에, 이렇게 해도 괜찮은 핸들러는 상관이 없다.
- 랜덤하게 시그널을 처리할 스레드를 지정하는 방법
- 모든 시그널을 처리할 스레드를 정하는 방법
- Thread Cancellation
- 스레드 종료시 바로 종료할지 시간을 두고 종료할지 결정해야 하는 문제이다.
- 종료를 할 스레드를 Target thread라고 한다.
- 종료 방식은 두가지가 있다.
- Asynchronous cancellation: 타겟 스레드를 바로 종료하는 방법
- Deferred cancellation: 타겟 스레드가 중요한 일을 처리하고 있으니, 그 일을 끝내고 종료하는 방법
- deferred cancellation이 있는 이유는 스레드가 중요한 일(시스템에 오류를 줄 수 있는)을 처리하기 때문이다.
- 스레드의 mode를 가지고 종료 모드를 지정할 수 있다.
Thread-Local storage
- 변수 타입
- local
- global
- static
- tls(스레드 내에서 쓰는 변수)
- 스레드 들이 활성화 되고 각 스레드 내부에서 변수를 사용하게 되면,
- local 형식으로 사용할 때, 스레드 안에서 하나의 변수로 쓰이고 변수 환경은 스레드가 종료 되더라도 계속 존재하게 된다.
- static 형식으로 사용하면, 전역 변수와 동일한 형태로 사용되기 때문에, 각 스레드에서 같은 변수로 쓰이게 된다.
- 이러한 것처럼 스레드 내에서 독립된 형태로 쓰이고 계속 스레드 환경에 남아 있는 변수를 Thread-Local storage라고 한다.
- 이 것을 사용하는 이유는 thread의 환경을 유지하고 싶기 때문이다.
Scheduler Activations
- Many-to-Many 모델에서 2-level 모델이라면 매핑된 커널 스레드가 고정되어 있기 때문에, 스레드 블락이 굉장히 쉽다.
- 그러나 일반적인 M2M 모델은 그렇지 않기 때문에 블락을 어떻게 할지 고민이 필요하다.
- upcall 함수 수행
- 어떤 유저 스레드1이 read 명령을 수행하여 device-wait queue에 들어가고 커널 스레드 하나가 블락이 된다고 했을 때,
- 다른 유저 스레드2 를 사용하기 위해서 upcall 함수를 수행한다.
- upcall은 블락이 될 스레드의 스테이트를 TCB(Thread Control Block)에 저장하고,
- 사용 가능한 커널 스레드에 연결시켜준다.
- 그러고 유저 스레드2가 수행된다.
- 이 것을 scheduler activations이라고 한다.