Super Kawaii Cute Cat Kaoani
본문 바로가기
💾 lecture/운영체제

[OS] Chapter 4: Multithreaded Programming

by wonee1 2023. 10. 14.
728x90
Motivation

하나의 응용 프로그램에 여러 작업이 있다고 가정하자

  • Fetch data
  • Update display

 

 만약 각 작업이 별도의 프로세스로 구현된다면  IPC가 필요하다.

 

*IPC는 프로세스 사이의 통신 방법 

 

만약 각 작업이 별도의 스레드로 구현된다면 각 작업은 다중 코어 시스템에서 병렬(parallel)로 실행된다.

 

대부분의 현대 응용 프로그램은 멀티 스레드로 구성되어 있다. 

 

 

Single and Multithreaded Processe

싱글 스레드 프로세스 VS 멀티 스레드 프로세스
3개의 프로세스가 concurrent하게 실행
하나의 프로세스를 3개의 스레드로 나눠서 CPU 할당
스레드가 9개 있다. 같은 프로세스의 스레드로 왔다 갔다 하면서 실행될 수 있다.

 

Benefits 

 

반응성 (응답성)

  • 대화형 응용 프로그램에서 사용된다 ex) 멀티 스레드 웹 브라우저 
  • 멀티스레드를 사용하면 여러 작업을 동시에 처리할 수 있어 사용자의 입력에 빠르게 반응할 수 있다.

자원 공유  

  • 스레드 기반 프로세스와 협력 프로세스를 비교하면, 스레드는 코드와 데이터를 공유할 수 있어서(한 프로세스 안에 있기 때문에) IPC가 필요하지 않는다
  • 메모리 공간을 공유하는 것이 다른 프로세스 간 통신 또는 메세지 전달보다 간단하며 효율적일 수 있다.

 

경제성 

  • 스레드는 프로세스보다 경제적입니다.
  • 스레드를 생성하고 스레드 간 Context Switch를 하는 것이 프로세스보다 더 경제적이다.

 

멀티 프로세서 아키텍처 이용

  •  멀티 프로세서 시스템에서  싱글 스레드 프로세스가 실행될 때 해당 프로세스는 사용 가능한 CPU 중 하나에서만 실행된다. 

* 멀티 프로세서 아키텍처에서 싱글  스레드 프로세스는 병렬로 여러 CPU를 활용하지 않고, 오직 하나의 CPU에서만 실행된다는 것

 

Concurrency vs. Parallelism

 

Concurrency(동시성)

  • 여러 작업이 진행되는 것을 지원한다.
  • 동시성은 여러 작업이 번갈아가며 실행되는 것을 의미하며, 실제로는 동시에 실행되는 것처럼 보일 수 있다. 그러나 실제로는 작업들이 간격을 두고 번갈아가면서 CPU를 사용하게 된다.
  • 동시성은 주로 다수의 작업이 동시에 진행되는 것처럼 보이는 환경에서 사용된다.

Parallelism(병렬성)

  • 시스템이 둘 이상의 작업을 동시에 수행할 수 있는 능력을 의미한다
  • 병렬성은 여러 작업이 동시에 실행되며, 각 작업이 별도의 처리 장치 또는 코어에서 병렬로 실행된다, 이것은 실제로 동시에 여러 작업이 실행되는 것을 의미한다. 
  • 병렬성은 대규모 작업을 빠르게 처리하거나 더 높은 성능을 얻기 위해 사용된다.

 

 

Example : POSIX Pthread

POSIX 표준 중 하나로, 스레드 생성과 동기화를 위한 API를 정의하는 표준이다.

이 표준 스레드의 동작 방식에 대한 명세서로 구체적인 구현을 다루지 않았다. 

  • 이 API는 스레드 라이브러리의 동작 방식을 정의하며 , 라이브러리의 실제 구현은 해당 라이브러리를 개발하는 사람들에게 달려 있다.

POSIX Pthread 표준의 구현

  • User-level
  • Kernel-level

표준은 주로 UNIX 운영체제인 Solaris,Linux,MAC OS X등과 같은 환경에서 일반적으로 사용된다. 

 

User Threads

