[코틀린 동시성 프로그래밍] 1장 Hello, Concurrent World! -1

Sdoubleu·2023년 1월 11일
0

코틀린 동시성

목록 보기
1/10
post-thumbnail

1장에서 다루는 주제

  • 프로세스, 스레드, 코루틴 및 이들의 관계
  • 동시성 소개
  • 동시성 vs 병렬성
  • CPU 바운드 및 I/O 바운드 알고리즘(CPU 집중 및 I/O 집중 알고리즘)
  • 동시성이 망설여지는 이유
  • 코틀린의 동시성
  • 개념과 용어

프로세스, 스레드, 코루틴

애플리케이션을 시작할 때 운영체제는 프로세스를 생성하고 여기에 스레드를 연결한 다음, 메인 스레드로 알려진 해당 스레드를 시작

동시성을 이해하고 구현하는데 반드시 필요한 요소
-> 프로세스, 스레드 및 코루틴 간의 관계


프로세스

프로세스는 실행 중인 애플리케이션의 인스턴스
애플리케이션이 시작될 때마다 프로세스 시작
프로세스는 상태를 갖음
리소스를 여는 핸들, 프로세스 ID, 데이터, 네트워크 연결 등은 프로세스 상태의 일부이며 해당프로레스 내부의 스레드가 액세스 할 수 있음

애플리케이션은 여러 프로세스로 구성될 수 있음


스레드

실행 스레드는 프로세스가 실행할 일련의 명령을 포함
따라서 프로세스는 최소한 하나의 스레드를 포함하며 이 스레드는 애플리케이션의 진입점(entry point)을 실행하기 위해 생성
진입점은 애플리케이션의 main() 함수이며 메인 스레드라 함
프로세스의 라이프 사이클과 밀접하게 연관
메인 스레드가 끝나면 프로세스의 다른 스레드와 상관없이 프로세스가 종료

ex)
fun main(args: Array<String>) {
	doWork()
}
// doWork() 종료되면 애플리케이션의 실행 종료

각 스레드는 스레드가 속한 프로세스에 포함된 리소스를 액세스하고 수정할 수 있지만 스레드 로컬 스토리지라는 자체 저장소도 갖고 있음

스레드 안에서 명령은 한 번에 하나씩 실행
-> 스레드가 블록(block)되면 블록이 끝날 때까지 같은 스레드에서
다른 명령 실행❌

그러나 많은 스레드가 같은 프로세스에서 생성될 수 있으며 서로 통신 가능✔️

사용자 경험(UX)에 부정적인 영향을 미칠 수 있는 스레드는 블로킹❌
-> 블로킹할 때는 블로킹 작업을 별도의 전용 스레드에 할당!

GUI app 에서는 UI 스레드가 있음
UI 스레드는 사용자 인터페이스를 업데이트하고 사용자와 app 간의 상호작용을 리스닝

  • GUI app은 app의 응답성을 항상 유지하기 위해서 UI 스레드 블록 ❌

코틀린이 동시성을 구현한 방식을 보면 직접 스레드를 시작하거나 중지할 필요 없음
한 두줄의 코드로 코틀린이 특정 스레드나 스레드 풀을 생성해서 코루틴을 실행하도록 지시하기만 하면 됨
스레드와 관련된 나머지 처리는 프레임워크에 의해 수행


코루틴

코루틴을 경량 스레드라고도 함
코루틴은 스레드와 비슷한 라이프 사이클을 가지고 있음
코루틴은 스레드 안에서 실행
스레드 하나에 많은 코루틴이 있을 수 있지만 주어진 시간에 하나의 스레드에서 하나의 명령만이 실행될 수 있음

  • 스레드와 코루틴의 가장 큰 차이점
    -> 코루틴이 비교적 적은 비용으로 생성할 수 있고, 수천 개의 코루틴도 쉽게 생성 가능
ex)
suspend fun createCoroutines(amount: Int) {
	val jobs = ArrayList<Job>()
    for (i in 1..amount) {
    	jobs += launch {
        	delay(1000)
		}
	}
    jobs.forEach {
    	it.join()
	}
}

