Kotlin parseInt(), 업캐스팅, 다운 캐스팅, 자료형 확인(is), 여러 개의 인스턴스 리턴(Pair, Triple), Scope Functions, 확장함수, 쓰레드, 코루틴

박미소·2023년 12월 11일
0

코틀린

목록 보기
1/44

ParseInt( )

숫자 형태의 문자열을 숫자로 바꾼다.

var strNum5 = "10"
var strNum6 = "10.21"

var num5 = Integer.parseInt(strNum5)
var num6 = strNum6.toDouble()

println("num5: $num5")
println("num6: $num6")
// num5: 10
// num6: 10.21


업캐스팅(OOP 중 다형성에 해당)

하위 클래스를 상위 클래스의 자료형으로 객체 생성 할 수 있다.



fun main() {
    println("몇 마리를 생성하시겠습니까?")
    var count = readLine()!!.toInt()
    var birds = mutableListOf<Bird>()

    for(idx in 0..count-1) {
        println("조류의 이름을 입력해주세요")
        var name = readLine()!!

				// as Bird는 생략가능
        birds.add(Sparrow(name) as Bird)
    }
    println("============조류 생성완료============")
    for(bird in birds) {
        bird.fly()
    }
}

open class Bird(name: String) {
    var name: String

    init {
        this.name = name
    }

    fun fly() {
        println("${name}이름의 조류가 날아요~")
    }
}

class Sparrow(name: String): Bird(name) {

}



다운 캐스팅

상위 클래스를 하위 클래스의 자료형으로 객체 생성

fun main() {
    println("몇 마리를 생성하시겠습니까?")
    var count = readLine()!!.toInt()
    var birds = mutableListOf<Bird>()

    for(idx in 0..count-1) {
        println("조류의 이름을 입력해주세요")
        var name = readLine()!!

        birds.add(Sparrow(name) as Bird)
    }
    println("============조류 생성완료============")
    for(bird in birds) {
        bird.fly()
    }
    // 다운캐스팅 오류
    // Sparrow는 Bird가 가져야할 정보를 모두 가지고 있지 않기 때문임
//    var s1:Sparrow = birds.get(0)
}

open class Bird(name: String) {
    var name: String

    init {
        this.name = name
    }

    fun fly() {
        println("${name}이름의 조류가 날아요~")
    }
}

class Sparrow(name: String): Bird(name) {

}



자료형 확인(is)

var name = "이름"
if(name is String) {
    println("name은 String 타입입니다")
} else {
    println("name은 String 타입이 아닙니다")
    // name은 String 타입입니다
var num = 10
if(num is Int) {
    println("num은 Int 타입입니다")
} else {
    println("num은 Int 타입이 아닙니다")
    // num은 Int 타입입니다
}
var name = 10
if (name is String) {
    println("name은 String 타입입니다")
} else {
    println("name은 String 타입이 아닙니다")
    }



Pair - 두 개의 인스턴스 리턴

메소드는 기본적으로 하나의 데이터를 리턴한다.
두 개 이상의 데이터를 포함하는 데이터 클래스를 설계하고 인스턴스를 리턴하면 가능하지만 이 방법은 불필요한 클래스를 만드는 행위가 될 수 있고 이는 비효율적이다.

	var chicken = Chicken()
    var eggs = chicken.getEggs()
    var listEggs = eggs.toList()
    
//    first, second로 관리
//    var firstEgg = eggs.first
//    var secondEgg = eggs.second
    
    // 리스트로 관리
    var firstEgg = listEggs[0]
    var secondEgg = listEggs[1]

    println("달걀의 종류는 ${eggs} 입니다.")
    println("리스트 달걀의 종류는 ${listEggs} 입니다.")
    println("첫번째 달걀의 종류는 ${firstEgg} 입니다.")
    println("두번째 달걀의 종류는 ${secondEgg} 입니다.")
    // 달걀의 종류는 (달걀, 맥반석) 입니다.
    // 리스트 달걀의 종류는 [달걀, 맥반석] 입니다.
    // 첫번째 달걀의 종류는 달걀 입니다.
    // 두번째 달걀의 종류는 맥반석 입니다
}