사용자 스레드 관리는 사용자 수준 스레드 라이브러리를 통해 이루어진다. 즉 스레드의 생성 및 관리는 사용자 수준에서 구현된 라이브러리를 사용하여 이루어진다. 

 

  • 라이브러리는 스레드 생성 및 관리를 위한 API를 제공한다. 이 API를 사용하여 스레드를 생성하고 관리할 수 있다.
  • 이 라이브러리는 완전히 사용자 공간에서 실행되며, 커널의 지원이 필요하지 않는다.
  • 사용자 스레드 라이브러리는 운영체제 커널의 지원없이 사용자가 직접 구현하거나 라이브러리를 활용하여 사용자 공간에서 스레드 관리를 한다. 
  • 이 API 함수 호출은 시스템 콜을 발생시키지 않고, 사용자 공간에서 직접 실행된다. 

따라서 스레드 관리와 관련된 작업이 커널 수준에서 처리도지 않고, 사용자 수준에서 처리된다.

 

*스레드를 만들기 위해서 API 함수를 부르는 것 (시스템 콜과 관계없음)

*운영체제는 프로세스 단위로 관리하기 때문에 프로세스만 지원한다.

 

 

 

 

 

JVM에서 스레드를 지원한다

 

 

운영체제는 CPU를 프로세스에게 할당한다고 스케쥴링하면 -> 라이브러리 안에 있는 스케쥴러에서 

어느 스레드에 CPU를 할당할지 결정한다.

스레드에서 Context switching이 일어나기 때문에 스레드의 context를 세이브 하기위한 PCB와 유사한 것이 있어야한다.

 

 

 

Kernel Threads

 

스레드 관리는 커널 수준 스레드 라이브러리를 통해서 지원된다. 즉, 스레드의 생성 및 관리는 운영체제 커널 수준에서 구현된 라이브러리를 사용하여 이루어진다.

  • 이 라이브러리는 스레드 생성 및 관리를 위한 API를 제공하며 이 API를 사용하여 스레드를 관리하고 생성할 수 있다.
  • 또한 이 API함수 호출은 시스템 콜을 발생시킨다,
  • 스레드 관리와 관련된 작업이 커널 수준에서 처리되며, 시스템 콜을 통해 운영 체제 커널에 요청된다. 

*커널이 스레드 단위로 스케줄링을 한다.

*운영체제가 스레드를 나눠주는 거라고 볼 수 있다.

 

 

운영체제가 스레드 단위로 스케줄링한다. 따라서 Ready queue에서 스레드가 줄 서 있는 것이고 이 중에서 하나를 선택하여서 CPU를 할당한다. 

 

PCB에 스레드에 대한 정보를 기록한다.

 

 

Multithreading Models

 

유저 스레드와 커널 스레드 간의 관계 

  • Many-to-One Model 
  • One-to-One Model 
  • Many-to-Many Model

Many-to-One Model 

 

많은 유저 레벨 스레드가 단일 커널 스레드에 연결된다.

  • 스레드 관리는 유저 레벨 스레드 라이브러리에서 수행된다.
  • 단일 커널 스레드는 즉 커널에서 프로세스 하나가 있다고 보는 것이다. 따라서 운영체제는 하나의 프로세스만 존재한다고 보고 스케줄링을 한다. 
  • 유저 레벨에 있는 라이브러리에서 별도의 스케줄링해서 스레드에 CPU를 할당한다. 
  • 만약 스레드가 시스템콜 했을 때 블록된다면  전체 프로세스가 차단된다. 즉 유저 스레드 중 하나가 차단된 상태에 있을 때 해당 사용자 스레드와 매핑된 커널 스레드와 함게 다른 사용자 스레드도 차단될 수 있다. 

 

 

 

*만약 어떤 프로세스안에 있는 하나의 스레드가 시스템 콜 하였을 때 블록된다면 운영체제는 프로세스 전체가 블록됐다고 보고 CPU를 빼앗는다. 

 

운영체제는 프로세스 단위로 스케줄링하기 때문에 멀티코어, CPU가 여러개 있는 시스템이라 하더라도 여러 개의 CPU를 이 여러개의 스레드에 할당에 줄 수 없다. 

 

 

One-to-One Model 

 