amount에 지정된 수만큼 코루틴을 생성해 각 
코루틴을 1초간 지연시킨 후 모든 코루틴이 종료될 때까지 기다렸다가 반환

-------------------------------------------------------------
fun main(args: Array<String>) = runBlocking {
	val time = measureTimeMillis {
    	createCoroutines(10_000)
    }
    println("Took $time ms")
}

코틀린은 고정된 크기의 스레드 풀을 사용하고 코루틴을 스레드들에 배포하기 때문에 실행 시간이 매우 적게 증가
-> 수 천개의 코루틴을 추가하는 것은 거의 영향❌

코루틴이 일시 중단되는 동안(이 경우에는 delay()를 호출했기 때문)
실행 중인 스레드는 다른 코루틴을 실행하는데 사용되며
코루틴은 시작 또는 재개될 준비 상태가 됨

🛠️ suspend, runBlocking에 대해 조사 필요

easureTimeMillis()는 코드 블록을 갖는 인라인 함수이며 실행 시간을
밀리초(ms)로 반환

fun main(args: Array<String>) = runBlocking {
	println("${Thread.activeCount()} threads active at the start")
    
    val time = measureTimeMillis {
    	createCoroutines(10_000)
    }
    println("${Thread.activeCount()} threads active at the end")
    println("Took $time ms")
}
  • Thread 클래스의 activeCount() 메소드를 활용하면
    활성화된 스레스 수를 알 수 있음

  • 앱을 시작할 때 이미 두 개의 스레드가 있음을 주의! 🔥

코루틴이 특정 스레드 안에서 실행되더라도 스레드와 묶이지 않는다는 점
코루틴 일부를 특정 스레드에서 실행하고, 실행을 중지한 다음 나중에 다른 스레드에서 계속 실행하는 것이 가능 👍

ex) createCoroutines()에 amount를 3으로 하고, launch() 블록을
다음과 같이 변경

suspend fun createCoroutines(amount: Int) {
	val jobs = ArrayList<Job>()
    for (i in 1..amount) {
    	jobs += launch {
        	println("Started $i in ${Thread.currentThread().name}")
        	delay(1000)
        	println("Finished $i in ${Thread.currentThread().name}")
    	}
	}
	jobs.forEach {
		it.join()
	}
}

스레드는 한 번에 하나의 코루틴만 실행할 수 있기 때문에
프레임워크가 필요에 따라 코루틴을 스레드들 사이에 옮기는 역할을 함

⭐내용정리

  • 앱은 하나 이상의 프로세스로 구성
  • 각 프로세스가 하나 이상의 스레드를 갖고 있음
  • 스레드를 블록한다는 것
    -> 스레드에서 코드 실행 중지를 의미
    -> 사용자와 상호작용하는 스레드는 블록 ❌❌

동시성은 앱이 동시에 한 개 이상의 스레드에서 실행될 때 발생
동시성이 발생하려면 두 개 이상의 스레드가 생성되어야 함
앱이 제대로 작동하려면 이런 스레드 간의 동기화가 필요!


동시성에 대해

  • 올바른 동시성 코드는 결정론적인 결과를 갖지만
    실행 순서에서는 약간의 가변성을 허용하는 코드
    코드의 서로 다른 부분이 어느 정도 독립성이 있어야 하고 약간의 조정도 필요
  • 동시성을 이해하는 가장 좋은 방법
    -> 순차적인 코드(비동시성 코드)와 동시성 코드를 비교
fun getProfile(id: Int) : Profile {
	val basciUserInfo = getUserInfo(id)
    val contactInfo = getContactInfo(id)
   
    return createProfile(basicUserInfo, contactInfo)
}
  • getProfile의 타임라인
    getUserInfo --> getContactInfo --> createProfile
    ------------------->getProfile --------------------->
  • 순차 코드의 장점
    -> 정확한 실행 순서를 쉽게 알수 있음
    -> 예측하지 못한 일이 벌어지지 않음

  • 순차 코드의 두 가지 큰 문제점

  1. 동시성 코드에 비해 성능 저하 가능성 존재
  2. 코드가 실행되는 하드웨어를 제대로 활용하지 못할 수 있음