class Chicken {
    fun getEggs(): Pair<String, String> {
        var eggs = Pair("달걀", "맥반석")
        return eggs
    }
}


Triple - 세 개의 인스턴스 리턴

fun main() {
    var chicken = Chicken()
    var eggs = chicken.getThreeEggs()
    var listEggs = eggs.toList()
    
//    first, second, third로 관리
//    var firstEgg = eggs.first
//    var secondEgg = eggs.second
//    var eggTime = eggs.third
    
    // 리스트로 관리
    var firstEgg = listEggs[0]
    var secondEgg = listEggs[1]
    var eggTime = listEggs[2]

    println("달걀의 정보는 ${eggs} 입니다.")
    println("리스트 달걀의 정보는 ${listEggs} 입니다.")
    println("첫번째 달걀의 종류는 ${firstEgg} 입니다.")
    println("두번째 달걀의 종류는 ${secondEgg} 입니다.")
    println("달걀은 ${eggTime}에 나왔습니다.")
    // 달걀의 정보는 (달걀, 맥반석, 20230101) 입니다.
    // 리스트 달걀의 정보는 [달걀, 맥반석, 20230101] 입니다.
    // 첫번째 달걀의 종류는 달걀 입니다.
    // 두번째 달걀의 종류는 맥반석 입니다.
    달걀은 20230101에 나왔습니다.
}

class Chicken {
    fun getTwoEggs(): Pair<String, String> {
        var eggs = Pair("달걀", "맥반석")
        return eggs
    }

    fun getThreeEggs(): Triple<String, String, Int> {
        var eggs = Triple("달걀", "맥반석", 20230101)
        return eggs
    }
}


Scope Functions

(1) let function

중괄호 블록 안에 it으로 자신의 객체를 전달하고 수행된 결과를 반환한다.
it == strNum, parseInt("10")이 숫자로 된 문자열에서 숫자 10이 되어 result에 할당된다.

var strNum = "10"

var result = strNum?.let {
        // 중괄호 안에서는 it으로 활용함
    Integer.parseInt(it)
    }

println(result!!+1)
// 11


(2) with function

중괄호 블록 안에 this로 자신의 객체를 전달하고 코드를 수행한다. this는 생략 가능해서 반드시 null이 아닐 때만 사용하는게 좋다.(with는 항상 null이 보장된 상태)

var alphabets = "abcd"

with(alphabets) {
 // var result = this.subSequence(0,2)
    var result = subSequence(0,2)
    println(result)
    // ab
    }


(3) also function

중괄호 블록 안에 it으로 자신의 객체를 전달하고 객체를 반환해준다. apply와 함께 자주 사용한다.

fun main() {
    var student = Student("참새", 10)

    var result = student?.also {  // ?.also => 널이 아닐 때만 also를 실행하게 한다.
        it.age = 50
    }

    result?.displayInfo()
    student.displayInfo()

    var result2 = student?.also {
        it.name = "비둘기"
        it.age = 150
    }

    result?.displayInfo()
    student.displayInfo()
    result2?.displayInfo()
}

class Student(name: String, age: Int) {
    var name: String
    var age: Int

    init {
        this.name = name
        this.age = age
    }

    fun displayInfo() {
        println("이름은 ${name} 입니다")
        println("나이는 ${age} 입니다")
    }
}

이름은 참새 입니다
나이는 50 입니다
이름은 참새 입니다
나이는 50 입니다
이름은 비둘기 입니다
나이는 150 입니다
이름은 비둘기 입니다
나이는 150 입니다
이름은 비둘기 입니다
나이는 150 입니다



(4) apply function

