티스토리 뷰
1. 동기와 비동기 프로그래밍
1-1. 동기 프로그래밍 (Synchronous Programming)
- 동기 방식의 프로그램에서 작업의 실행 흐름은 순차적으로 동작한다.
- 순차적으로 동작하기 때문에 코드를 파악하기 쉽고 디버깅이 쉽다.
- 특정 작업을 실행하는 동안에 다른 작업을 할 수 없다.
fun main() {
// 실시간 주식 정보를 가져온다.
val stock: StockDto = getRealtimeStockData("APPLE")
println("주식 심볼 : $(stock.symbol}")
println("시가 : $(stock.price.open}")
println("종가 : $(stock.price.close}")
println("시가총액 : $(stock.price.marketCap}")
}
1-2. 비동기 프로그래밍 (Asynchronous Programming)
- 비동기 방식의 프로그램에서 작업의 실행 흐름은 기본적으로 순차적이지 않다.
- 비동기 처리 방식은 현재 실행 중인 작업이 끝나는 것을 기다리지 않고 다른 작업을 할 수 있다.
- 서버, 클라이언트 등 모든 환경에서 유용하다.
- UI 애플리케이션의 경우 특정 이벤트가 발생할 경우에 반응하는 동작을 구현해야 하는데 이 때 필수적으로 비동기 프로그래밍을 사용하게 된다.
// 버튼을 누를 때 마다 카운터가 증가하는 예제
const button = document.querySelector('button');
// 'click' 옆의 두 번째 인자가 비동기 콜백
button.addEventListener('click', event => {
button.innerHTML = `클릭 수: $(event.detail)`;
});
- 대부분의 프로그래밍 언어들은 각 언어의 철학에 맞는 다양한 비동기 처리 방법들을 지원한다.
- 대표적으로 Callback, Promise, Future, Async-await, Coroutine 등이 있고 각각 장점과 단점이 존재한다.
2. 비동기 프로그래밍 구현
2-1. Thread
- 가장 기본이 되는 비동기 처리 방식이다.
- Runnable 인터페이스를 사용해 비동기 동작을 수행한다.
- 스레드가 1개인 경우 싱글 스레드, 하나 이상 존재하는 경우 멀티 스레드라고 부른다.
- 멀티 스레드를 사용하면 애플리케이션에서 여러 개의 작업을 동시에 할 수 있다.
fun main() {
for (i in 0..5) {
val thread = Thread(Runnable {
println("current-thread-name : ${Thread.currentThread().name}")
})
thread.start()
}
println("current-thread-name : ${Thread.currentThread().name}")
}
// 호출할 때 마다 출력 결과가 달라짐
- 멀티 스레드를 사용하면 스케쥴링 알고리즘에 의해 스레드가 전환되면서 작업을 처리하는데 이를 컨텍스트 스위칭이라 한다.
- 하나의 프로세스에는 최소한 하나 이상의 스레드가 존재하고 프로세스 내의 스레드들은 동일한 메모리를 공유한다.
- 스레드는 프로세스를 생성하는 것보다 가볍다.
- 스레드가 무한정 많아지면 메모리 사용량이 높아져서 OOME(OutOfMemoryError)가 발생할 수 있고 높은 동시 처리량을 요구하는 시스템에서는 스레드를 생성하면서 발생하는 대기 시간 때문에 응답 지연이 발생한다.
- 이런 문제를 해결하기 위해 스레드 풀(Thread Pool)을 사용한다. 스레드 풀을 사용하면 애플리케이션 내에서 사용할 총 스레드 수를 제한할 수 있고 기존에 생성된 스레드를 재사용하므로 빠른 응답이 가능하다.
- 직접 만드는 것보다 검증된 라이브러리를 사용해야 한다. java.util.concurrent 패키지의 ExecutorService를 사용하면 쉽고 안전하게 스레드 풀을 사용할 수 있다.
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
fun main() {
val pool: ExecutorService = Executors.newFixedThreadPool(5)
try {
for (i in 0..5) {
pool.execute {
println("current-thread-name : ${Thread.currentThread().name}")
}
}
} finally {
pool.shutdown()
}
println("current-thread-name : ${Thread.currentThread().name}")
}
2-2. Future
- 퓨처는 비동기 작업에 대한 결과를 얻고 싶은 경우에 사용된다.
- 예를 들어 수행 시간이 오래 걸리는 작업이나 작업에 대한 결과를 기다리면서 다른 작업을 병행해서 수행하고 싶은 경우에 유용하다.
- 스레드는 Runnable을 사용하고 퓨처는 Callable을 사용한다.
import java.util.concurrent.Callable
import java.util.concurrent.Executors
fun sum(a: Int, b: Int) = a + b
fun main() {
val pool = Executors.newSingleThreadExecutor()
val future = pool.submit(Callable {
sum(100, 200)
})
println("계산 시작")
val futureResult = future.get()
println(futureResult)
println("계산 종료")
}
- Future의 단점
- get 함수는 비동기 작업의 처리가 완료될 때 까지 다음 코드로 넘어가지 않고 무한정 대기하거나 지정해둔 타임아웃 시간까지 블로킹된다.
- 동시에 실행되는 한 개 이상의 비동기 작업에 대한 결과를 하나로 조합하여 처리하거나 수동으로 완료 처리 할 수 있는 방법을 지원하지 않는다.
Reference
'CS > 프로그래밍' 카테고리의 다른 글
리액티브 프로그래밍 (1) | 2022.09.19 |
---|---|
프로그래밍 패러다임 (0) | 2022.09.14 |
절차지향 프로그래밍 (0) | 2022.09.14 |
객체지향 프로그래밍 (0) | 2022.09.14 |
선언형과 함수형 프로그래밍 (0) | 2022.09.14 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 프로세스 컴파일
- 클러스터형인덱스
- 스프링 WebFlux
- 스레드
- 프로그래밍
- 함수형 프로그래밍
- 메모리 계층
- 선언형 프로그래밍
- 중첩루프조인
- 캐시매핑
- 자바
- 스프링 R2DBC
- Design Pattern
- 코틀린
- 직접매핑
- 네트워크
- 연관매핑
- 인덱스최적화
- 대수확장성
- 정렬병합조인
- 네트워크 기초
- 디자인 패턴
- 직접연관매핑
- 세컨더리인덱스
- 프로그래밍 패러다임
- 프로세스
- 보이스코드정규형
- java
- 불연속할당
- 프로세스와 스레드
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
글 보관함