[Kotlin] Ch6-1. 동시성 프로그래밍

leeeha·2022년 9월 7일
0

코틀린

목록 보기
27/28
post-thumbnail

출처: https://www.boostcourse.org/mo234/lecture/154327?isDesc=false

동시성 프로그래밍

동기 (synchronous)

  • 순서대로 작업을 수행하여 하나의 루틴을 완료한 뒤에 다른 루틴을 실행하는 방식
  • 다양한 기능이 한꺼번에 일어나는 다중 실행 환경에서는 성능 상의 제약이 발생할 수 있음. ex) UI와 데이터 다운로드를 동시에 처리해야 하는 경우

비동기 (asynchronous)

  • 다양한 기능을 동시에 수행할 수 있는 방식
  • 전통적인 스레드를 이용하거나 RxJava, Reactive와 같은 third-party 라이브러리에서 제공
  • 코틀린에서는 코루틴을 기본으로 제공

코루틴 (coroutine)

  • 먼저 하나의 개별적인 작업을 루틴이라고 부르는데, 여러 개의 루틴들이 협력(co)한다는 의미로 만들어진 합성어
  • 코틀린의 코루틴을 사용하면, non-blocking 또는 비동기 코드를 마치 일반적인 동기 코드처럼 쉽게 작성하면서도 비동기 효과를 낼 수 있다.

용어 정리

[blocking] Task A와 B가 싱크를 맞춘다고 보면 된다. A의 작업이 완료되고 나서 B가 실행되고, B의 작업이 완료되고 나서 A가 실행된다.

[non-blocking] 아까와는 다르게 A의 작업이 완료되지 않더라도 블로킹 되지 않고 B가 실행될 수 있다. 이때 A와 B는 OS 스케줄러에 의해 시분할 되어 동시에 실행되는 것처럼 보인다. (코어가 여러 개일 경우에는 서로 다른 코어에서 A와 B가 실제로 동시에 실행된다.)

프로세스와 스레드

  • 보통 태스크(task)큰 실행 단위인 프로세스좀 더 작은 실행 단위인 스레드로 생각할 수 있다.
  • 프로세스는 실행되는 메모리, 스택, 열린 파일 등을 모두 포함하기 때문에 프로세스 간 문맥 교환을 하는 데 들어가는 비용이 크다.
  • 스레드는 자신의 스택만 독립적으로 가지고, 나머지는 대부분 공유하므로 문맥 교환 비용이 낮아서 프로그래밍에서 많이 사용된다. 단, 여러 개의 스레드를 구성하면 코드가 복잡해져서 디버깅이 어려울 수 있다.

코루틴

  • 코틀린의 코루틴 개념을 사용하면, 전통적인 스레드를 만들지 않고도 상위 레벨에서 좀 더 쉽게 여러 개의 협력형 태스크들을 만들어낼 수 있다.
  • 스레드와 달리, 문맥 교환이 없고 최적화된 비동기 함수를 통해 비선점형으로 작동하는 협력형 멀티태스킹을 구현할 수 있다.

문맥 교환 (Context-Switch)

하나의 프로세스나 스레드가 CPU를 사용하고 있는 상태에서 다른 프로세스나 스레드가 CPU를 사용하도록 만들기 위해, 이전의 프로세스 상태를 보관하고 새로운 프로세스 상태를 적재하는 과정

스레드 생성

package chap06.section1

// 1. 클래스 상속
class SimpleThread: Thread() {
    override fun run(){
        println("Class Thread ${currentThread()}")
    }
}

// 2. 인터페이스 구현
class SimpleRunnable: Runnable {
    override fun run() {
        println("Interface Thread ${Thread.currentThread()}")
    }
}

fun main() {
    val thread = SimpleThread()
    thread.start() // 메인 스레드와는 별도의 스레드 실행 

    val runnable = SimpleRunnable()
    val thread2 = Thread(runnable)
    thread2.start()

    // 3. 익명객체 생성과 동시에 스레드 실행 
    object: Thread(){
        override fun run(){
            println("object Thread: ${currentThread()}")
        }
    }.start() 

    // 4. 람다식 사용 
    Thread {
        println("Lambda Thread: ${Thread.currentThread()}")
    }.start()
}

Class Thread Thread[Thread-0,5,main]
Interface Thread Thread[Thread-1,5,main]
object Thread: Thread[Thread-2,5,main]
Lambda Thread: Thread[Thread-3,5,main]

package chap06.section1

fun thread(start: Boolean = true, isDaemon: Boolean = false,
                contextClassLoader: ClassLoader? = null, name: String? = null,
                priority: Int = -1, block: () -> Unit): Thread {
    val thread = object: Thread() {
        override fun run(){
            block()
        }
    }
    if(isDaemon)
        thread.isDaemon = true
    if(priority > 0)
        thread.priority = priority
    if(name != null)
        thread.name = name
    if(contextClassLoader != null)
        thread.contextClassLoader = contextClassLoader
    if(start)
        thread.start()
    return thread
}

fun main() {
    thread(start = true){
        println("Current Thread: ${Thread.currentThread()}")
        println("Priority: ${Thread.currentThread().priority}")
        println("Name: ${Thread.currentThread().name}")
        println("isDaemon: ${Thread.currentThread().isDaemon}")
    }
}

Current Thread: Thread[Thread-0,5,main]
Priority: 5
Name: Thread-0
isDaemon: false

스레드 풀 사용하기

newFixedThreadPool()

자주 재사용되는 스레드를 이용하기 위해서 미리 생성된 스레드풀에서 스레드를 이용할 수 있다.

ex) 8개의 스레드로 특정 백그라운드 서비스를 하도록 만드는 경우

val myService: ExecutorService = Executors.newFixedThreadPool(8)
var i = 0
while(i < items.size) { // 아주 큰 데이터를 처리할 때 
	val item = items[i]
    myService.submit {
    	processItem(item) // 여기서 아주 긴 시간 동안 데이터 처리 
    }
}
profile
꾸준히!

0개의 댓글