CPU 바운드, I/O 바운드
CPU 바운드
CPU만 완료하면 되는 작업 중심 알고리즘
알고리즘 성능 → CPU 성능에 좌우
코드를 더 빠른 CPU에서 실행하면 코드 변경 없이 성능 향상
I/O 바운드
입출력 장치에 의존하는 알고리즘
실행시간 → 입출력 장치의 속도에 좌우
네트워킹이나 컴퓨터 주변기기로부터 입력받는 작업들도 포함됨
최적화가 외부시스템이나 장치에 의존
EX) 코드에 DB와 통신하는 부분이 포함될경우 통신, DB 성능에 따라 실행시간이 달라짐
CPU 바운드 알고리즘에서의 동시성과 병렬성
- 다중코어에서 병렬성을 활용하면 성능향상가능 : 단어 3000개 검사시 1000개당 스레드 1개 생성 및 실행 → 스레드 3개에서 계산 → 순차적 실행의 약 3분의 1로 단축
- 단일 코어에서 동시성을 구현하면 성능저하가능
- 하나의 코어가 3개 스레드사이에서 교차배치
- 매번 일정량 단어 검사 후 다음 스레드로 전환
- 컨텍스트 스위칭의 오버헤드 발생 → 순차가 더 나을수도 있다. 컨텍스트 스위칭 오버헤드 없음.
CPU 바운드 알고리즘을 위해서는 사용중 장치의 코어수를 기준으로 적절한 스레드 수를 생성해야 한다
I/O 바운드 알고리즘에서의 동시성 대 병렬성
- 단일 코어 기기에서 대기하는 중에 다른 작업에 프로세스 사용 가능
- 멀티/싱글 코어 상관없이 유사하게 수행
I/O 작업은 늘 동시성으로 실행하는 편이 좋다
코틀린이 우리가 만든 동시성 코드를 동기화하고 통신할 수 있도록 하여 실행흐름이 바뀌어도 애플리케이션 작동에는 영향이 없다.
레이스 컨디션
- 동시성 코드 일부가 제대로 작동하기 위해 일정한 순서로 완료되어야 할 때 발생
lateinit var user : UserInfo fun main(...) = runBlocking {
asyncGetUserInfo(1) // user 정보 가져와 세팅
println("User:$user")
}
fun asyncGetUserInfo(id: Int) = async { user = UserInfo(...) }
asyncGetUserInfo() 완료전에 println() 이 실행되면 유저 정보를 출력 할 수 없음!
원자성 위반
원자성은 객체의 상태가 동시에 수정될 수 있을때 필요 그 상태의 수정이 겹치지 않도록 보장해야함
→EX) 코루틴 1이 코루틴 2가 수정하고있는 데이터를 바꿀 수 있음!
교착 상태
순환적 의존성으로 인해, 서로 작업을 기다리는 상황
잠금 연관관계(network of locks)에 의해 발생 레이스 컨디션과 자주 함께 발생
라이브락
교착상태를 복구하려는 시도에서 발생가능
→ EX) 사람1, 사람2가 좁은 복도에서 마주걸어오는 경우 : 서로 같은 쪽으로 계속 피하면 복도를 지날 수 없음
넌 블로킹
- 코틀린은 **중단 가능한 연산(Suspendable Computations)**이라는 기능을 제공 : 스레드의 실행을 블로킹하지 않으며 실행을 잠시 중단 EX) 스레드A에서 작업이 끝나기를 기다리려면 스레드B를 블록킹 하지 않고, 대기해야하는 코드를 일시중단함. 그동안 스레드B를 다른 연산에 사용
명시적인 선언
- 동시성은 연산이 동시에 실행돼야하는 시점을 명시적으로 만드는것이 중요! 중단 가능한 연산은 기본적으로 순차실행 됨
fun main(...) = runBlocking {
val time = measureTimeMillis {
val name = getName()
val lastName = getLastName()
println("Hello, $name $lastName")
}
println("Excecution took $time ms")
}
suspend fun getName() : String {
delay(1000)
return "Susan"
}
suspend fun getLastName() : String {
delay(1000)
return "Calvin"
}
main()은 현재 스레드에서 getName(), getLastName() 순차 실행 → 2016 ms 소요
: getName(), getLastName() 서로 의존성이 없어 동시 수행 가능
fun main(...) = runBlocking {
val time = measureTimeMillis {
val name = async{ getName() }
val lastName = async{ getLastName() }
println("Hello, ${name.await()} ${lastName.await()}")
}
println("Excecution took $time ms")
}
→ 1019 ms 소요
가독성
suspend fun getProfile(id :Int){
val basicUserInfo = asyncGetUserInfo(id)
val contactInfo = asyncGetContactInfo(id)
return createProfile(basicUserInfo.await(),contactInfo.await())
}
→ suspend 메소드는 백그라운드 스레드에서 실행될 두 메소드를 호출하고 정보를 처리하기전에 완료를 기다림
- 비동기 함수작성 대신→ suspend 함수 작성 및 async{} launch{} 안에서 호출 권장 : 사용에 더 많은 유연성 제공
기본형 활용
코틀린은 고급 함수와 기본형을 제공
newSingleThreadContext() : 스레드 생성
newFixedThreadPoolContext() : 스레드 풀 생성
CommonPool : CPU 바운드 작업에 최적인 스레드 풀. 최대크기: 시스템 코어-1
유연성 유연하게 동시성을 사용할수 있는 기본형을 다수 제공.
- 채널 : 코루틴간 데이터를 보내고 받는 파이프
- 작업자 풀: 다수 스레드에서 연산 집합의 처리를 나눌수 있음
- 액터 : 채널과 코루틴을 사용하는 상태를 감싼 래퍼. 여러 스레드에서 상태를 안전하게 수정하는 매커니즘 제공
- 뮤텍스 : 크리티컬 존 영역을 정의하여 한번에 하나의 스레드만 실행할수 있도록 하는 동기화 매커니즘. 해당 존에 액세스 하려는 코루틴은 이전 코루틴이 빠져나올때까지 일시정지
- 스레드 한정: 코루틴 실행을 제한하여 지정된 스레드에서만 실행
- 생성자 : 필요에 따라 정보생성, 새로운 정보가 필요없을때 일시중단 될 수 있는 데이터 소스
일시 중단 연산(Suspendable Computations) : 해당 스레드를 block 하지 않고 실행을 일시 중지 할수 있는 연산 스레드를 다른 연산에서 사용할 수 있음
일시 중단 함수 : 함수형식의 일시 중단 연산. suspend 제어자 사용 함수가 중지 된 동안 실행 스레드가 다른 연산에 사용될 수 있음
EX) suspend fun getName()
람다 일시 중단 : 익명 로컬함수. 다른 일시중단 함수를 호출함으로써 자신의 실행을 중단할 수 있음
코루틴 디스패쳐 : 코루틴을 시작하거나 재개할 스레드를 결정하기 위해 사용됨
코루틴 빌더 : 일시중단 람다를 받아 실행시키는 코루틴을 생성하는 함수
- async() : 결과가 예상되는 코루틴을 시작하는데 사용
코루틴 내부에서 일어나는 모든 예외를 결과에 넣기때문에 조심해서 사용해야함.
결과 또는 예외를 포함하는 Defferred<T> 반환
- launch() : 결과를 반환하지 않는 코루틴을 시작. 자체 혹은 자식 코루틴의 실행을 취소하기위해 사용할 수 있는 Job을 반환.
- runBlocking() : 코루틴의 실행이 끝날때까지 현재스레드를 차단. 블로킹 코드를 일시 중지 가능한 코드로 연결하기 위해 작성됨.
보통 main() 혹은 유닛테스트에서 사용.
댓글