프로그래밍 면접 이렇게 준비한다?

[필기,면접]운영체제_스레드

vhxpffltm 2019. 4. 10. 20:35


동시성
스레드 : 어플리케이션 실행의 기본 단위, 실행중인 어플리케이션은 최소 하나의 스레드로 구성
           각 스레드마다 별도의 스텍이 있으며, 다른 스레드와 독립적으로 움직임, 파일핸들이나 메모리 자원을 공유함 -> 공유자원에 대한 접근을 잘 제어해야함
           스레드는 운영체제에서 생성 및 관리 - 커널 수준 스레드
           운영체제 위의 SW계층에서 구현 - 녹색 스레드 , 멀티코어 활용X ,Blocking I/O, Synchronous I/O 구현힘듬, 멀티코어 시스템이 주류가 되면서 거이 안씀
           
동시에 돌릴 수 있는 스레드수는 컴퓨터 코어 갯수로 제한-> O/S에서 각 스레드에 조금씩 시간을 나눠주면서 여러 스레드 운영
선점형 스레딩 : O/S에서 마음대로 실행을 멈추고 다른 스레드를 실행시킬 수 있음
협력형 모델 : 어떤 스레드가 멈추고 다른 스레드가 돌아가려면 원래 실행중인 스레드에서 명시적으로 어떤 행동을 취해야함
컨텍스트 스위칭(Context Switching) -> 다른 스레드가 시작될 수 있도록 한 스레드를 멈추는것 -> 프로세스 데이터를 레지스터와 메모리 사이를 복사하는 작업 -> 너무 많으면 오버헤드 발생으로 성능저하

*개념 정리*

기본 개념이니 이정도는 알고 넘어가면 좋을것 같다.
멀티코어 프로그래밍 : 하나의 작업을 위해 여러개의 CPU코어를 사용하기 위해 코드를 작성
멀티프로세싱 : 한 개 이상의 컴퓨터 프로세서들이 협력하여 프로그램들을 처리하는 것, 같은 프로그램을 동시에 병렬로 처리하고있는 여러 대의 컴퓨터
멀티스레딩 : 하나의 프로세스에서 여러 스레드를 생성하여 여러 CPU코어를 사용
                스레드는 한 프로세스의 힙 메모리공간과 텍스트+코드 메모리공간을 공유

 

 


출처 :   https://kldp.org/node/295 

시스템 스레드   사용자 스레드

* 사용자 수준 커널과 시스템 수준의 커널에 대해 서술하는 문제를 보았었다. 이와함께 커널 수준에서 Non-blcoking call에 대해 알고 있는지 물어보는 수준으로 나왔었다. Non-blocking call에 따른 system call에 대해 조금은 공부를 해놓자

위의 출처에 있는 내용을 가져온 것이다.

시스템 스레드는 시스템에서 생성하고 관리, 어플리케이션의 첫번째 스레드(메인스레드)는 시스템 스레드이며, 종료될때 어플리케이션 종료
프로그래머 요청에 따라 스레드를 생성하고 스케줄링하는 주체가 커널이면 커널 레벨(Kernel Level) 스레드라고 한다.

Kernel-level에 있는 Threads는 독립적으로 스케줄되므로 특정 Thread에서의 Blocking이 process로 전파되지 않습니다. 
그래서 Blocking System Calls를 이용할수 있습니다. 또한 각 Threads끼리 Signal을 주고 받을 수 있습니다. 
커널이 각 스레드를 개별적으로 관리할 수 있다. 몇몇 프로세서에서 한꺼번에 디스패치 할 수 있기 때문에 멀티프로세서 환경에서 매우 빠르게 동작
Kernel-level threads는 특별히 고려할만한 장애를 가지고 있지는 않습니다. 
물론 마찬가지로 Thread-Safe해야 하지만, OS 개발자들은 대개의 표준 라이브러리를 Thread-Safe하게(재진입해도 문제없겠끔) 만들기에 User-level threads보다 통상적으로 보다덜 말썽을 피웁니다.

Kernel-level threads는 안정성에 비해서 너무 느리다는게 큰 단점입니다. 바로 Thread Context-Switch 때문입니다. 마로비치의 연구에 의하면 10배정도 느리다고 합니다.


사용자 스레드는 메인 스레드에서 할 수 없는 혹은 하면 안되는 작업을 할때, 어플리케이션에 명시적으로 생성하는 스레드
커널에 의존적이지 않은 형태로 스레드의 기능을 제공하는 라이브러리를 활용하는 방식이 사용자 레벨(User Level) 스레드다.

User-Level threads는 응용 프로그램과 Link/Load가 되는 라이브러리로 구현되어집니다. 
이 라이브러리에 동기화, 스케줄링 기능을 모두 담고 있습니다. 커널에서는 아무런 지원을 해주지 않으며, 커널이 보기에는 단지 그냥 Single process일뿐입니다. 
*프로세스마다 런타임 라이브러리의 Copy가 호출되므로 스케줄링 정책을 프로세스마다 달리 취할 수 있으며*, *각 Thread마다 time quantum을 소모할 필요 없고, 런타임 라이브러리가 context를 유지하기때문에 switching을 할 필요가 없습니다. *
커널은 사용자 레벨 스레드의 존재조차 모르기 때문에 모드 간의 전환이 없고 성능 이득이 발생한다. 그래서 User-Level Threads는 빠르고, 매우 효율적입니다. 그러나 장애가 꽤 있습니다.

