Do it! 코틀린 프로그래밍 [셋째마당, 코틀린 표준 라이브러리의 활용] 학습
✏️1. 동시성 프로그래밍✏️
- 동기적(synchronous) 프로그래밍
- 순서대로 작업을 수행하여 1개의 루틴을 완료한 후 다른 루틴을 실행하는 방식
- 코드의 복잡도가 낮음
- 코드의 여러 구간에서 요청된 작업이 마무리가 될 때 까지 멈춰 있는 현상 블로킹(Blocking)이 발생
- 비동기적(Asynchronous) 프로그래밍
- 여러 개의 루틴이 선행 작업의 순서나 완료 여부와 상관없이 실행되는 방식
- RxJava, Reactive와 같은 서드파티(third-party) 라이브러리에서 제공
- 코틀린에서는 코루틴(Coroutine)을 서드파티가 아닌 기본으로 제공
- 다중작업을 위해 스레드와 같은 비동기 코드를 작성하면 코드가 복잡해짐
1-1. 블로킹과 넌블로킹
📌 블로킹 동작

- 입출력 과정(I/O)인 읽기나 쓰기(R/W)과정이 수행될 때 태스크 A의 코드가 더이상 진행되지 않고 내부 메모리 영역에서 해당 작업이 마무리 될 때까지 코드가 멈춤 -> 이런 상황을 코드가 '블로킹'하고 있다고 함
- 태스크 A가 블로킹하는 동안 운영체제의 스케줄링 정책에 따라 우선순위가 낮은 또 다른 태스크 B가 실행될 수 있음
- 태스크 A의 실행이 재개되면 우선순위가 낮은 B는 블로킹하고 태스크 A가 종료되면 태스크 B가 재개
📌 넌블로킹 동작

- 입출력 요청을 하더라도 운영체제에 의해 EAGAIN과 같은 시그널을 태스크 A가 받아서 실행을 재개
- 태스크 A는 다른 루틴을 수행하다가 내부적으로 입출력 완료 시그널을 받은 후 콜백 루틴(Callback Routine) 등을 호출해 완료된 이후의 일을 처리
- 태스크 A를 수행하는 도중에 또 다른 태스크 B가 생성될 수 있는데 이때 태스크 A와 B는 비동기적으로 수행될 수 있음
- A와 B의 실행 시점 운영체제의 스케줄링 기법에 의해 결정되어 프로그래머는 어떤것이 어떻게 수행될 지 알 수 없음
- 태스크 A와 B는 프로세서 코어 수에 따라 동시에 수행될 수도 있고 2개의 태스크를 자주 교환에 동시에 수행되는 것처럼 보이게 할 수 있음
- 여러개의 코어가 태스크를 동시에 수행하는 것을 병행 수행(Concurrency)라고 함
1-2. 프로세스와 스레드
📌 프로세스 (Process)
- 코드, 데이터, 열린 파일의 식별자, 동적 할당 영역, 스택등을 가진 큰 실행 단위
-> 이러한 정보를 문맥(Context)라고 함
- 실행되는 메모리, 스택, 열린 파일 등을 모두 포함
-> 프로세스 간 문맥 교환(Context-Switching)을 할 때 많은 비용이 들어감
📌스레드 (Thread)
- 프로세스의 코드, 데이터, 열린 파일 등을 고융하는 작은 독립된 실행 단위
- 자신의 스택만 독립적으로 가지고 나머지는 대부분 스레드끼리 공유
-> 문맥 교환 비용이 낮아 프로그래밍에서 많이 사용
- 여러 개의 스레드를 구성하면 코드가 복잡해짐
📌 문맥교환(Context-Switching)
- 하나의 프로세스나 스레드가 CPU를 사용하고 있는 상태에서 다른 프로세스나 스레드가 CPU를 사용하도록 하기 위해, 이전의 프로세스의 상태(문맥)를 보관하고 새로운 프로세스의 상태를 적재하는 과정
- 프로세스와 프로세스는 완전히 독립적이기 때문에 프로세스 간의 실행을 전환하려면 문맥을 저장해 두었다가 새로운 프로세스의 문맥을 불러들이는 과정 필요
- 스레드는 레지스터와 스택만 독립적으로 가지고 있고 대부분의 문맥은 프로세스 안에서 공유
-> 프로세스 간 전환보다 훨씬 빠르고 운영체제 입장에서 비용이 낮음
📌 스레드 생성하기
class SimpleThread: Thread() {
override fun run() {
println(Thread.currentThread())
}
}
class SimpleRunnable: Runnable {
override fun run() {
println(Thread.currentThread())
}
}
fun main() {
SimpleThread().start()
val runnable = SimpleRunnable()
Thread(runnable).start()
}
-> (1)번에서는 Thread 이외의 클래스를 상속할 수 없음
-> (2)번에서는 Runnable 인터페이스를 구현한 것이므로 다른 클래스를 상속할 수 있음
⬇️ 익명 객체를 사용하기
object: Thread() {
override fun run() {
println(Thread.currentThread())
}
}.start()