유저 레벨에서 스레드가 하나 있을 때 커널 레벨에서도 스레드가 하나 있는 것. (1:1 매칭)

 

  • 각각의 유저레벨 스레드는 커널 레벨 스레드에 매핑된다.
  • 커널이 스레드의 존재를 알고 있다. (프로세스단위로 관리하는 것이 아닌 스레드 단위로 관리한다)

 

 

* 스레드가 시스템 콜하여 블록당했을 때 다른 스레드로 CPU를 할당하면 된다. 따라서 프로세스 입장에서 CPU를 쓰는 시간이 더 늘어날 수 있다. 

 

  • 사용자 수준 스레드를 생성하면 커널 스레드가 생성된다
  • 많은 스레드가 하나의 스레드에 대응하는 Many to one 모델보다 더 많은 동시성을 제공하며, 한 스레드가 블록되는 시스템호출을 수행할 때 다른 스레드가 실행될 수 있다,
  • 멀티 코어 시스템에서 여러 스레드가 병렬로 실행될 수 있으며 프로세스 당 스레드 수는 제한 될 수 있다. 

 

3개의 스레드가 Concurrent하게 돌아간다. 따라서 many to one 모델보다 이득이다. 

하지만 한 프로세스가 생성할 수 있는 스레드의 개수에 대한 제한이 있을 수 있다. 

 

-> 프로세스 하나가 만들어 질 때마다 pcb 한칸을 차지하기 때문에 

 

 

 

Many-to-Many Model

 

Many-to-Many 모델은 Many-to-One 모델과 One-to-One 모델의 절충안이다,

 

Many-to-one 모델: 원하는 만큼 사용자 스레드를 생성할 수 있지만, 커널이 한번에 하나의 프로세스만 스케줄링하므로 동시성을 얻을 수 없다.

One-to-One 모델: 큰 동시성을 얻을 수 있지만, 스레드의 수가 제한된다.

 

스레드 시스템 콜이 블록 됐을 때 커널은 실행하기 위해  다른 스레드를 스케줄링 한다. 

 

Two-level Model

 

Many-to-Many 모델과 유사하지만 사용자 스레드를 커널 스레드에 바인딩할 수 있는 모델이다.

두 가지 모델을 섞어 놓은 것. 

Threading Issues

 

▪ Semantics of fork() and exec() system calls

▪ Thread Cancellation

▪ Signal Handling

▪ Thread Pools

▪ Thread-Local Storage

▪ Scheduler Activations

 

 

Semantics of fork() and exec() system calls

fork() 

-  유닉스에 있는 시스템 콜로 호출하는 스레드를 복제한다. 현재 프로세스를 복제하여 새로운 자식으로 복사본을 만드는 것이다. (자식 프로세스의 각 스레드는 부모 프로세스의 해당 스레드의 복사본)

 

exec()

- 지정된 프로그램으로 전체 프로세스가 대체(교체)된다 

 

fork() 직후에 exec()가 사용되는 경우 

- fork() 직후에 exec()를 호출하는 것은 일반적인 방법으로, 새로운 프로세스를 생성하고 그 이미지를 다른 프로그램으로 교체하기 위해 사용된다.  이것은 일반적으로 자식 프로세스를 만들어 환경을 설정하고, 그 후 새 프로그램을 시작하고자 할 때 사용된다. fork()에 의해 생성된 자식 프로세스는 주로 환경을 설정하는 데 사용되며, 그런 다음 exec()를 사용하여 새로운 프로그램을 시작한다.

* fork()를 사용하여 복사할 때 스레드를 복제할 것인가 전체를 복사할 것인가 -> 스레드 이슈 

 

 

Thread Cancellation

 

돌아가고 있는 스레드를 종료 시키는 방법 

 

스레드가 완료되기 전에 스레드를 종료하는 것

  • 여러 스레드가 이미지를 다운로드 중에 중지 버튼을 누르는 경우
  • 멀티 스레드가 동시에 데이터 베이스를 검색하며, 하나의 스레드가 결과를 반환하는 경우 

두 가지 종료시키는 방법

Asynchronous cancellation(비동기적 취소)

  • 대상 스레드(target thread,종료될 스레드)가 즉시 종료된다
  • 문제가 발생할 수 있다. (프로세스의 경우 상관이 없는데 스레드일 경우 문제가 발생할 수 있다)
  • 다른 스레드와 공유 중인 데이터 업데이트 중에 스레드가 취소된다. 

