이번엔 컨텍스트 스위칭에 대해서 이야기해보려합니다.
이전 동시성 프로그래밍에 관련한 키워드에 대해 이야기했었는데, 내용이 상당히 길었죠?
저도 좀 쓰면서 오반데....했습니다..🙏
이번에는 이후 코루틴에 대해 이야기하기 전에 알아야 할 선수지식들에 대해서 포스팅을 해보려합니다.
위 항목들에 대해 다뤄야 추후 얘기할 코루틴과 Spring Webflux에 대해 조금이나마 도움이 될 듯하여 포스팅을 해볼까합니다.
(위 내용을 공부해도 Webflux는 어려운 내용입니다..)
우선 운영체제 영역에서 공부를 좀 해보셨다면 멀티 프로세싱/멀티 스레딩에 대해 공부를 좀 해보셨을 겁니다. 저는 학점을 위한 공부를 했어서 다시 상기시킬겸 공부를 해봤는데 미리미리 좀 할걸 그랬습니다..😅
뒷 내용 컨텍스트 스위칭에 대해 이야기하기 전 잠깐 알아보겠습니다.
멀티 프로세싱
멀티 프로세스는 여러 개의 독립적인 CPU를 사용해 여러 프로세스를 병렬적으로 수행하는 것입니다.
이는 각 프로세스가 서로 영향을 주지 않으므로 안정성이 높지만, 프로세스 간 통신(IPC)을 위한 추가적인 오버헤드가 발생합니다.
멀티 프로세스의 장점은 위와 같이 각 프로세스가 독립된 메모리 공간을 가지기 때문에 하나의 프로세스가 실패해도 다른 프로세스에 영향을 주지 않는다는 점입니다.
단점으로는 프로세스 간 통신 비용이 높고, 메모리 사용량이 많다는 점을 들 수 있습니다. 각 프로세스가 독립된 메모리 공간을 가지므로, 같은 데이터를 여러 프로세스에서 사용할 경우 중복 저장되어 메모리 사용량이 증가할 수도 있습니다.
예를 들어, 웹 서버와 데이터베이스 서버를 별도의 프로세스로 실행하여, 하나의 서버에 문제가 발생해도 다른 서버는 정상적으로 작동할 수 있습니다.
멀티 스레딩
멀티 스레드는 하나의 프로세스 내에서 여러 스레드가 메모리를 공유하며 동시에 실행되는 방식입니다. 이는 프로세스 생성 비용보다 스레드 생성 비용이 훨씬 낮으며, 스레드 간 데이터 공유가 용이합니다.
멀티스레딩은 CPU의 사용률을 극대화하고, I/O 작업이 블로킹되는 동안 다른 스레드가 CPU를 사용할 수 있게 하기 때문입니다. 또한, 멀티스레딩은 프로그램의 구조를 단순화할 수 있으며, 사용자 인터페이스와 같은 비동기 작업을 용이하게 합니다. 이처럼 컴퓨터의 여러 코어 자원을 효율적으로 활용할 수 있어 대규모 연산이나 I/O 작업에서 큰 장점을 가질 수 있습니다
또 다른 장점으로는 메모리 공유로 인한 효율성입니다. 왜냐하면 스레드 간 데이터를 공유할 수 있으므로, 데이터 복사 비용이 줄어들고, 통신 비용이 낮아집니다. 각 요청을 별도의 스레드에서 처리하여 처리 속도를 높일 수 있습니다.
단점으로는 스레드 간의 동기화 문제가 발생할 수 있다는 점입니다. 공유된 메모리에 여러 스레드가 동시에 접근할 경우, 한없이 기다리는 데드락이나 레이스 컨디션에 빠지게됩니다. 이에 대한 적절한 동기화 기법으로 데이터의 일관성을 유지하기 위한 추가적인 작업이 필요합니다. 그 외 디버깅과 오버헤드에 단점이 존재합니다.
간단히 멀티 프로세싱/멀티 스레딩 이야기를 해보았습니다.
다음 본격적인 컨텍스트 스위칭에 대해 알아보겠습니다.
CPU가 어떤 프로세스를 실행하고 있는 상태에서 인터럽트에 의해 다음 우선 순위를 가진 프로세스가 실행되어야 할 때 기존의 프로세스 정보들은 PCB에 저장하고 다음 프로세스의 정보를 PCB에서 가져와 교체하는 작업을 컨텍스트 스위칭이라 합니다. 이러한 컨텍스트 스위칭을 통해 우리는 멀티 프로세싱, 멀티 스레딩 운영이 가능합니다.
컨텍스트 스위칭 발생 시점
컨텍스트 스위칭 동작 과정
컨텍스트 스위칭 비용
컨텍스트 스위칭에는 오버헤드(Overhead)가 발생합니다. 즉, CPU가 작업을 전환할 때 상태를 저장하고 복원하는 과정은 일정한 시간이 소요되며, 이는 CPU 자원을 소모하게 됩니다. 컨텍스트 스위칭이 빈번하게 일어나면 CPU가 실제 작업을 처리하는 시간보다 오히려 스위칭에 소요되는 시간이 많아질 수 있습니다.
비용이 발생하는 이유는?
스케줄링: 특정 스레드를 특정 코어에 고정시켜 캐시의 재사용률을 높이는 기법으로, 하드웨어적으로 캐시 및 TLB 히트율을 높일 수 있습니다.
스레드 풀(Thread Pool) 사용: 스레드 수 조정 및 효율적 관리로 새로운 스레드를 계속 생성하는 대신, 일정한 수의 스레드를 미리 만들어서 재사용하면 스위칭 빈도를 줄일 수 있습니다.
락(lock) 사용 최소화: 불필요한 락 사용을 줄여 스레드 간 경쟁을 줄이고, 컨텍스트 스위칭 빈도를 줄일 수 있습니다.
비동기 프로그래밍: I/O 작업을 기다리는 동안 CPU가 빈번한 컨텍스트 스위칭을 발생시키지 않도록 비동기 방식으로 작업을 처리합니다.
코루틴 활용: 스레드 단위의 동시성 대신 경량화된 코루틴을 활용하면, 스레드 수준에서 발생하는 컨텍스트 스위칭을 줄일 수 있습니다.
Kotlin의 코루틴은 스레드의 물리적 전환 없이 논리적인 작업 단위 전환을 지원하여 하드웨어적 관점에서도 효율적입니다.
아래는 코루틴을 사용하여 여러 작업을 효율적으로 실행하는 예제입니다. 이 예제는 두 개의 작업을 동시에 실행하며, 하드웨어 레벨의 스레드 전환 없이 동작하는 것을 보여줍니다.
import kotlinx.coroutines.*
fun main() = runBlocking {
println("시작 - ${Thread.currentThread().name}")
// launch를 통해 코루틴을 시작합니다.
val job1 = launch {
task("Task1", 1000)
}
val job2 = launch {
task("Task2", 1500)
}
// 모든 코루틴이 끝날 때까지 기다립니다.
job1.join()
job2.join()
println("완료 - ${Thread.currentThread().name}")
}
suspend fun task(name: String, timeMillis: Long) {
println("$name 시작 - ${Thread.currentThread().name}")
delay(timeMillis) // 비동기적으로 지연시킵니다.
println("$name 완료 - ${Thread.currentThread().name}")
}
이 코드에서는 runBlocking
을 통해 메인 스레드에서 코루틴을 실행하고, launch
와 delay
를 통해 하드웨어적인 스레드 전환 없이 두 개의 작업을 비동기적으로 수행합니다. delay를 사용하는 동안 다른 코루틴이 실행될 수 있어 스레드 전환 없이 비동기 처리가 가능합니다.
Kotlin의 코루틴을 활용하여 스레드 단위의 전환 비용을 절감하고, 하드웨어 자원을 보다 효율적으로 사용할 수 있습니다. 이를 통해 고성능이 요구되는 애플리케이션에서도 보다 효율적인 동시성 처리를 기대할 수 있습니다.
결국 기승전 코루틴으로 끝나는 거 같지만, 코루틴은 경량 스레드로 컨텍스트 스위칭이 이뤄지기에 조금 다뤄봤습니다.
다음에는 Atomic (CAS), syncronized (lock), volatile에 대해 다뤄보겠습니다:)