getUserInfo와 getContactInfo는 서로 의존하지 않아 보이므로 동시에 호출해서 getProfile의 실행 시간을 절반으로 줄일 수 있음

getProfile의 동시성 구현

suspend fun getProfile(id: Int) {
	val basicUserInfo = asyncGetUserInfo(Id)
    val contactInfo = asyncgetContactInfo(Id)

    createProfile(basicUserInfo.await(), contactInfo.await())
}

asyncGetUserInfo() 와 asyncgetContactInfo()는 서로 다른 스레드에서 실행되도록 작성했기 때문에 동시성이라고 함

업데이트 된 getprofile에 대한 동시성 타임라인

asyncGetUserInfo() -----------> createProfile() ------->
asyncgetContactInfo() --------->
---------------------> getProfile ------------------------>

getProfile의 동시성 구현 버전은 순차적 수현보다 두 배 빠르게 수행될 수 있지만 실행할 때 약간의 가변성이 존재

createProfile()을 호출할 때 두 개의 wait() 호출 하는 이유
-> 두 함수가 모두 완료될 때 까지 getProfile()의 실행을 일시 중단하는 것

어떤 동시성 호출이 먼저 종료되는지에 관계없이 getProfile()의 결과가 결정론적임을 보장


동시성은 병렬성이 아니다

getProfile의 비동시성(순차적) 예제

fun getProfile(id: Int) : Profile {
	val basciUserInfo = getUserInfo(id)
    val contactInfo = getContactInfo(id)
   
    return createProfile(basicUserInfo, contactInfo)
}

-> 두 함수의 실행시간이 겹치지 않음
-> getContactInfo()의 실행은 항상 getUserInfo()가 종료된 후 실행

getProfile의 동시성 구현

suspend fun getProfile(id: Int) {
	val basicUserInfo = asyncGetUserInfo(Id)
    val contactInfo = asyncgetContactInfo(Id)

    createProfile(basicUserInfo.await(), contactInfo.await())
}

병렬적 실행을 위한 타임라인은 위의 동시성 타임라인과 정확히 같아 보일 것

동시성과 병렬성의 차이점

  • 같은 프로세스 안에서 서로 다른 명령 집합의 타임라인이 겹칠 때 동시성이 발생한다는 점

  • 동시성은 정확히 같은 시점에 실행되는지 여부와는 상관 ❌

다음 다이어그램은 단일 코어에서 발생한 동시성을 나타냄

저수준의 동시성 표현

-> 동시성이지만 병렬은 아님
-> 단일 처리 장치는 X와 Y 스레드 사이에 교차 배치
-> 두 개의 전체 일정이 겹치지만 지정된 시점에 둘 중 하나만 실행

🔥 반면에 병렬 실행은 두 스레드가 정확히 같은 시점에 실행될 때만 발생

다음 다이어그램은 두 개의 프로세스 유닛을 사용해 각각 독릭적인 스레드를 실행하는 동시성 코드병렬로 실행
스레드 X와 Y의 타임라인이 겹칠 뿐 아니라, 정확히 같은 시점에서 실행!

저수준의 병렬 표현

⭐내용정리

  • 동시성은 두 개 이상의 알고리즘의 실행 시간이 겹칠 때 발생 !

  • 중첩이 발생하려면 두 개 이상의 실행 스레드가 필요
    -> 이러한 스레드들이 단일 코어에서 실행되면 병렬 이 아니라 동시에 실행되는데, 단일 코어가 서로 다른 스레드의 인스트럭션을 교차 배치해서, 스레드들의 실행을 효율적으로 겹쳐서 실행

  • 병렬은 두 개의 알고리즘이 정확히 같은 시점에 실행될 때 발생
    -> 가능하게하려면 2개 이상의 코어와 2개 이상의 스레드가 있어야 각 코어가 동시에 스레드의 인스트럭션을 실행할 수 있음.

  • 병렬동시성을 의미하지만 동시성병렬성이 없어도 발생할 수 있다는 점을 유의

profile
개발자희망자

0개의 댓글