Deferred cancellation(지연 취소)

  • 대상 스레드가 주기적으로 스스로 종료 여부를 확인한다.( 종료해도 되는지 수시로 체크한다)
  • 대상 스레드는 안전하게 취소될 수 있는 경우에만 종료된다. 

 

 

Signal Handling

 

일종의 인터럽트 

UNIX 시스템에서 특정 이벤트가 발생했음을 프로세스에 알리기 위해 신호(Signal)이 사용된다.

 

인터럽트가 들어오면 인터럽트 서비스 루틴이 호출된다. 인터럽트 종류별로 호출된 함수가 따로따로 지정된다. 

 

시그널이 프로세스에게 전달되면 시그널 핸들러가 호출된다. 

프로세스가 여러개의 스레드로 나눠져있다. 

  • 시그널은 특정 이벤트에 의해 생성된다
  • 시그널은 프로세스로 전달된다
  • 시그널은 처리된다. 

 

여러가지 옵션이 있다. 

 

해당 시그널을 해당 시그널이 적용되는 스레드에 전달

  • 예시) 메모리 엑세스 위반을 발생시킨 스레드

해당 시그널을 프로세스 내의 모든 스레드에 전달

  • 예시) 사용자가 ctrl c를 누른 경우

해당 시그널을 프로세스 내의 특정 스레드에 전달

  • 예시) kill(pid,,signal) vs ptherad_kill(tid,signal) 이때 kill은  시그널을 죽이라는 뜻이 아니라 시그널을 보내라는 뜻이다

프로세스에 전송된 모든 신호를 수신할 특정 스레드를 지정한다. 

 

 

 

Thread Pools 

고객의 각 요청마다 새로운 스레드가 생성되어 요청을 처리한다

이때 동시에 너무 많은 요청이 들어오는 상황에선 어떻게 처리해야할까? 

 

 

 

Thread Pools

프로세스 시작 시 작업을 기다리는 스레드들을 가지고 있는  풀을 만든다. 

  • 스레드가 필요한 경우 풀에서 하나 할당한다
  • 풀이 비어있으면 사용 가능한 스레드가 생길 때까지 기다린다.
  • 스레드가 작업을 완료하면 풀로 돌아간다

장점

  • 일반적으로 새로운 스레드를 생성하는 것보다 이미 존재하는 스레드로 요청을 처리하는 것이 더 빠르다 
  • 응용 프로그램이 스레드 수를 풀의 크기와 바인딩하여 관리할 수 있다. 

스레드 풀 크기는 다음과 같은 경험에 의한 기준을 기반으로 설정된다

  • CPU 개수
  • 주 메모리 양
  • 예상되는 동시 사용자수 

 

Thread-Local Storage

프로세스의 스레드들은 프로세스의 데이터를 공유한다. 데이터 공유로 인해 멀티스레드 프로그래밍의 이점을 얻을 수 있다. 

 

 

그러나 일부 상황에서 각 스레드가 고유한 데이터 복사본을 필요로 할 수 있다. 

Therad-Local-Storage (TLS) 가 이를 가능하게 한다.

  • 트렌젝션 처리 시스템에서 각 트렌젝션을 별도의 스레드에서 실행해야한다.

TLS는 지역변수와 다르다 

  • 지역 변수는 단일 함수 호출동안에만 사용가능하지만 TLS는 함수 호출간에도 사용 가능하다.
  • TLS는 스레드 내에서 전역역할을 하는 형식 또는 메모리 공간 
  • TLS는 각 스레드에 대해 고유하다

 

대부분의 스레드 라이브러리는 TLS에 대한 어떤 형태의 지원을 제공한다. 

 

 

Scheduler Activations

 

Many-to-may 모델은 커널과 스레드 라이브러리 간에 통신이 필요하다. 

* 유저레벨에서 스레드가 만들어지고 커널 레벨에서도 스레드가 만들어진다. 여러 유저 스레드에 더 적거나 같은 수의 커널 스레드가 대응한다.

 

통신을 위해, 경량 프로세스 (Lightweight Process, LWP)를 사용한다. 사용자와 커널 스레드 간의 중간 역할을 한다. 

 