중괄호 블록 안에 this로 자신의 객체를 전달하고 객체를 반환해준다. 주로 객체의 상태를 변화시키고 바로 저장하고 싶을때 사용한다.
student 변수에 Student클래스에 name=참새, age=10이 매개변수가 되어 할당될거라고 했지만 변수 result에서 student 객체의 age를 50으로 다시 저장한다고 한 것.

fun main() {
    var student = Student("참새", 10)

    var result = student?.apply {
        student.age = 50
    }
    result?.displayInfo()
    student.displayInfo()

    var result2 = student?.apply {         // ?.apply => 널이 아닐 때만 apply를 실행한다.
        student.name = "비둘기"
        student.age = 150
    }
    result?.displayInfo()
    result2?.displayInfo()
    student.displayInfo()
}

class Student(name: String, age: Int) {
    var name: String
    var age: Int

    init {
        this.name = name
        this.age = age
    }

    fun displayInfo() {
        println("이름은 ${name} 입니다")
        println("나이는 ${age} 입니다")
    }
}

이름은 참새 입니다
나이는 50 입니다
이름은 참새 입니다
나이는 50 입니다
이름은 비둘기 입니다
나이는 150 입니다
이름은 비둘기 입니다
나이는 150 입니다
이름은 비둘기 입니다
나이는 150 입니다



(5) run function

  • 객체에서 호출하지 않는 경우의 예시
var totalPrice = run {
    var computer = 10000
    var mouse = 5000

    computer+mouse
    }
println("총 가격은 ${totalPrice}입니다")
// 총 가격은 15000입니다

  • 객체에서 호출하는 경우의 예시

with는 항상 null이 보장된 상태에서만 사용할 수 있다. with와 달리 run은 null 체크를 수행할 수 있으므로 더욱 안전하게 사용 가능하다.

a. null 값 출력

fun main() {
    var student = Student(null,10)
    student?.run {
        displayInfo()
    }
}

class Student(name: String?, age: Int) {
    var name: String?
    var age: Int

    init {
        this.name = name
        this.age = age
    }

    fun displayInfo() {
        println("이름은 ${name ?: "알 수 없음"} 입니다.")
        println("나이는 ${age} 입니다")
    }
}

이름은 알 수 없음 입니다.
나이는 10 입니다


b. 값 모두 전달

fun main() {
    var student = Student("참새",10)
    student?.run {
        displayInfo()
    }
}

class Student(name: String?, age: Int) {
    var name: String?
    var age: Int

    init {
        this.name = name
        this.age = age
    }

    fun displayInfo() {
        println("이름은 ${name ?: "알 수 없음"} 입니다.")
        println("나이는 ${age} 입니다")
    }
}

이름은 참새 입니다.
나이는 10 입니다



(6) 수신객체와 람다의 관계

수신객체와 람다함수 간의 긴밀한 관계가 존재한다. Scope Functions는 크게 두 가지로 구분할 수 있다.

  1. 명시적으로 수신객체 자체를 람다로 전달하는 방법.
  2. 수신객체를 람다의 파라미터로 전달하는 방법.

수신객체와 람다와의 관계:
T는 수신객체를 의미한다. block: 내부는 람다함수의 소스코드이다. 수신객체는 it으로 사용할 수 있다.

// 수신객체 자체를 람다의 수신객체로 전달하는 방법
public inline fun <T, R> T.run(block: T.() -> R): R
public inline fun <T> T.apply(block: T.() -> Unit): T
public inline fun <T, R> with(receiver: T, block: T.() -> R): R

// 수신객체를 람다의 파라미터로 전달
public inline fun <T> T.also(block: (T) -> Unit): T
public inline fun <T, R> T.let(block: (T) -> R): R


(7) 수신객체 shadow 방지

it이 여러 개면 가려지는(shadow) 상황이 발생해 제대로 참조하지 못하게 된다. 그래서 it 대신 다른 이름으로 변경해 사용한다.

// Scope Function을 중첩으로 사용할 경우 누가 누구의 범위인지 알수 없다!
// Implicit parameter 'it' of enclosing lambda is shadowed 경고 발생!

