Coroutine Basic
Coroutine Basic ๊ด๋ จ
์๋ ํ์ธ์. ๊ฐ๋จ์ธ๋์์ Android ๊ฐ๋ฐ์ ๋งก๊ณ ์๋ David ์ ๋๋ค.
์ด๋ฒ ๊ธ์์๋ Coroutine์ ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ ๋ฐฉ๋ฒ๊ณผ ์ Coroutine์ ์จ์ผํ๋์ง์ ๋ํ์ฌ ๋ค๋ค๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
coroutine ์ด light-weight thread ๊ฐ๋ค ๋ผ๊ณ ๋งํฉ๋๋ค.
๊ทธ ์ด์ ๋ Kotlin์์์ coroutine์ ์์ ๋ง์ stack์ด ์กด์ฌํ์ง ์์ผ๋ฉฐ
(C#, Scala, Kotlin์ coroutine์ stackless์ด๋ฉฐ Quasar, Javaflow์์์ coroutine์ stackful ์ ๋๋ค.)
native thread์ mapping ๋์ง ์๊ธฐ ๋๋ฌธ์ context switchig์ด ํ์ํ์ง ์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ coroutine์ thread ์ ๋น์ทํ ์ญํ ์ ํฉ๋๋ค.
์ด๋ค ์ ์ด ์๋ก ๋น์ทํ์ง ์์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
์๋ ์ฝ๋๋ฅผ ์คํ์ํค๋ฉด ์ด๋ค ๊ฒฐ๊ณผ๊ฐ ๋์ฌ๊น์?
fun main() {
thread {
Thread.sleep(1000)
println("World!")
}
println("Hello,")
Thread.sleep(2000)
}
๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
Hello,
World!
Thread.sleep()
๋ฉ์๋๋ blocking ๋ฉ์๋ ์ด๊ธฐ ๋๋ฌธ์
"Hello," ๋ฌธ์์ด์ด ๋จผ์ ๋ํ๋๊ณ 1์ด ๋ค์ "World!"๊ฐ ๋ํ๋ฉ๋๋ค.
์ด๋ฒ์๋ thread ๋์ ์ GlobalScope.launch
๋ฅผ ์ด์ฉํ์ฌ ์คํ์ ์์ผ๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
fun main() {
GlobalScope.launch {
delay(1000)
println("World!")
}
println("Hello,")
Thread.sleep(2000)
}
๊ฒฐ๊ณผ๋ ์ญ์ ๋๊ฐ์ต๋๋ค.
Hello,
World!
์์ ๊ฒฐ๊ณผ๋ฅผ ๋ดค์๋ ๋ค์๊ณผ ๊ฐ์ด ๋ํ๋ผ ์ ์์ต๋๋ค.
GlobalScope.launch { โฆ }
โthread { โฆ }
delay(โฆ)
โThread.sleep(โฆ)
์ฌ๊ธฐ์ ์ฃผ์ํด์ผํ ๋ถ๋ถ์ delay()
๋ฉ์๋๋ coroutine scope ์์์๋ง ๋์ํฉ๋๋ค.
main
ํจ์์์ delay()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด
fun main() = runBlocking {
GlobalScope.launch {
delay(1000)
println("World!")
}
println("Hello,")
delay(2000)
}
main ํจ์์ runBlocking {...} coroutine scope๋ฅผ ์ค์ ํ๋ฉด ์ฌ์ฉํ ์ ์์ต๋๋ค.
coroutine์ thread ์ ๋น์ทํ ๋์์ ํ๋๋ฐ ์ coroutine์ ์ฌ์ฉํด์ผํ ๊น์?
1. thread ๋ณด๋ค ๋ ์ข์ performance๋ฅผ ๋ํ๋ด๊ธฐ ๋๋ฌธ์ ๋๋ค.
๋ง์ฝ 10๋ง๋ฒ ๋ฐ๋ณต๋๋ ์์ ์ ์คํํ๊ณ ์ถ์ผ๋ฉด ์ด๋ป๊ฒ ํด์ผํ ๊น์?
์ผ๋ฐ์ ์ผ๋ก thread๋ฅผ ์์ฑํ์ฌ ์์ ์ ์คํํฉ๋๋ค.
์๋ ์ฝ๋๋ 100_000๊ฐ์ thread๋ฅผ ์คํ์ํค๋ ์ฝ๋์ ๋๋ค.
fun main() = rubBlocking {
repeat(100_000) {
thread {
Thread.sleep(1000)
print(".")
}
}
}
ํด๋น ์ฝ๋๋ฅผ ์คํ์ํค๋ฉด
out-of-memory error
๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ์ผ๋ก OOM ์๋ฌ๊ฐ ๋ํ๋ฉ๋๋ค.
thread ์์ฑํ ์ ์๋ ๊ฐ์๊ฐ ์ ํ๋์ด ์๊ธฐ ๋๋ฌธ์
thread pool๋ฅผ ์ด์ฉํ์ฌ thread๋ฅผ ๊ด๋ฆฌํ๋ฉด 10๋ง๋ฒ ๋ฐ๋ณต๋๋ ์์ ์ ์คํ์ํฌ ์ ์์ต๋๋ค.
ํ์ง๋ง thread pool์ ์ด์ฉํ์ฌ ๊ฐ๋ฐ์ ํ๋ฉด ์ฝ๋๊ฐ ๊ธธ์ด์ง๊ณ ์๋ชป ์ฌ์ฉํ ๊ฒฝ์ฐ์๋ memory leak์ด ๋ฐ์ํ๊ณ
๊ฐ๋ฐ์๊ฐ ์ ๊ฒฝ์จ์ผํ ๋ถ๋ถ์ด ๋ง์์ง๋๋ค.
coroutine ์ผ๋ก 10๋ง๋ฒ ์์ ์ ์คํํ๋ฉด ์ด๋ค ๊ฒฐ๊ณผ๊ฐ ๋์ฌ๊น์?
fun main() = runBlocking {
repeat(100_000) {
launch {
delay(1000)
print(".")
}
}
}
๊ฒฐ๊ณผ๋
...........
์์ํ๋๋ก "."์ด 10๋ง๋ฒ ์ฐํ๋๋ค.
thread ์ฌ์ฉํ๋ฉด ์์ฑํ๋๋ฐ ๋น์ฉ์ด ๋ค๊ณ ์ฌ๋ฌ๊ฐ์ thread๋ฅผ ์์ฑํ๋ฉด OOM์ด ์ผ์ด๋์ง๋ง
coroutine์ ์ฌ์ฉํ๋ฉด ๋ง์ด ์์ฑํ๋๋ผ๋ ์์ฃผ ํ๋ฅญํ performance๋ฅผ ๋ณด์ฌ์ค๋๋ค.
2. ๋ฐ๋ณต๋๋ ์์ ์ ์ฝ๊ฒ ์ทจ์ํ ์ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ๋ฐ๋ณต๋๋ ์์ ์ ํ๊ณ ์์๋ ํด๋น ์์ ์ ์ทจ์ํ๊ณ ์ถ์ผ๋ฉด ์ด๋ป๊ฒ ํด์ผํ ๊น์?
fun main() = runBlocking {
thread {
while(true) {
Thread.sleep(1000)
println("running...")
}
}
}
thread ์ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๋ณด๋ฉด
thread๊ฐ ์์ฑ๋๊ณ start()
๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด ์์
์ด ์คํ๋ฉ๋๋ค.
stop()
๋ฉ์๋๋ฅผ ์ด์ฉํ๋ฉด thread๋ฅผ ์ข
๋ฃ์ํฌ ์ ์์ต๋๋ค.
๋ค์์ 3์ด๋ค์ ์ค๋ ๋๋ฅผ ์ข ๋ฃ์ํค๋ ์ฝ๋์ ๋๋ค.
fun main() = runBlocking {
val thread = thread {
while(true) {
Thread.sleep(1000)
println("running...")
}
delay(3000)
thread.stop()
}
}
๊ฒฐ๊ณผ๋
running...
running...
"running..." ์ด ๋๋ฒ ์ฐํ๊ณ ์ข ๋ฃ๋ฉ๋๋ค.
ํ์ง๋ง stop()
๋ฉ์๋๋ฅผ ๋ณด๋ฉด
/**
* @deprecated This method is inherently unsafe. Stopping a thread with
* Thread.stop causes it to unlock all of the monitors that it
* has locked (as a natural consequence of the unchecked
*/
@Deprecated(since="1.2")
public final void stop() {
. . .
}
Java 1.2 ๋ฒ์ ๋ถํฐ Deprecated ๋ฌ์ผ๋ฉฐ ์ฌ์ฉํ๋๊ฒ์ ๊ถ์ฅํ์ง ์๊ณ ์์ต๋๋ค.
๊ทธ๋ฌ๋ฉด ์ด๋ป๊ฒ thread๋ฅผ ์ข ๋ฃ ์์ผ์ผ ํ ๊น์?
๋ฐฉ๋ฒ์ thread๋ฅผ Interrupting ํ์ฌ ์ข ๋ฃ ์์ผ์ผ ํฉ๋๋ค.
fun main = runBlocking {
val thread = thread {
try {
while(!Thread.interrupted()) {
Thread.sleep(1000)
println("running...")
} catch (e: InterruptedException) {
println(e)
}
}
delay(3000)
thread.interrupt()
}
}
2๊ฐ์ง๋ฅผ ์ค์ ํด์ค์ผ ํ๋๋ฐ interrupt state๊ฐ ์ค์ ๋์๋์ง ํ์ธ์ ํด์ผํฉ๋๋ค.
๋ฐ๋ณต๋๋ ์์
์ Thread.interrupted()
๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ๋งค๋ฒ ํ์ธ์ ํฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ interrupt๋ฅผ ํํ sleep()
๊ฐ์ Blocking ๋ฉ์๋๊ฐ ํธ์ถ๋์์ ๊ฒฝ์ฐ InterruptedException์ด ์ผ์ด๋ฉ๋๋ค.
Blocking ๋ฉ์๋๋ฅผ ํธ์ถํ ๊ฒฝ์ฐ try-catch๋ก ๊ฐ์ธ์ค์ผํฉ๋๋ค.
coroutine ์์๋ ์ด๋ป๊ฒ ์์ ์ ์ทจ์ํ ์ ์์๊น์?
fun main() = runBlocking {
val job = launch {
while(true) {
delay(1000)
println("running...")
}
delay(3000)
job.cancelAndJoin()
}
}
์์ฃผ ๊ฐ๋จํ๊ฒ cancelAndJoin()
๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด ํด๋น ์์
์ ์ทจ์ ํ ์ ์์ต๋๋ค.
launch()
๋ฉ์๋๋ฅผ ๋ณด๋ฉด Job
์ return ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๋ ํ ๊ฒฐ๊ณผ ๊ฐ์ ๋ฐ์ ์ ์์ต๋๋ค.
thread ์ ๋น์ทํ๊ฒ ๋์์ ํ์ง๋ง thread์ ๋นํ์ฌ ์์ ์ ์ฝ๊ฒ ์ทจ์ํ ์ ์๋ค๋ผ๋ ์ฅ์ ์ด ์์ต๋๋ค.
์ธ๋ฒ์งธ, ๋น๋๊ธฐ ๋ณ๋ ฌ ์ฒ๋ฆฌ๋ฅผ ์ฝ๊ฒ ํ ์ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ ์ฝ๋๋ฅผ ์คํ์์ผฐ์ ๊ฒฝ์ฐ ๊ฒฐ๊ณผ๊ฐ ์ด๋ป๊ฒ ๋์ฌ๊น์?
fun main() = runBlocking {
val time = measureTimeMillis {
val one = doSomethingUsefulOne()
val two = doSomethingUsefulTwo()
println("The answer is ${one + two}")
}
println("Completed in $time ms")
}
suspend fun doSomethingUsefulOne(): Int {
delay(1000)
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000)
return 29
}
๊ฒฐ๊ณผ๋
The answer is 42
Completed in 2009 ms
๊ฐ์ 42์ด์ง๋ง ์๊ฐ์ 2์ด๊ฐ ๊ฑธ๋ ธ์ต๋๋ค.
๊ฐ ๋ฉ์๋์ 1์ด์ฉ delay ๋์๊ธฐ ๋๋ฌธ์ ์ด 2์ด๊ฐ ๊ฑธ๋ ธ์ต๋๋ค.
ํด๋น ์์ ์ ๋ณ๋ ฌ๋ก ์ฒ๋ฆฌํ๋ ค๋ฉด
fun main() = runBlocking {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
suspend fun doSomethingUsefulOne(): Int {
delay(1000)
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000)
return 29
}
coroutine์ async
๋ฅผ ์ฌ์ฉํ๋ฉด ์ฝ๊ฒ ๋ณ๋ ฌ์ฒ๋ฆฌ๋ฅผ ํ ์ ์์ต๋๋ค.
async
์ return ๊ฐ์ Deferred ์ด๋ฉฐ Java์ Future ์ ๋น์ทํ๊ฒ ๋์์ ํฉ๋๋ค.
thread๋ฅผ ์ด์ฉํ์ฌ ๋ณ๋ ฌ์ฒ๋ฆฌ ํ๋ ๋ฐฉ์๋ณด๋ค ํจ์ฌ ๋ ๊ฐ๋จํ๊ฒ ์์ฑํ ์ ์๋ค๋ผ๋๊ฒ์ ์ ์ ์์ต๋๋ค.
์ด์ธ์๋ coroutine์ ์ฌ์ฉํ๋ฉด ์ฝ๊ฒ ์ฝ๋๋ฅผ ์งค ์ ์๋ค๋ผ๋ ์ฅ์ ์ด ์์ต๋๋ค.
ํ์ง๋ง thread๋ฅผ ์ ํ ์ดํดํ์ง ๋ชปํ๊ณ ๋ฌด์์ coroutine์ ์ฌ์ฉํ๋๊ฒ์ ๊ถ์ฅํ์ง ์์ต๋๋ค.
๋ฉด์ ์์ thread ๋์์๋ฆฌ์ ๋ํ์ฌ ์ง๋ฌธ์ ํ๋ฉด ๋๋ถ๋ถ ๋ต์ ๋ชปํ์๋ ๋ถ๋ค์ด ๋ง์ต๋๋ค.
thread ๋์์๋ฆฌ๋ ๋ชจ๋ฅด๋๋ฐ RxJava๋ฅผ ์ฌ์ฉํ์ ๋ถ๋ค์ด ๋ง์ผ์ ๋ฐ ๊ทธ๋ด๊ฒฝ์ฐ RxJava๋ฅผ ์๋ชป ์ฌ์ฉํ ๊ฒฝ์ฐ๊ฐ ํฝ๋๋ค.
thread์ ๋ํ์ฌ ์ถฉ๋ถํ ํ์ต์ ํ ๋ค coroutine์ ํ์ตํ๊ณ
RxJava์ coroutine์ ์ฐจ์ด์ ์ ๋ํ์ฌ๋ ๋ถ์ํ๋๊ฒ๋ ์ถ์ฒ๋๋ฆฝ๋๋ค.