사용자 스레드는 LWP에 연결된다. 

  • 사용자는 스레드 라이브러리에서보면, LWP는 사용자 스레드가 실행되는 가상 프로세서로 나타난다. 따라서 LWP위에서 유저레벨 스레드가 돌아가고 있다고 간주한다. 

각 LWP는 커널 스레드에 연결된다

  • 커널 스레드는 운영체제에 의해 스케줄링되고 물리적 프로세스에 의해서 실행된다. 

커널 스레드가 차단되면, LWP도 차단되고 마지막으로 LWP에 연결된 사용자 스레드도 차단된다. 

 

 

유저 스레드 라이브러리에서 LWP를 하나의 프로세서로 간주한다.

 

 

이때 유저레벨에선 가상의 프로세서가 3개 있다고 본다. LWP는 유저레벨에서 만들어진 스레드를 왔다갔다하면서 실행된다. 

커널 레밸에선 스레드가 3개가 있다고 생각한다. 운영체제는 6개의 스레드가 만들어졌다고 보는 것이 아니라 3개의 스레드가 만들어졌다고 보고 스케줄링한다. 

 

유저레벨 스레드가 시스템 콜했는데 블록 된다면 유저 레벨 스레드가 돌아가는 LWP도 블록된다.

 

이 스레드들이 가상의 프로세서 위에서 돌아가고있다. 6개의 유저 레벨 스레드는 유저 레벨에 있는 라이브러리가  스케줄링한다. 커널은 3개의 스레드를 스케줄링한다.

 

LWP위에서 돌아가는 스레드가 시스템 콜했는데 블록되어버리면 운영체제가 이 스레드에 할당됐던 CPU를 회수하고 다른스레드에 할당한다. 이러면서 원래 할당되어있던 CPU의 개수가 줄어들고 커널의 개수도 줄어든다. 따라서 이 그룹이 줄어디지 않고 유지되기 하기위해서 UPCALL을 사용한다, 

 

* LWP 개수와 커널 스레드 개수는 일정하게 유지가 되어야한다.

 

 

Scheduler Activations

 

사용자 스레드 라이브러리와 커널간의 통신을 위한 체계이다

Upcall: 커널에서 스레드 라이브러리로의 통신 메커니즘으로 어플리케이션에 특정 이벤트에 대한 정보를 제공하는데 사용된다. (운영체제에서 위에있는 라이브러리쪽으로 콜)

Upcall Handler: 업콜을 처리하는 사용자 스레드 라이브러리 루틴으로 LWP, 가상 프로세스에서 실행된다.

 

 

 

 

어플리케이션 스레드가 블록되기 직전, 커널은 해당 특정 스레드를 식별하는 Upcall를 수행한다. 

 

커널은 어플리케이션에 새로운 LWP를 할당하고 여기서 Upcall handler가 실행된다.

  • 차단된 스레드의 상태를 저장한다,
  • 차단된 스레드가 실행중인 LWP를 반납한다
  • 새로운 LWP에서 다른 스레드를 스케줄링하여 실행시킨다. 

 

차단된 스레드가 기다리던 이벤트가 발생하기 직전, 커널은 이전에 차단된 스레드가 이제 실행 가능한 상태임을 알리기 위해 또 다른 Upcall를 수행한다. (블록상태 해제를 알려주기 위해서)

 

 

이 이벤트에 대한 Upcall handler 또한 LWP가 필요하다. 커널은 이벤트를 처리하기 위해 어플리케이션에 새로운 LWP를 할당한다. Upcall handler는 새로운 LWP(경량 프로세스)에서 실행된다. 핸들러는 이전에 차단되었던 스레드를 포함하여 다른 스레드를 스케줄링한다.

 

728x90

'💾 lecture > 운영체제' 카테고리의 다른 글

[OS] Chapter 6 : Process Synchronization  (0) 2023.10.28
[OS] Chapter 5: CPU Scheduling  (0) 2023.10.18
[OS] Chapter 3: Processes  (0) 2023.10.13
[OS] Chapter 2: Operating-System Structures  (0) 2023.10.09
[OS]chapter 1: Introduction  (0) 2023.10.08