묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨코틀린 코루틴 완전 정복
코루틴이 멀티스레드의 단점을 해결했다는 부분에 대해 질문드립니다.
안녕하세요? 강의 잘 듣고 있습니다. 코루틴이 멀티스레드의 단점을 해결했다고 말씀해주셨는데요, 관련해서 약간 정리가 되는 듯 안되는듯 하여 질문드립니다.1. 우선 아래의 정리가 맞는지 여쭤보고 싶습니다.멀티 스레딩의 문제점은 결국 blocking이고 이 blocking을 해결하기 위해 코루틴을 도입했음코루틴은 스레드를 점유하는 형태로 동작하므로, 반대로 코루틴이 blocking될때 스레드를 점유하지 않음으로써 다른 코루틴이 해당 스레드를 점유하게 되고 결과적으로 스레드가 blocking되는 일이 없어진다. 2. 그런데 blocking이 되는 현상이 언제발생하나요?강의에서 말씀해주신 내용에 따르면, 다른 스레드 혹은 코루틴의 결과가 필요할 때 blocking되는 상황에 놓여지는 것 같은데 맞을까요?결국 그렇다고하면 이전 코드의 완료를 보장하는, 그러니까 sync한 방식으로 코딩을 해야할 때 스레드가 놀지 않으면서 & completableFuture처럼 콜백지옥이나 예외처리가 어렵지 않게 하는 것이 코루틴의 장점이 맞을까요? 3. 일반적인 IO상황도 위에서 얘기한 blocking이 맞을까요?다르게 말하면, Dispatcher IO에서 [요청을 보내고 기다려야만 하는 상황]에서도 코루틴은 스레드의 점유권을 내려놓음으로써 해당 스레드가 다른 작업을 처리할 수 있게 되는걸까요?예를 들면, A스레드가 코루틴의 DIspatcher IO에 의해 관리되는 IO전용 스레드고 IO스레드는 해당스레드하나만 존재할때(가용가능한 다른 스레드가 없는 상황) c코루틴은 서버에 호출을 보내서 4초가 걸리고, d코루틴은 서버에 호출을 보내서 5초가 걸리면 A스레드에서 c코루틴과 d코루틴을 병렬적으로 처리할 수 있는건가요? 단순히 다른 스레드를 하나 생성해서 두가지 작업을 다 맡겼더라면 해당 스레드에서 4초 + 5초해서 9초가 걸렸을텐데, 코루틴기반의 A스레드에서는 약 5초정도밖에(조금 더 길수는 있겠지만) 안걸리는 게 맞을까요? 4. 3번에 이어지는 질문인데요, 만약 3번이 맞다고 하면 IO작업의 응답이 왔을 때 콜백같은 게 적용이 되어서 Dispatcher에 새로운 작업으로 추가되는걸까요?그러면, IO요청을 보낸 스레드와 IO응답을 처리하게 되는 스레드가 왠지 다를 수도 있을 것 같은데 맞을까요? 3번이 맞다고 하면, 코루틴은 아주 아름다운 것일 것 같은데 굉장히 설레네요 ㅎㅎ좋은 강의 감사합니다.
-
해결됨코틀린 코루틴 완전 정복
Dispatcher.IO의 동작원리
안녕하세요! 강의 재밌게 보고 있습니다! ㅎㅎ 코루틴을 공부하면서 항상 의문이던 부분이였는데 Dispatcher.IO가 Http Request 같은 IO 작업에서 어떤 원리로 Dispatcher.Default보다 더 효율적인걸까요? 내부 스레드선언이 더 많아서 Default보다 더 자주 코루틴 컨텍스트 스위칭이 되는걸까요?
-
해결됨코틀린 코루틴 완전 정복
실무에서 runBlocking 와 CoroutineScope 실무 사용에 대해
실무에서 코루틴을 사용해야할 때runBlocking 으로 코루틴 영역을 생성해야 할지 CoroutineScope로 생성해야 할지 또 어떻게 코루틴을 활용해야할지 감이 살짝 오시 않습니다.물론 호출부의 스레드를 블럭킹해야 한다면 runBlocking 일 것 같지만 보통 이렇게 사용한다라는 지향점이 궁금합니다. 실무에서 사용하기 위해 추천할만한 깃헙 레파지토리나 베스트프랙티스가 있을까요? - 학습 관련 질문을 남겨주세요. 질문을 상세히 작성하면 더 좋습니다.- 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.
-
해결됨코틀린 코루틴 완전 정복
Code3-6에서 imageProcessingDispatcher가 inline으로는 동작하지 않는 이유가 궁금합니다
안녕하세요. 기초적인 질문 같지만, 코루틴을 처음 사용하는 입장에서 imageProcessingDispatcher를 인라인으로 넣을 수 없는 이유가 궁금합니다. 직접 테스트했을 때,launch(Dispatchers.Default.limitedParallelism(2)) { //... }와 같은 코드는 예상한대로 동작하지 않고, 머신 processor만큼을 사용하는 것으로 보이는데, 그 이유가 뭘까요?
-
해결됨2시간으로 끝내는 코루틴
간단 질문.. join() vs delay()
예제를 실행하면서 job이 종료되기까지 join()으로 기다리는 방법이 있고, 스코프를 다른곳으로 보내서 메인스레드가 더 빨리 종료되게 하지 않기 위해 자식스코프 또는 main스레드 내부의 Root 코루틴스코프의 실행시간보다 delay를 길게 주는 방법이 있는것같은데요아래 1번, 2번 방법 중에 어떤 차이가 있고, 코루틴을 현업에서 쓸 때는 어떤 방법으로 보통 사용하게되나요?그리고 강의를 아직 다 보지는 않았는데.. RxJava, WebFlux, Virutal Thread, Coroutine 등을 어떤 영역에서 활용하는지 궁금합니다Network, File I/O, DB CRUD, External API call, etc...그리고 음.. 2022년도쯤에는 비동기트랜잭션이 하이버네이트쪽에서 잘 지원 안해줘서 r2dbc를 사용하다가 취소한 곳이 있다고도 얼핏 들은것같은데 현재는 어떤지 위에 활용처 질문과 함께 알려주시면 감사하겠습니다!fun test(): Unit = runBlocking { val job1 = CoroutineScope(Dispatchers.Default).launch { delay(1_000L) printWithThread { "Job 1" } } val job2 = CoroutineScope(Dispatchers.Default).launch { delay(1_000L) printWithThread { "Job 2" } } // 1번 방법 job1.join() job2.join() // 2번 방법 delay(2_000L) }
-
해결됨코틀린 코루틴 완전 정복
공유 스레드 풀 질문드려요!
섹션4. CoroutineDispatcher 에서 미리 정의된 공유스레드 풀 내에 Dispatchers.Defualt 는 프로세서 개수(최하 2), Dispatchers.IO (64or프로세서 개수 중의 큰 수) 의 개수만큼의 스레드가 할당되는건가요? - 학습 관련 질문을 남겨주세요. 질문을 상세히 작성하면 더 좋습니다.- 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.
-
해결됨코틀린 코루틴 완전 정복
코루틴 이름 출력관련해서 질문이 있습니다!
println("[${Thread.currentThread().name}] 코루틴 실행2") 위 코드 실행할 때 실행결과가 아래와 같이 나오는데 @coroutine#1 어떻게 이렇게 코루틴 이름까지 같이 나오게 할 수 있나요?! [출력 결과] [MyThread1 @coroutine#1] 코루틴 실행 2
-
해결됨코틀린 코루틴 완전 정복
coroutineScope에 대해 질문 있습니다.
suspend fun searchByKeyword(keyword: String): Array<String> = coroutineScope { val dbResultsDeferred = async { searchFromDB(keyword) } val serverResultsDeferred = async { searchFromServer(keyword) } return@coroutineScope arrayOf(*dbResultsDeferred.await(), *serverResultsDeferred.await()) }안녕하세요 "일시 중단 함수의 사용" 학습 중 나온 코드에 대해 질문 드립니다.강의에서는 "coroutineScope을 사용하면, 일시중단 함수 내부에 coroutineScope 객체를 만들 수 있다."라고 하셨는데, 이 부분이 이해가 잘 안갑니다.먼저 coroutineScope {}를 선언하고, async 비동기 작업 -> delay 일시중단 순으로 진행되는데 일시중단 함수 내부에 coroutineScope 객체를 만들 수 있는 것이 이해가 잘 가지 않습니다.제가 정리하기로는, "coroutineScope을 사용하면, 일시중단 함수 내부에 coroutineScope 객체를 만들 수 있다."라기 보다는 위 그림처럼 "하나의 coroutineScope에 2개의 비동기 작업과 각각의 일시중단 함수를 포함하는 것"으로 받아들여지는데 제가 어느 부분을 놓치고 있는건지 잘 모르겠습니다. 감사합니다. ^^
-
미해결2시간으로 끝내는 코루틴
코루틴을 잘 사용하고 있는지 궁금합니다.
강사님 강의를 듣고 배치 단위로 요청을 가져와 Redis 서버에서 작업을 처리하는 부분에 코루틴을 적용하려고 합니다. 강의를 통해 최소한의 스레드로 여러 코루틴을 실행해야 코루틴을 극대화할 수 있다고 이해했습니다. n개의 요청은 모두 독립적이며 배치 단위로 가져온 이유는 1개의 스레드로 여러 코루틴을 실행하기 위함입니다. Redis는 마스터에서만 작업이 가능하므로 요청을 보내고, 응답받아 후속 처리하는 부분을 Redis 서버로 요청 (코루틴1)Redis 서버로부터 응답받아 후속 처리 (코루틴2)로 분리했습니다.1개의 스레드에서 여러 코루틴을 동작시키기 위해 이런 기준으로 코루틴의 중단 지점을 생성하는 것이 적절한지 궁금합니다. 또한 n개의 요청을 모두 Redis 서버로 보낸 후 응답 받는 것이 아니라응답을 받고 후속 처리가 가능한 요청은 ‘모든 요청을 Redis 서버로 전달했다는 여부와 상관없이’ 후속처리를 하기 위해 다음과 같이 코드를 작성했습니다. coroutineScope { val jobs = userIds.map { userId -> async { // main 스레드가 처리 val result = 레디스 접근 메서드 (RedisTemplate) Pair(userId, result) } } jobs.forEach { job -> launch(Dispatchers.IO) { val (userId, status) = job.await() // 응답에 대한 후속 처리 // 결과마다 다른 DefaultDispatcher-worker 가 처리 } } }위와 같이 작성하면 Redis로 요청을 보내는 부분은 1개의 main스레드가 처리하지만, 후속 처리는 결과마다 다른 DefaultDispatcher-workder가 처리합니다. 이는 결과마다 다른 스레드가 처리하는 것이 맞다면 이 코드는 코루틴을 제대로 사용하지 못하고 있다고 생각하는데 제 생각이 맞는지 궁금합니다.coroutineScope { launch { for(userId in userIds) { val result = 레디스 접근 메서드 channel.send(Pair(userId, result)) } channel.close() } launch { for(result in channel) { // 응답에 대한 후속 처리 } } }위와 같이 작성하면 1개의 main 스레드가 모든 작업을 처리합니다. 이렇게 작성해야 코루틴을 제대로 사용하는 것이 맞는지 궁금합니다. 강의 잘 들었고 감사합니다!
-
미해결코틀린 코루틴 완전 정복
공유 스레드풀에 대하여 질문 있습니다
안녕하세요. 1) "미리 정의된 CoroutineDispatcher - 1. Dispatchers.IO, Dispatchers.Default" 강의 중 마지막 부분에서 Dispatchers.IO와 Dispatchers.Default가 사용하는 스레드의 이름이 동일한 이유는 공유 스레드풀 때문이라고 하셨는데, 이 부분이 이해가 잘 가지 않습니다.이것이 중요한 이유가 각각의 Dispatcher가 사용하는 스레드가 실제로는 다름에도 불구하고, 이름은 동일하기 때문에 헷갈리면 안되기 때문인가요? 제가 강조하신 부분을 잘 이해한건지 모르겠습니다.2) LimitedParallelism은 코루틴 사용 시 모든 부분에 적용해야 하는 것인가요? 아니면 특정 경우에만 사용하면 되는 것인가요?3) Code3-1~3-4를 실행할 때 어떤 때는 스레드 이름이 DefaultDispatcher-worker-1,2,3으로 뜨지만 또 어떤 때는 1,2,1 또는 1,2,4 또는 1,2,5 이런식으로 뜨는데 이유가 무엇인가요?감사합니다!
-
해결됨코틀린 코루틴 완전 정복
강의와 책을 통해 학습한 내용을 출처를 남기고 기술 블로그 등에 공개해도 될까요?
안녕하세요, 해당 강의와 책을 통해 코루틴에 대한 학습을 진행하고 있는 학생입니다.학습한 내용을 기술 블로그나 깃헙 등에 출처를 남기고 공개해도 되는지 궁금합니다!요즘 강의와 책에 대한 저작권이 중요한 만큼, 강의자님께 직접 여쭙게 되었습니다. 좋은 강의 감사합니다.
-
해결됨코틀린 코루틴 완전 정복
Coroutine과 VirtualThread의 차이점에 대해질문드립니다
안녕하세요 Coroutine 강의 잘듣고있는 수강생입니다 (_ _)최근 JDK21의 VirtualThread관련해 찾아보게 되었는데요 Coroutine과 유사하게 Os단의 Thread를 점유하지 않고 Software적으로(?) 쓰레드에 작업분배를 하는점이 유사하다고 느꼈습니다 (잘못이해했다면 첨삭부탁드립니다 ㅠㅠ)만약 제 이해가 맞다면, JVM레벨에서 제공하게되는 VirtualThread가 더 범용성이 높아보이는데 Coroutine의 사용이 유지가 될까요? 남아있게 된다면 Kotlin 언어차원에서의 사용성이 좋아서 일정도일지... 아니면 Coroutine이 VirtualThread와 다르게 차별화된 장점이 있을지 궁금합니다감사합니다!
-
해결됨2시간으로 끝내는 코루틴
CoroutineScope & Dispatcher 질문
안녕하세요.제가 복습을 하다가 궁금한 부분이 생겨 질문드립니다. section1 아래 코드를 다시 보니까 조금 혼란이 옵니다. fun main(): Unit = runBlocking { val job1 = CoroutineScope(Dispatchers.Default).launch() { ... } val job2 = CoroutineScope(Dispatchers.Default).launch() { ... } } 위 코드는 같은 Dispatchers.Default를 사용하는 것으로 보이는데, 질문.두 CoroutineScope가 공통된 Dispatchers.Default를 공유하니까 한 코루틴에서 예외가 발생하면 다른 코루틴에서 예외가 발생할 것 같은데, 따로 처리되는 이유가 무엇인지 궁금합니다.예를 들어, job1에 대한 CoroutineScope에서 예외가 발생하면 Dispatchers.Default에 job1에 대한 id를 주고, 해당 코루틴(ex. job1 내부 delay 상태로 된 내부 코루틴)에 대한 코루틴을 전부 지워라이런 식으로 작동해서 독립적으로 실행되는건가요? 코루틴을 스레드에 배정하는 역할이 Dispatchers.Default, IO, Main 등이 있는데, 이름이 같은데 따로 예외처리되고 하니까 헷갈려서 질문드립니다. 제가 Dispatchers와 CoroutineScope 관계에 대해서 잘 이해를 못해서 또 이렇게 질문 올립니다.. 감사합니다.
-
해결됨2시간으로 끝내는 코루틴
async await 관련 질문
안녕하세요.val job1 = async { apiCall1() } val job2 = async { apiCall2(job1.await()) } printWithThread(job1.await() + job2.await())만약, job1에 대한 결과를 메인 코루틴과 job2 코루틴 각각에서 사용하고 싶으면 위와 같이 작성하면 되나요?제가 실제로 apiCall1에 print문을 넣어 보니까 1번만 출력이 되는 것을 확인했습니다. 만약, 위에 질문이 맞다면 코루틴 job을 생성하고 해당 결과를 받을 때는 항상 await() 함수를 호출하면 되나요?여기서 궁금한 점은 만약 서버와 통신하는 api라고 한다면, 새롭게 호출하고 싶을 때도 있을 텐데 이럴 때는 어떻게 기존 값이 아닌 새로운 값을 받을 수 있는지도 궁금합니다. 감사합니다.
-
해결됨2시간으로 끝내는 코루틴
코루틴 실행 순서 궁금합니다.
안녕하세요.먼저 선생님 강의가 도움 많이 되고 있습니다.감사합니다. 질문이 두 가지 있습니다.질문 1.fun main() = runBlocking { println("START") launch { println("1") } launch { println("2") } launch { println("3") } launch { println("4") } yield() println("END") }이런식으로 되어 있다면yield실행 후에 1~4번 중 어떤게 출력될지는 랜덤인건가요?실제 실행 시에는 순서대로 되는걸 확인 했는데, 이게 항상 launch가 호출된 순서대로 출력되는건가요?질문 2.fun main() = runBlocking { println("START") launch { println("1") } launch { println("2") } launch { yield() println("3") } launch { println("4") } yield() println("END") }만약 3번에 yield 가 있다면, 어떻게 되는건가요?3번에 들어온 순간 끝나지 않은 Coroutine 중에서 랜덤하게 호출되는건가요?아니면 main으로 가서 END가 출력되는건가요?감사합니다.
-
미해결2시간으로 끝내는 코루틴
corutine task에 대한 질문
안녕하세요! 강의 정말 잘보고 있습니다. 한 가지 질문이 있는데요. 스레드도 많이 생성될 경우 컴퓨터의 자원을 빠르게 소모하여 서버가 다운될 수 있기 때문에 스레드 풀로 관리를 할텐데 코루틴의 경우에도 코루틴 자원을 무분별하게 많이 생성을 방지하기 위해 별도의 pool 같은게 있을까요?
-
미해결2시간으로 끝내는 코루틴
completing의 존재의의가 궁금합니다.
안녕하세요? 강의 정말 잘 듣고 있습니다. 세심한 답변도 감사드립니다. 강의를 듣던중 궁금증이 생겼는데요,completing이라는 status의 의의가 좀 궁금합니다. 강의예시로 보여주신 코드는 대략 아래와 같은 느낌이였는데요, 이경우 두번째 자식 코루틴 취소 -> 부모로 전파 -> 다른 자식으로 전파(취소요청) -> 취소 된다는 부분은 이해했습니다.fun main(): Unit = runBlocking { launch { delay(700L) printWithThread("First Child Corutine") } launch { delay(500L) throw IllegalArgumentException("Second Child Corutine Exception~~~!") } } 그런데 강사님이 말씀해주신 completing은 마치 특정 코루틴의 작업이 완료되어도, 다른 코루틴의 작업이 실패했을 때 다시 취소처리하기 위한(그래서 Structured Concurrency를 달성하기 위한) 수단인것처럼 말씀해주셨는데, 실제로 어떤식으로 동작하는지를 잘 이해가 안갑니다 예를 들어 제가 처음 강의를 들었을 때는, 아래의 코드에서 우선적으로 첫번재 launch 실행 -> completing상태 -> 두번째 launch 실행 -> 예외발생 -> 첫번재 코루틴이 다시 cancelling이 되어야 한다고 이해했는데..그러면 아래 코드에서 첫번째 코루틴에서 cancellationException이 잡혀서 "First Child Coroutine caught an exception: ${e.message}" 가 출력되어야 할 것 같은데 그러지 않더라구요. 아마 첫번째 코루틴이 completing이 아닌 completed상태가 되어서 더이상 영향을 받지 않게 되는 것 같은데.. completing이 정확히 어떤 상태인지가 궁금합니다fun main(): Unit = runBlocking { launch { try { delay(500L) println("First Child Coroutine Completed Successfully") } catch (e: Exception) { println("First Child Coroutine caught an exception: ${e.message}") } } launch { delay(700L) throw IllegalArgumentException("Second Child Corutine Exception~~~!") } } // 출력결과 First Child Coroutine Completed Successfully Exception in thread "main" java.lang.IllegalArgumentException: Second Child Corutine Exception~~~! ...
-
미해결2시간으로 끝내는 코루틴
코루틴 dispatcher IO관련 질문
안녕하세요 강사님? 강의 잘 듣고 있습니다. 듣다보니 몇가지 궁금증이 생겨서 질문드립니다. 너무 쉬운 질문들도 있겠지만, 선생님의 답변을 통해 확신을 얻고 싶은 마음이 있어 질문드립니다 __)동기적 호출을 전제하는 동작, 예를 들 FeignClient기반의 호출을 다른 스레드(DISPATCHER.IO)에위임하지 않고 기존 스레드에서만 코루틴형태로 동작시키면, 이때에도 여전히 의도와 다르게 블락킹될 것 같은데 맞을까요? 강의에서 보여주신 예시중에 아래와같은 예시가 있는데, 이 경우에는 하나의 스레드만 사용하시는 것 같아 혹시나 하는 마음에 여쭤봅니다. // Async 사용 fun main(): Unit = runBlocking { val time = measureTimeMillis { val job1 = async { apiCall1() } val job2 = async { apiCall2() } printWithThread(job1.await() + job2.await()) } printWithThread("소요시간 : $time") } // 출력 결과 [main] 소요시간 : 1030 IO를 효율적으로 진행하기 위해서는 결국 Dispatcher I.O의 스레드들에게 위임해야 할 것 같은데그렇다면 애초부터 Dispatcher I.O의 스레드들은 ‘블락킹당해도 괜찮다’를 전제로 만들어진 스레드들(스레드풀)인걸까요?그러면 결론적으로 Dispatcher IO는 블락킹 당해도 괜찮을 수 있도록 어떤식으로 처리되어 있는 건지 궁금합니다.
-
미해결2시간으로 끝내는 코루틴
Job 질문이 있습니다
이전 강의 설명에서 아래의 코드가 실행될 때 launch로 만든 코루틴이 바로 실행되지 않고 다음 코드로 넘어간다고 했는데fun main(): Unit = runBlocking { println("START") launch { newRoutine() } println("END") }왜 아래의 코드에서는 job 객체를 변수에 담으면 다음 코드로 넘어가는게 아니라 바로 실행이 되는건가요?fun main(): Unit = runBlocking { val job = launch { (1..5).forEach { printWithThread(it) delay(500) } } delay(1_000L) job.cancel() }
-
해결됨2시간으로 끝내는 코루틴
스프링 MVC 환경에서의 코루틴
안녕하세요.Spring MVC 환경에서 코루틴을 사용할 때 질문이 있습니다. (WebFlux 사용 X)다음과 같은 코드가 있다고 가정하겠습니다. 컨트롤러@GetMapping("/test") suspend fun test(): ApiResponse<Void> { testService.run() return ApiResponse.success(statusCode = HttpStatus.OK) }서비스class TestService( private val userRepository: UserRepository, ) { suspend fun run(): Triple<String, String, Int> { // 외부 API 호출 1 val nicknameDeferred = CoroutineScope(Dispatchers.IO).async { callApiForNickname() } // 외부 API 호출 2 val addressDeferred = CoroutineScope(Dispatchers.IO).async { callApiForAddress() } // Do something business logic // DB 호출 val duplicateCount = userRepository.findDuplicateCountById(1L) return Triple(first = nicknameDeferred.await(), second = addressDeferred.await(), third = duplicateCount) } }(callApiFor~() 메소드는 외부 API 호출이라고 가정하며 suspend function입니다)서비스 레이어에서는 2개의 외부 API 호출을 수행하고 DB호출을 한 후 리턴해주는 간단한 구조입니다. 질문초기에는 내부적으로 코루틴을 사용할 지 말지 결정할 수 없는 상황이 있기에 모든 컨트롤러 메소드를 suspend로 선언하는건 어떨까 합니다. (2번 질문의 성능 차이가 크지 않다면요^^) 이 부분에 대해 강사님의 생각과 실무에서는 보통 어떻게 하는지 궁금합니다.위 질문에 이어서, 코루틴이 전혀 사용되지 않는 상황에서 컨트롤러 메소드를 suspend로 선언하면 일반 메소드로 선언하는것과 성능차이가 어느정도 있을까요? (물론 트래픽 등 기타 상황별로 다르겠지만요)서비스 레이어를 보면 CoroutineScope을 통해 각 외부 API호출별로 루트 코루틴을 따로 만들고 있습니다. 각 외부 API들은 단순 조회 요청으로써 구조적 동시성이 필요하지 않다고 생각하기 때문인데요. 실무에서도 위와 같이 여러개의 외부 API호출을 병렬로 수행해야하는 경우 각 호출마다 CoroutineScope을 사용하여 개별로 루트 코루틴을 만들어서 사용하는지 궁금합니다.일반적인 웹 API 서버 개발을 수행할 때 구조적 동시성이 필요한 케이스가 있을까요? 어차피 추후 로직을 통해 반환값에 대한 검증을 수행한다고 생각해서요. 예를 들어 A, B, C를 코루틴으로 호출할 때 A가 실패해서 B, C를 취소하려해도 각 메소드의 레이턴시에 따라 취소될수도, 이미 완료됐을수도 있을 것 같습니다. 실무에서 웹 API 비즈니스 로직 구성 시, 개별 루트 코루틴이 아닌 하나의 루트 코루틴으로 묶어서 구조적 동시성을 보장해야만 하는 상황이 있을지, 있다면 무엇일지 궁금합니다.감사합니다.