*** 1. Blocking System Calls ***
Blocking function이란 처리가 완료되지않으면 return되지 않는 함수인데, 만약 특정 Thread에서 Blocking이 되어 버리면, 전체 process가 Blocking이 되어버립니다. 이런 이유로 운영체제가 제공하는 non-blocking 함수들만 사용해야 하며, 사용 빈도가 높은 함수(read,select,wait,...)들은 해당 함수의 non-blocking 버젼으로 대체해야할 필요가 있습니다. 

2. Shared System Resources
동기화나 Locking없이 Thread끼리 공유하는 변수(드러나지 않고 감춰져 있는 경우)가 있을때, 그 Thread가 thread-safe하지 않으면 Overwrite되는 문제가 생길 수 있습니다. 이 이유로 사용할 함수는 재진입이 가능해야합니다. User-Level뿐만아니라 Kernel-Level 함수까지 모두.

3. signal Handling, Thread Scheduling
User-Level에서 이것을 구현하기란 상당히 어렵습니다. Timeslice를 다루기 위해 Hardware Clock 인터럽트를 보통의 방법으로는 받지 못합니다. 선점형(Preemptive) 스케줄링을 하기 위해서는 커널로 부터 Time Siganl을 받는 함수를 등록해두어야 하며, Timer Alarm Siganl을 다루는것은 다른 시그널을 다루는 것보다 아주 어럽습니다.

4. Multiprocess Utilization
하나의 프로세스에서 Time을 공유하고 있기때문에 여러개의 CPU를 동시에 사용할 수는 없습니다.

정리 - 구현상의 어려움과 복잡성 그리고, 몇가지 장애에도 불구하고, concurrency와 efficiency의 이득을 가져다 줍니다.


모니터, 세마포어, 뮤텍스


스레드 동기화 -> 스레드와 공유자원의 상호작용을 제어함 -> wait, signaling
모니터 : 상호 배제 좌물쇠로 보호되는 루틴의 집합, 뮤텍스 + condition value(wait,signal)
           자물쇠의 획득과 *해제를 모두 처리

세마포어 : 공유자원을 보호하기 위한 자물쇠만 존재, 자물쇠를 획득하고 해제할 수 있는 작업을 챙겨야함, 소유불가,하나 이상을 동기화
P(Wait) - 자물쇠를 가지고 가는 방식 없다면 기다림 , V(Post) - 자물쇠반납

뮤텍스 : 상호배제 세마포어, 동기화된 지역으로 만들기 위해 Critical Section을 설정, 하나의 스레드만이 Critical Section에서 행동가능, 소유할 수 있고 책임을 짐, 1개만 동기화
Ex) 스레드에서 공유자원을 사용하려면 자물쇠를 획득해야함 -> 자물쇠를 쥐고 있는 스레드에서 놓아주기 전까지 다른 스레드는 막힘
    -> 자물쇠를 놓는 순간, 대기하고 있던 다른 스레드가 자물쇠를 획득

 

 

 

출처 : https://www.crocus.co.kr/1363


*크리티컬 섹션(Critical Section) 프로세스 하나에 포함된 여러 개의 스레드가 공유 리소스에 접근할 때 배타적 제어를 하기 위한 구조다. 
 1. 동일한 프로세스 내의 여러 스레드 사이에서만 동기화가 가능하다.
2. 커널 모드 객체(Mutex, Semaphore)가 아닌 유저 모드 동기화 객체이므로 가볍고 빠르다.
3. 먼저 접근한 스레드는 EnterCiricalSection을 통해 락을 획득하고, 그 이후에 Critical Section에 들어오고자 하는 스레드는 대기시킨다. 이후 LeaveCriticalSection으로 락을 해제 하게되면 대기중이던 다른 스레드가 접근 할 수 있게 된다.
4. 대기 중인 스레드는 다른 스레드에게 CPU를 yield해야하므로 컨텍스트 스위칭이 발생하고 대기 시간동안에는 CPU 점유를 하지 않게 된다.


바쁜 대기(Busy Waiting)
대기 중인 스레드가 활성상태이긴 하지만, 실제로는 아무 일도 하지 않음 -> 다음 스레드에서 일을 처리할 수 있는 소중한 프로세서 싸이클을 뺏아버림
  -> 모니터나 세마포어로 해결 -> 다른 스레드에서 작업이 끝났음을 알려줄 때까지 대기하는 스레드를 휴지(Sleep) 상태로 전환

 


스핀락 : 바쁜 대기의 개념을 이용한 락
임계구역에 진입이 불가능할 때 진입이 가능할 때까지 루프를 돌아가며 재시도하는 방식으로 구현된 락
대기중인 락이 일반적인 락에 비해 더 짧은 시간안에 풀린다는 보장이 있을때 사용하면 좋다.
컨텍스트 스위칭을 줄여 CPU부담을 덜어줄 수 있지만 임계구역 진입을 위한 대기시간이 짧을때 사용해야 한다.