https://picayune-handle-420.notion.site/10-1ac28b2d422c47c0a4b737600b96c956
https://pages.cs.wisc.edu/~remzi/OSTEP/Korean/
thread-safe한 자료구조 : 여러 스레드가 같은 자원을 사용하더라도 race condition이 발생하지 않는 자료구조.
fine grained lock : 모든 인덱스마다 lock을 걸어서 사용
lock striping : 길이가 L인 Lock Array를 사용해 값에 1:n 관계로 매핑해 사용
프로세스
- OS에서 메모리 영역을 할당받아 실행되는 독립적인 프로그램 인스턴스이다.
- 하나의 프로세스는 별도의 메모리 공간을 할당 받아 사용한다.
프로세스 상태
- 하나의 cpu가 여러 개의 프로세스들을 실행시키기 위해 프로세스들을 상태를 두어 관리(멀티 코어 환경에서도 동일)
프로세스는 기본적으로 4가지의 상태를 가진다.
- ready : 프로세스가 생성되어 실행 되고의 초기 상태
- running : OS가 ready 상태의 프로세스 중 하나를 실행
- waiting : 실행 중 파일을 읽거나 I/O 등의 작업이 발생했을 때 다음 프로세스를 작업하기 위해 기존 프로세스를 waiting 상태로 만듦. 작업이 다 끝난 waiting 상태의 프로세스는 ready 상태로 바뀜.
- terminated : 프로세스의 모든 작업이 끝나면 exit 되어 terminated상태로 전환.
프로세스 제어
OS는 프로세스가 시작되고 종료될 때까지 프로세스의 상태를 PCB(process control block)를 생성해서 관리한다.
PCB에는 스케줄링 정보, 실행되고 있는 주기억 장치의 주소 정보 등을 포함하고 있다.
여러개의 프로세스가 생성되면 여러개의 PCB가 1:1로 생성되어, 큐에 linked list 형태로 저장되어 순서대로 실행된다.
PCB는 메모리상에서 ready 큐나 waiting 큐에 위치하다가 running이 되면 cpu가 PCB 정보를 가져와서 실행한다.
실행중인 프로세스가 교체되는 경우
- 실행중인 프로세스가 끝났을 때 (terminated)
- 실행중인 프로세스가 혼자 너무 오래 일하고 있을 때 (ready 상태로 전환)
- 프로세스 실행 중 파일입력이나 I/O 처리 등의 다른 일이 진행될 때는 waiting 상태로 전환
- context switching 발생
- 대기 상태가 끝나면 다시 ready 큐로 가서 실행될 순서를 기다린다.
- Context Switching이란?
cpu에서 여러 프로세스들을 돌아가면서 작업을 처리하는데
동작 중인 프로세스를 대기시키고 해당 프로세스의 상태(context)를 보관하고,
대기하고 있던 다음 순서의 프로세스가 동작하면서 이전에 보관했던 프로세스의 상태를 복구(switching)하는 작업을 말한다.
프로세스 스케줄링 관리
ready 큐에서 어떤 프로세스를 먼저 실행할지 결정하는 알고리즘.
OS는 프로세스를 관리하는 몇 가지 방법을 가지고 있다.
스레드
스레드는 각각 stack을 할당 받아 실행되는 독립적인 단위 이다.
경량 프로세스라고도 하며, 프로세스에서 실행 제어만 분리해서 처리하는 단위이다.
멀티 스레드인 경우, 같은 그룹의 스레드와 stack을 제외한 나머지 자원을 공유한다.
한 스레드가 프로세스 자원을 변경하면 다른 이웃 스레드는 그 변경 결과를 즉시 볼 수 있다.
프로세스는 하나 이상의 스레드를 가지고 각 스레드는 다음 같은 동작을 담당한다.
- 스레드 실행에 대한 상태 관리
- 실행을 위한 별도 스택
- 지역 변수와 스레드 특정 데이터를 저장하는 데이터 저장소
- 프로세스의 메모리와 자원에 대한 접근을 기록하는 컨텍스트 정보
특징
- 스레드를 사용하면 사용자에 대한 응답성을 증가시킬 수 있다.
- 프로세스 자원과 메모리를 공유할 수 있다.
- 자원을 공유하기 때문에 경제적이다.
- 다중 프로세서와 다중 스레드를 혼합해서 병렬 실행이 가능하다.
- 현대 cpu들은 다중스레드를 처리하는 하드웨어 로직을 가지고 있다.
Thread pool
스레드를 매번 생성하지 않고 재사용하는 방식.
OS입장에선 매번 메모리를 할당하거나 스레드를 생성하는일은 비용이 큰 일이다.
동작 방식
- task queue에 task가 추가 됨
- thread pool이 task를 가져와서 thread에 작업 할당
- 각 thread는 작업 진행
- 완료된 thread는 완료 상태로 존재.
참고) 브라우저의 process와 thread
- 브라우저는 UI를 처리하는 UI Process와 렌더링을 담당하는 Renderer Process가 존재.
- 탭이 하나 생성되면?
- 별도의 process를 생성하거나 별도의 thread를 생성할 수 있음
다시 말해, Multi Processing 이거나 Multi Threading
- 각 탭 안에는 Main Thread와 I/O Thread 등의 멀티 쓰레딩 방식으로 동작.
프로세스와 스레드의 동작 방식
cpu는 작업의 효율성을 증가시키기 위해 멀티 스레딩 방식을 사용한다.
가령 cpu가 한 task가 끝날 때 까지 대기한다고 하면,
task가 동작하던 중 I/O의 개입이 발생했을 때 I/O가 끝날 때 까지 기다려야 하는 불상사가 발생할 수 있다.
이러한 cpu의 시간 낭비를 막기 위해 context switching을 통해 여러 task를 번갈아 시행한다.
context switcing이 일어나면 현재 동작하고 있는 프로세스의 정보를 PCB에 저장하고, 다음에 시행할 프로세스의 정보를 PCB에서 가져와 메모리에 적재한다.
이 때, 스레드를 사용한다면 기존의 힙과 데이터 영역 등 일정 메모리 영역의 내용을 공유하기 때문에, 데이터를 불러오는 작업의 오버헤드를 줄일 수 있다.
Background Thread
1. Main Thread
- Main Thread는 오직 한개 뿐이며 나머지는 모두 Background Thread
- 대부분의 코드는 Main Thread에서 실행.
- interface thread라고도 불리는데 유저가 interface에 접근하면
이벤트가 main thread에 전달돼 프로그램이 반응.
interface와 관련된 모든 코드는 반드시 main thread에 작성되어야 함.
2. Background Thread(= Global Thread)
- iOS의 framework들은 background에서 구동 됨
- 화면에 나타나지 않는 대부분의 작업들은 background thread에서 실행되는데 main thread에서 delegate method와 callback 함수로 호출하여 컨트롤하는 것이 일반적.
3. Background thread를 명시적으로 사용하는 경우
- code 수행에 시간이 지나치게 오래 걸리는 경우
실행에 시간이 많이 걸리는 코드를 main thread에 구현한다면
UI가 마비되는 상황이 발생할 수 있다.
이러한 코드는 의도적으로 background thread에 구현해준다.
4. Background Thread의 위험성
- 코드는 원칙적으로 한줄씩 차례대로 실행되지만 background thread가 실행되면 이 원리가 깨져 작업의 실행 완료 순서를 장담할 수 없게 된다.
- 객체의 lifetime과 상관없이 동작해 이미 deinit이 된 객체에 접근하여 app에 crash가 발생할 수도 있고,
deinit이 되어야 하는 객체를 deinit이 되지 못하게 만들 수도 있다.
앞선 4번의 위험들을 관리할 수 있는 tool
- Debug navigator: thread를 구분하여 대기중인 call이 enqueue되는 시점을 알려줍니다.
- NSLog와 os_log: 우측 하단 console창에 호출된 thread를 식별할 수 있는 번호를 print해줍니다.
- Time Profiler: 다른 thread의 활동을 기록해줍니다.
- Thread Sanitizer: 발생할 수 있는 문제를 감지합니다.
iOS에서의 Multi Threading
iOS에서는 main thread에 몰린 task를 queue에 보내기만 하면 background thread를 적절히 생성하여 분배해준다.
이를 개발자가 직접 하려면 어떻게 해야 할까?
- 직접 Thread를 생성하고 관리하기
- GCD 활용
- NSOperation 활용
직접 Thread를 생성하고 관리하기
- 개발자가 직접 Thread 클래스로부터 생성하여 사용하는 방법이다.
- 예제 코드
import Foundation
class ThreadTest{
func createThread(){
let thread = Thread(target: self, selector: #selector(print), object: nil)
}
@objc func print() {
Swift.print("Thread running")
}
}
}
let threadClass = ThreadTest()
threadClass.createThread()
threadClass.start()
GCD(Grand Central Dispatch)
- 사용자가 DispatchQueue에 작업을 추가하면 GCD는 작업에 맞는 스레드를 자동으로 생성해서 실행하고, 작업이 종료되면 스레드를 제거한다.
- C언어 기반의 저수준 API로 가볍고 성능이 뛰어나다.
- Closure로 구현되어 있어 가독성과 사용성이 좋다.
- 작업 취소, 작업 재사용 등의 기능은 지원하지 않는다.
NSOperation
- Object-C 기반의 고수준 API.
- 내부적으로는 C언어로 구현된 GCD를 고수준 언어로 Wrapping한 것으로 GCD보다 무겁다.
- 사용자가 OperationQueue에 작업을 넣으면 스레드를 적절히 생성해 분배해준다.
- 작업 취소, 작업 재사용 등 GCD에 비해 많은 기능을 제공한다.
추가 학습
ios 플랫폼에서 정확도 높은 타이머를 구현하기 위한 여러 방식
멀티스레드가 공용 리소스에 접근할 때 임계구역을 다루는 방식(Semaphore, Mutex, Monitor등)과, 어떤 경우에 사용하는지.
프로세스를 관리하는 자료구조
운영체제에서 사용하는 작업 스케줄링 알고리즘
메모리 - 폰 노이만 구조와 하버드 구조
프로세스나 스레드가 생성되고 종료될 때까지 스케줄러에 따라서 어떤 상태로 변화하는지
참고 : https://velog.io/@yongchul
https://hyunndyblog.tistory.com