This content is primarily based on the official documentation of Inside Java and Kotlin Docs
Ron Pressler가 강조하는 가장 핵심적인 메시지는 parallelism과 concurrency가 완전히 다른 문제라는 것이다. 많은 사람들이 이 둘을 혼동하는데, 이는 둘 다 "동시에 여러 일을 한다"는 표면적 유사성 때문이다. 하지만 그 본질과 목적, 그리고 해결 방법은 전혀 다르다.
ㅤ
Parallelism은 단일 작업(single job)을 더 빠르게 완료하기 위한 문제다. 큰 리스트를 정렬하거나 행렬을 역전시키는 등의 작업을 여러 처리 장치(CPU 코어)를 활용해 가속화하는 것이 목표다.
ㅤ
Parallel 알고리즘이 직접 subtask를 생성한다. 즉, 여러 작업이 존재하는 이유는 일을 더 빠르게 끝내고 싶기 때문이다.
주된 관심사는 latency(단일 작업의 지속 시간)다. 하나의 큰 작업을 얼마나 빠르게 끝낼 수 있는가가 중요하다.
분할된 작업들은 서로 협력(cooperating)하며, 효율적으로 조율된다.
Thread는 CPU 코어의 프록시로 사용된다. 10개의 코어가 있다면 최적의 thread 수는 10개다. 작업의 크기와 무관하게 thread 수는 코어 수에 의해 결정된다.
ㅤ
Pressler는 이를 "피라냐 떼가 큰 먹이를 먹어치우는 것"에 비유한다. 하나의 큰 목표를 여러 주체가 나눠서 동시에 처리하는 것이다.
ㅤ
Concurrency는 외부에서 들어오는 독립적인 여러 작업들을 제한된 리소스에 효율적으로 스케줄링하는 문제다.
ㅤ
작업들은 외부에서 발생하며, 문제의 일부이지 해결책이 아니다. 즉, 여러 작업이 존재하는 이유는 여러 작업을 처리하는 것 자체가 일이기 때문이다. 전형적인 예가 바로 서버다.
주된 관심사는 throughput(단위 시간당 처리 가능한 작업 수) 이다. 각각의 작업이 얼마나 빨리 끝나는지보다, 전체적으로 얼마나 많은 요청을 처리할 수 있는지가 중요하다.
작업들은 리소스를 놓고 경쟁(competing)한다.
Thread는 독립적으로 진행할 수 있는 여러 순차적 작업들을 동시에 진행시키는 수단이다. Little's Law에 따르면, 동시성 수준(level of concurrency)을 높이면 throughput이 증가한다.
매우 많은 수의 thread가 필요하다. 각 요청이 네트워크 소켓이나 I/O를 필요로 한다면, 코어 수와 무관하게 수천, 수만 개의 thread가 유용할 수 있다.
ㅤ
Pressler는 이를 "도시의 거리를 달리는 자동차들"에 비유한다. 각자 독립적인 목적지를 향해 가는 여러 주체들이 제한된 도로 자원을 공유하는 이미지다.
ㅤ
thread는 두 가지 서로 다른 기능을 수행한다.
공간적 스케줄링(Spatial scheduling): 서로 다른 CPU 코어에 작업을 할당하여 동시에 진행한다. (parallelism)
시간적 스케줄링(Temporal scheduling): 여러 작업이 독립적으로 진행하도록 하되, 반드시 동시일 필요는 없다. (concurrency)
즉, Parallelism은 "공간에 걸친 리소스 스케줄링"이고, Concurrency는 "시간에 걸친 리소스 스케줄링"으로 볼 수 있다.
ㅤ

Asynchronous programming은 blocking을 방지하기 위한 (non-blocking) 프로그래밍 기법이다. 즉, asynchronous programming 는 concurrency 의 한 종류다.
Blocking이란? 프로그램이 어떤 작업(예: 파일 읽기, 네트워크 요청, DB 쿼리)이 완료될 때까지 기다리면서 다른 작업을 수행하지 못하는 상태다.
Blocking은 수십 년간 개발자들이 직면했던 핵심 문제다.
사용자를 기다리게 하지 (blocking) 않아야 한다.
애플리케이션의 확장성을 저해하는 병목현상을 방지해야 한다.
데스크톱, 모바일, 서버 사이드 모두에서 Blocking은 공통적인 문제다.
ㅤ
가장 오래되고 잘 알려진 방법이지만 한계가 명확하다.
Multi-threading은 각 스레드 내에서 blocking이 발생하며, Single threading에서 non-blocking 구현이 가능하다.
Context switch 비용이 크다.
생성 가능한 thread 수가 OS에 의해 제한된다.
일부 플랫폼(JavaScript)에서는 지원하지 않는다.
디버깅과 race condition 회피가 어렵다.
ㅤ
함수를 파라미터로 전달하여 완료 시 호출하는 방식이다.
Callback Hell: 중첩된 콜백들이 피라미드 모양을 만들며 코드가 난해해진다.
에러 처리가 복잡하다.
프로그램의 흐름을 따라가기 어렵다.
ㅤ
호출 시 나중에 완료될 것을 약속하는 객체를 반환한다.
Top-down 명령형에서 compositional 체이닝으로 전환한다.
새로운 API 학습이 필요하다.
반환 타입이 실제 필요한 데이터가 아닌 Promise 타입으로 변경한다.
에러 전파와 체이닝이 여전히 복잡하다.
ㅤ
Erik Meijer가 C#에 도입하고 Netflix가 Java로 포팅한 RxJava를 통해 주류로 자리잡은 접근법이다.
"모든 것은 스트림이고, 관찰 가능하다(Everything is a stream, and it's observable)"
데이터를 스트림(무한한 양의 데이터)으로 생각한다.
Observer Pattern의 확장이다.
스트림에 대한 다양한 연산 제공한다.
ㅤ
Futures와 유사하지만, Future가 단일 요소를 반환한다면 Rx는 스트림을 반환한다.
많은 플랫폼으로 포팅되어 일관된 API 경험을 제공한다.
향상된 에러 처리 방식이다.
ㅤ
완전히 새로운 사고방식 필요하다.
동기 코드와는 매우 다른 접근이다.
문제 해결 방식의 큰 전환 필요하다.
Rx가 강력하지만, 근본적으로 프로그래밍 패러다임의 전환을 요구한다.
ㅤ
Kotlin의 coroutines는 suspendable computations의 개념, 즉 함수가 특정 시점에 실행을 멈추고 나중에 재개할 수 있다는 아이디어에 기반한다.
suspend 함수는 함수가 실행되고(execute), 실행을 멈추고(pause), 나중에 재개한다.(resume)
실행 시작: 함수가 호출되면 정상적으로 실행한다.
일시 중단: 특정 지점(보통 I/O 작업이나 다른 suspend 함수 호출)에서 실행을 멈춘다.
Thread 해방: 중요한 점은, 멈춘 동안 thread가 블로킹되지 않고 다른 일을 할 수 있다는 것이다.
재개: 작업이 완료되면 멈췄던 지점부터 다시 실행을 계속한다.
여기서 중단은 Blocking 이 아닌 Suspending 으로 사용법은 유사하나 전자는 쓰레드 자체를 대기 상태로 전환시키나 후자는 동일 쓰레드에서 다른 코루틴을 실행시킨다.
플랫폼에 독립적이다.
JVM, JavaScript 또는 다른 어떤 플랫폼을 타겟으로 하든, 작성하는 코드는 동일하다.
내부적으로 컴파일러가 각 플랫폼에 맞게 적응한다.
ㅤ