data class Person(
	var name: String = "",
	var age: Int? = null,
	var child: Person? = null
)

// 잘못된 예시
Person().also {
	it.name = "한석봉"
	it.age = 40
  val child = Person().also {
	  it.name = "홍길동" // 누구의 it인지 모른다!
    it.age = 10 // 누구의 it인지 모른다!
  }
  it.child = child
}

data class Person(
    var name: String = "",
    var age: Int? = null,
    var child: Person? = null
)

fun main () {
// 수정한 예시
    val person = Person().also {             // 외부에서 사용 가능한 person
        it.name = "한석봉"
        it.age = 40
        val child = Person().also { c ->    // 내부에서 사용 가능한 child
            c.name = "홍길동"
            c.age = 10
        }
        it.child = child
    }

    val child = person.child               // 외부에서 사용 가능한 child

    println(person.name)  // 한석봉
    println(person.age)  // 40
    println(child)      // Person(name=홍길동, age=10, child=null)
    if (child != null) {
        println(child.name)  // 홍길동
        println(child.age)   // 10
    }
     // 오류: println(child.age) => Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Person? //?.안쓰면 오류 조건문으로 쓰는 것이 아니면 
    // println(child?.name) => ?. 추가해서 널체크 해야함.
    // println(child?.age)      
}

Scope Functions 표



확장함수

외부에서 클래스의 메소드를 추가할 수 있다. 과도하게 사용하면 코드의 가독성을 해칠 수 있다. 원하는 메소드가 있지만 내가 설계한 클래스가 아닐때 외부에서 메소드를 관리하는 것이다. 내 목적을 위해 외부에서 관리하기 때문에 원본 클래스의 일관성을 유지할 수 있다. 클래스를 변경하지 못하는 상황에서 확장함수로 메소드를 추가해서 사용할 수 있다.

주의사항:
확장함수는 public에만 접근할 수 있고 private, protected는 접근 할 수 없다. 하위 클래스에서 확장함수를 재정의(오버라이드)할 수 없다.

이름 나이만 출력하는 displayInfo 메소드가 있는데 추가로 등급까지 조회

fun main() {
    fun Student.getGrade() = println("학생의 등급은 ${this.grade} 입니다")
    var student = Student("참새", 10, "A+")
    student.displayInfo()
    student.getGrade()
}

class Student(name: String, age: Int, grade: String) {
    var name: String
    var age: Int
		var grade: String

    init {
        this.name = name
        this.age = age
				this.grade = grade
    }

    fun displayInfo() {
        println("이름은 ${name} 입니다")
        println("나이는 ${age} 입니다")
    }
}


쓰레드

쓰레드 이해를 위한 사전지식:

  • 프로세스: 프로그램이 메모리에 올라가서 실핼될 때 이를 프로세스 1개라고 한다. 보통 프로그램을 더블클릭하면 프로세스가 생긴다.

  • 쓰레드: 쓰레드는 프로세스보다 더 작은 단위이다. 프로세스 안에서 더 작은 작업의 단위를 쓰레드라고 부른다. 즉, 쓰레드를 한 개 생성하면 스택메모리의 일정 영역을 차지한다.

  • 메모리 영역:
    Stack 영역은 컴파일 시점(개발자가 소스코드를 작성하고 컴파일 과정을 통해 기계어코드로 변환)에 크기가 결정되는 영역 (매개변수, 지역변수 등)이다.
    Heap 영역은 런타임(컴파일 과정을 마친 프로그램은 사용자에 의해 실행되어 지며 이러한 응용 프로그램이 동작되어지는 때)에 크기가 결정되는 영역이다. (동적할당, 객체 등)

fun main() {
	val person = object
    {
    	val name: String = "박치치"
    	val age: Int = 12
    }	
	println("이름 : " + person.name)
    println("나이 : " + person.age)
    // 이름 : 박치치
	// 나이 : 12
}

위 코드에서 person 이라는 객체의 좌표를 저장하기 위한 공간 데이터는 stack 영역에 저장되고,
name = "치치", age = 12의 데이터는 heap 영역에 저장된다.

