Thread-safe한 데이터 구조
Threads과 Coroutines에 모두 작동하는 일반적인 해결 방법은 공유 상태에 수행되어야하는 모든 동작에 대한 필수적인 동기화를 제공하는 스레드 안전한(동기화된, 선형성, 원자성 이라고도 부름) 데이터 구조를 사용하는 것이다. 간단한 카운터에 대해서는 incrementAndGet 이라 불리는 원자적인 동작을 제공하는 AtomicInteger 클래스를 사용할 수 있다.
val counter = AtomicInteger()
fun main() = runBlocking {
withContext(Dispatchers.Default) {
massiveRun {
counter.incrementAndGet()
}
}
println("Counter = $counter")
}
📌 전체 코드는 이곳에서 확인할 수 있습니다.
이는 이 특정한 문제에 대한 가장 빠른 해결 방법이다. 이는 일반적인 카운터, 컬렉션, 큐와 다른 표준적인 데이터 구조와 그들의 동작에서 작동한다. 하지만, 이는 이미 만들어진 스레드 안전한 구현이 없는 복잡한 상태나 복잡한 작업으로 쉽게 확장될 수 없다.*1
📖 아래 내용은 독자의 이해를 위해 번역자가 추가한 글입니다.
*1. 단순한 counter에서만 이렇게 사용 가능하다. 객체를 사용하는 더 복잡한 동작에서도 객체 내부 프로퍼티를 AtomicInteger 등으로 사용할 수 있지만 모든 프로퍼티를 그렇게 만드는 것은 어렵다. 따라서 별도의 동시성 처리가 필요하다.
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Shared mutable state and concurrency - Thread-safe data structures
원문 최종 수정 : 2022년 6월 27일
세밀하게 Thread 제한하기
스레드 제한은 특정한 공유된 상태로의 접근이 단일 스레드로 제한된 공유 상태 문제에 대한 접근 방식이다. 이는 일반적으로 모든 UI 상태가 단일 이벤트 디스패처/어플리케이션 스레드로 제한된 UI가 있는 어플리케이션에 사용된다.*1 단일 스레드를 관리하는 Context를 사용해 Coroutine을 적용하는 것은 쉽다.
val counterContext = newSingleThreadContext("CounterContext")
var counter = 0
fun main() = runBlocking {
withContext(Dispatchers.Default) {
massiveRun {
// confine each increment to a single-threaded context
withContext(counterContext) {
counter++
}
}
}
println("Counter = $counter")
}
📌 전체 코드는 이곳에서 확인할 수 있습니다.
이 코드는 세밀하게 스레드를 제한하기 때문에 아주 느리게 동작한다. 숫자를 증가시키는 각 동작은 멀티 스레드 디스패처인 Dispatchers.Default Context에서 withContext(counterContext) 블록을 사용해 단일 스레드 Context로 전환한다.
📖 아래 내용은 독자의 이해를 위해 번역자가 추가한 글입니다.
*1. 단일 이벤트 디스패처 혹은 어플리케이션 스레드 모델은 iOS, Android 등에서 사용된다. iOS에서는 Dispatcher.main이 이 역할을 하며, 안드로이드는 메인 스레드가 이 역할을 담당한다. 이는 어플리케이션에서 데드락 등을 방지하기 위해 사용된다.
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Shared mutable state and concurrency - Thread confinement fine-grained
원문 최종 수정 : 2022년 6월 27일
굵게 Thread 제한하기
실제로 스레드 제한은 큰 코드 덩어리에서 수행된다. 예를 들어, 큰 부분의 상태를 갱신하는 비즈니스 로직은 단일 스레드로 제한된다. 다음의 예제에서는 각 Coroutine을 단일 스레드 Context에서 실행되도록 함으로써 그렇게 한다.*1
val counterContext = newSingleThreadContext("CounterContext")
var counter = 0
fun main() = runBlocking {
// confine everything to a single-threaded context
withContext(counterContext) {
massiveRun {
counter++
}
}
println("Counter = $counter")
}
📌 전체 코드는 이곳에서 확인할 수 있습니다.
이제 더 빠르게 실행되고 옳은 결과를 생성한다.
📖 아래 내용은 독자의 이해를 위해 번역자가 추가한 글입니다.
*1. newSingleThreadContext는 하나의 스레드만을 관리하는 Dispatcher을 가지는 Context를 생성한다.
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Shared mutable state and concurrency - Thread confinement coarse-grained
원문 최종 수정 : 2022년 6월 27일
'공식 문서 번역 > Coroutines 공식 문서' 카테고리의 다른 글
IntelliJ IDEA 사용해서 Coroutine 디버깅 하기 (0) | 2023.05.17 |
---|---|
Coroutine 공유 상태와 동시성 3편 - Mutex 사용하기, Actors 사용하기 (0) | 2023.05.16 |
Coroutine 공유 상태와 동시성 1편 - Coroutine을 여러개 실행했을 때의 문제점, Volatile은 동시성 문제를 해결하지 못한다. (0) | 2023.05.14 |
Coroutine 예외 처리 3편 - Supervision - SupervisorJob, SupervisionScope 사용해 예외 처리하기 (0) | 2023.05.13 |
Coroutine 예외 처리 2편 - Cancellation과 Exceptions, Exceptions 합치기 (0) | 2023.05.12 |