출처:
https://spaghetti-code.tistory.com/35

https://velog.io/@heyksw/Kotlin-Object%EC%99%80-Class




  • 쓰레드 사용을 위한 사전 작업 : 외부 종속성 추가

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0-RC")
-> Gradle dependencies에 추가하기

fun main() {
    thread(start = true) {
        for(i in 1..10) {
            println("Thread1: 현재 숫자는 ${i}")
            runBlocking {
                launch {
                    delay(1000)  // 1초
                }
            }
        }
    }

    thread(start = true) {
        for(i in 50..60) {
            println("Thread2: 현재 숫자는 ${i}")
            runBlocking {
                launch {
                    delay(1000)
                }
            }
        }
    }
}

Thread1: 현재 숫자는 1
Thread2: 현재 숫자는 50
Thread1: 현재 숫자는 2
Thread2: 현재 숫자는 51
Thread2: 현재 숫자는 52
Thread1: 현재 숫자는 3
Thread2: 현재 숫자는 53
Thread1: 현재 숫자는 4
Thread2: 현재 숫자는 54
Thread1: 현재 숫자는 5
Thread1: 현재 숫자는 6
Thread2: 현재 숫자는 55
Thread1: 현재 숫자는 7
Thread2: 현재 숫자는 56
Thread1: 현재 숫자는 8
Thread2: 현재 숫자는 57
Thread1: 현재 숫자는 9
Thread2: 현재 숫자는 58
Thread1: 현재 숫자는 10
Thread2: 현재 숫자는 59
Thread2: 현재 숫자는 60

Process finished with exit code 0

쓰레드 순서의 출력값이 불규칙한 이유는 동시에 실행하지만 CPU 자원을 할당하기 위해 경쟁을 할 때 더 빨리 CPU에 접급한 쪽이 자원을 할당받하서 코드를 실행한 것이다. 그렇기때문에 출력값이 랜덤성을 가져서 쓰레드 순서가 실행할 때마다 다르다.



코루틴

쓰레드보다 더 경량화 되어있는 동시처리 기능(비동기 프로그래밍)을 제공한다. 최적화된 비동기 함수를 사용하고 하드웨어 자원의 효율적인 할당을 가능하게 한다. 안정적인 동시성, 비동기 프로그래밍을 가능하게 하고 로직들을 협동해서 실행하자는게 목표이고 구글에서 적극 권장하고 있다.


코루틴 빌더(Coroutine Builder):

코루틴을 만드는 함수를 코루틴 빌더라고 한다.

  • runBlocking이란?

새로운 코루틴을 실행한다. 해당 코루틴이 완료될 때까지 현재 스레드를 차단(blocking)한다. 주로 main() 함수 또는 테스트에서 사용한다. runBlocking은 CoroutineScope을 생성하는 가장 기본적인 Coroutine Builder이며 스레드를 차단(blocking)하고 코루틴을 실행한다.

CoroutineScope는 코루틴 실행을 관리하는 인터페이스이며 코루틴 실행에 필요한 정보를 가진 CoroutineContext 객체를 가진다. 코루틴은 CoroutineScope 내에서 생성되고 실행된다.

대표적인 코루틴 빌더에는 runBlocking을 포함하여 launch, async, withContext등이 있다. 구분하자면 withContext는 Scoping Function이다. launch는 새로운 코루틴을 만드는 코루틴 빌더이다. 그렇기 때문에 runBlocking이 더 빠르게 실행되고 lauch를 통해 새롭게 생성된 코루틴이 완료될 때까지 기다렸다가 실행된다.

fun main() = runBlocking {
    println("Hello, ${Thread.currentThread().name} thread!")
    launch {
        println("Goodbye, ${Thread.currentThread().name} thread!")
    }
    println("Wait, ${Thread.currentThread().name} thread!")
}
// Hello, main thread!
// Wait, main thread!
// Goodbye, main thread!

runBlocking의 가장 큰 특징인 현재 스레드 차단 기능은 launch에게 없다. 또한 launch는 CoroutineScope 내에서만 선언될 수 있다. 따라서 runBlocking을 제거하거나 runBlocking 블록 외부에서 launch를 선언하면 에러가 발생한다. 코루틴 빌더 블록 내부 역시 CoroutineScope이다.

출처:
https://brunch.co.kr/@mystoryg/183



  • launch의 Job 객체

launch는 결과값이 없는 코루틴 빌더이다.
Job 객체는 다양한 함수를 가지고 있다.
a. join: 현재의 코루틴이 종료되기를 기다린다.
b. cancel: 현재의 코루틴을 즉시 종료한다.

  • async

결과값이 있는 코루틴이다.

  • 코루틴의 스코프

a. GlobalScope: 앱이 실행된 이후에 계속 수행되어야할때 사용한다.

b. CoroutineScope: 필요할때만 생성하고 사용후에 정리가 필요하다.

  • Dispatcher

코루틴을 실행할 쓰레드를 Dispatcher로 지정할 수 있다.

a. Dispatchers.Main: UI와 상호작용하기 위한 메인쓰레드

b. Dispatchers.IO: 네트워크나 디스크 I/O작업에 최적화되어있는 쓰레드

c. Dispatchers.Default: 기본적으로 CPU최적화되어있는 쓰레드



쓰레드 예시

안드로이드는 항상 앱이 켜있는 상태지만 실습환경(안드로이드 스튜디오)은 실행 후 종료되는 JVM환경이다.

fun main(args: Array<String>) {
    println("메인쓰레드 시작")
    var job = GlobalScope.launch {
        delay(3000)
        println("여기는 코루틴...")
    }
    println("메인쓰레드 종료")
}
// 메인쓰레드 시작
// 메인쓰레드 종료

3초 후 코루틴의 결과를 얻기 전에 main함수가 종료해버린다.



  • GlobalScope 사용 (runBlocking 블록 내에 job 객체)

비동기적(동시에 여러가지)으로 코루틴의 결과를 조회하고 싶다면 job의 join메소드를 활용할 수 있다. 앱이 항상 켜져있지 않기 때문에 실습을 진행할 때 job으로 진행한다.

fun main(args: Array<String>) {
    println("메인쓰레드 시작")
    var job = GlobalScope.launch {
        delay(3000)
        println("여기는 코루틴...")
    }
    runBlocking {
        job.join()
    }
    println("메인쓰레드 종료")
}
// 메인쓰레드 시작
// 여기는 코루틴...
// 메인쓰레드 종료

  • CoroutineScope 사용
fun main(args: Array<String>) {
    println("메인쓰레드 시작")
    var job = CoroutineScope(Dispatchers.Default).launch {
        delay(3000)
        println("여기는 코루틴...")
    }
    runBlocking {
        job.join()
    }
    println("메인쓰레드 종료")
    job.cancel()
}
// 메인쓰레드 시작
// 여기는 코루틴...
// 메인쓰레드 종료

await

  • 여러 개의 코루틴 사용

코루틴의 결과값을 리턴 받을 수 있다. 결과값을 리턴받아야 하기에 await을 이용해 일시중단이 가능한 코루킨에서 실행가능하다.

fun main() {

println("메인쓰레드 시작")
var job = CoroutineScope(Dispatchers.Default).launch {
    var fileDownloadCoroutine = async(Dispatchers.IO) {
        delay(10000)  // 10초 뒤
        "파일 다운로드 완료"
    }
    var databaseConnectCoroutine = async(Dispatchers.IO) {
        delay(5000)  // 5초 뒤
        "데이터베이스 연결 완료"
    }
    println("${fileDownloadCoroutine.await()}")
    println("${databaseConnectCoroutine.await()}")
}
runBlocking {
    job.join()
}
println("메인쓰레드 종료")
job.cancel()
}
// 메인쓰레드 시작
// 파일 다운로드 완료
// 데이터베이스 연결 완료
// 메인쓰레드 종료

async(Dispatchers.IO)에서 Dispatchers.IO(네트워크나 디스크 I/O작업에 최적화되어있는 쓰레드)로 둔 이유는 외부로부터 파일 다운로드를 하고 데이터베이스로부터 데이터를 가져오고 넣는 행위를 하기 위해서 적합한 쓰레드인 IO를 쓴 것이다.




쓰레드와 코루틴 이론

  1. 쓰레드(Thread)

d작업 하나하나의 단위는 Thread로, 각 쓰레드가 독립적인 Stack 메모리 영역을 가진다. 운영체제 커널에 의한 Context Switching을 통해 동시성을 보장한다.


커널이 응용 소프트웨어를 컴퓨터 하드웨어에 연결하고 있다.

커널

커널(kernel)은 컴퓨터 운영 체제의 핵심이 되는 컴퓨터 프로그램으로, 시스템의 모든 것을 완전히 제어(control)한다. 자원 관리부분에서 한정된 시스템 자원을 효율적으로 관리하여 프로그램의 실행을 원활하게 한다. 특히 프로세스에 처리기를 할당하는 것을 스케줄링이라 한다.




a. 블로킹(Blocking)
Thread A가 Thread B의 결과를 기다릴 때 Thread A는 블로킹 상태라고 할 수 있다. A는 Thread B의 결과가 나올 때까지 해당 자원을 사용하지 못한다.

b. 예시

  • Thread A가 Task 1을 수행하는 동안 Task 2의 결과가 필요하면 Tread B를 호출한다.
  • 이 때 Tread A는 블로킹 되고 Tread B로 프로세스 간에 스위칭이 일어나 Task 2를 수행한다.
  • Task 2가 완료되면 Tread A로 다시 스위칭해서 결과 값을 Task 1에게 반환한다.
  • 이때 Task 3, Task 4는 A, B작업이 진행되는 도중에 멈추지 않고 각각 동시에 실행되게 된다.
  • 이때 컴퓨터 운영체제 입장에서는 각 Task를 쪼개서 얼마나 수행할지가 중요하다.
    그래서 어떤 쓰레드를 먼저 실행해야할지 결정하는행위를 스케줄링이라고 하는데 스케줄링을 통해 동시성을 보장한다.


  1. 코루틴

작업 하나하나의 단위는 Coroutine Object로 여러 작업 각각에 Object를 할당한다. 코루틴 객체도 엄연한 객체이기에 JVM Heap에 적재한다(코틀린 기준). 동시성 보장 수단은  Programmer Switching (No-Context Switching)이고 소스코드를 통해 Switching 시점을 마음대로 정한다(OS는 관여하지 않는다).

a. suspend(Non-Blocking)

Object 1이 Object 2의 결과를 기다릴 때 Object 1의 상태는 Suspend로 바뀐다. 그래도 Object 1을 수행하던 Thread는 그대로 유효하다. 그래서 Object 2도 Object 1과 동일한 Thread에서 실행된다.

b. 예시

  • Coroutine은 작업 단위가 Object이다.
  • Task 1을 수행하다가 Task 2의 수행요청이 발생했다고 가정한다면, 신기하게도 컨텍스트 스위칭(스레드의 동시성 보장 수단) 없이 동일한 Thread A에서 수행할 수 있다.
  • Thread C처럼 하나의 쓰레드에서 여러 Task Object들을 동시에 수행할 수 있다.

이러한 특징때문에 코루틴을 Light-Weight Thread라고 이야기한다. 동시처리를 위해 스택영역을 별도로 할당하는 쓰레드처럼 동작하지 않느다. 하지만 동시성을 보장할 수 있다. 하나의 쓰레드에서 다수의 코루틴을 수행할 수 있다. 커널의 스케줄링을 따르는 컨텍스트 스위칭(Thread의 동시성 보장 수단)을 수행하지 않는다.

0개의 댓글