동기와 비동기에 대해 알아보면서 비슷한 개념인 블로킹 / 논블로킹에 대해서도 알아보겠습니다.
동기와 비동기 란?
- 어원
- Synchronous 의 Syn는 그리스어로 ‘함께’의 뜻
- chorono는 ‘시간’ 이라는 뜻
—> 요청한 작업에 대해 완료 여부를 따져 순차대로 처리하는 뜻 - 정의
- 즉, 동기(Synchronous)는 작업을 순차적으로 대로 처리하는 것을 말합니다. 비동기는(Asynchronous) 는 작업의 순서가 지켜지지 않을 수 있다는 것을 말합니다.
- 핵심개념
- 프로그래밍에서 동기( Synchronous)는 다른작업을 처리 할 수 없고 기다린다 는 개념입니다.
- 비동기(Asynchronous)는 요청을 작업을 다른 쓰레드에서 시키고 끝날 때 까지 기다리지 않는다는 개념입니다.
동기
func functionA() {
print("Start Func A")
funcB()
print("End Func A")
}
func funcB() {
print("Start Func B")
funcC()
print("End Func B")
}
func funcC() {
print("Start Func C")
print("End Func C")
}
functionA()
비동기 코드
func functionA() {
print("Start Func A")
DispatchQueue.main.async {
funcB()
}
print("End Func A")
}
func funcB() {
print("Start Func B")
DispatchQueue.main.async {
funcC()
}
print("End Func B")
}
func funcC() {
print("Start Func C")
print("End Func C")
}
functionA()
블로킹과 논블로킹이란?
- 어원
- block : 차단하다 / 막다
- 정의
- 어원에서 알 수 있듯 다른요청의 작업을 처리하기 위해 현재 작업을 차단/대기(block) 한다는 의미
- 핵심개념
- 동기 / 비동기가 작업의 순차적 흐름에 대한 유무라면 블로킹 논 블로킹은 전체적인 작업의 흐름을 막느냐 막지 않느냐의 유무 ex) 파일을 읽을 때, 블로킹 방식으로 읽으면 파일을 다 읽을때까지 대기하고, 논블로킹 방식은 파일을 다 읽지 않고 다른 작업을 진행할 수 있다.
비동기와 논블로킹의 헷갈림 포인트와 차이
글을 작성하면서 비동기와 논블로킹의 개념이 다른것 같으면서도 비슷한 것 같아 계속 혼동됐습니다.
이를 구분하기 위해 조금 더 자세하게 설명해보겠습니다.
- 동기 / 비동기는 출력순서 에 관련된 개념이고 블로킹 논블로킹은 병렬실행 즉, 제어권에 관련된 개념입니다.
(A 함수가 B함수를 호출했을 떄 제어권을 B에게 주면 blocking / 안주면 non-blocking) - 제어권을 넘기고나면 A함수는 실행을 멈춥니다.
- B함수가 완료되고 제어권을 돌려주면 A함수는 그 다음 작업을 실행합니다.
즉, 동기 / 비동기는 시간적 개념, 블로킹 / 논블로킹은 동작방식의 개념입니다.
비동기는 작업의 결과를 언제 받을 수 있을지 모르기 때문에 기다리지 않고 다른일을 할 수 있도록 처리하고 콜백, 알림 등을 통해서 결과를 전달 받습니다. 이 과정에서 비동기는 블로킹일수도, 논블로킹일수도 있습니다.
그러나 논블로킹의 초점은 요청 후 프로그램이 멈추지않는 것에 더 맞춰져있습니다. 요청에 대한 응답이 즉시 돌아옵니다. 이는 작업의 결과가 아니라 작업 요청에 대한 응답입니다. 즉, 작업 자체는 아직 완료되지 않았더라도, 작업 요청자체가 즉시 반환된다는 의미입니다.
즉시반환이란, 작업이 완료되지 않아도 작업 요청을 보내는 시점에서 바로 다른 작업을 수행할 수 있다는 것을 의미합니다.
그러니까 정리하자면, 요청 이후 결과를 기다리지않고 다른 일을 할 수 있다는 개념에 대해서는 공통점이 있지만 비동기는 완료시점에 결과를 받아서 처리하고 논블로킹은 결과가 나중에 나올 수 있지만 기다리지 않고 계속 프로그램이 진행된다는 점에서 차이점을 가집니다.
간단한 예시를 들어본다면,
- 논블로킹
- 주문을 하고 주문이 바로 접수되었다는 알림을 받는 것과 같습니다. 음식은 아직 나오지 않았지만 다른 일을 할 수 있습니다. 음식이 준비되었든 아니든 그 순간에 다른일을 할 수 있습니다.
- 비동기
- 마찬가지로 비동기 또한 주문 후 다른일을 할 수 있지만, 음식이 준비되면 반드시 준비가됐다는 알림을 받고 그때 가서 음식을 받아야합니다(콜백, 컴플리션, 알림 등). 또한 이 음식을 받아야 다음 단계를 진행 할 수 있습니다.
동기 / 블로킹 조합
sync - blocking
다른 작업이 진행되는 동안 자신의 작업을 처리하지 않고 (blocking), 완료 여부를 받아 순차적으로(sync) 처리하는 방식입니다. 다른 작업의 결과가 자신의 작업에 영향을 주는 경우에 사용합니다.
func syncBlocking() {
print("Start")
let data1 = readFile(fileNum: 1)
print(data1)
let data2 = readFile(fileNum: 2)
print(data2)
let data3 = readFile(fileNum: 3)
print(data3)
print("End")
}
func readFile(fileNum: Int) -> Int{
Thread.sleep(forTimeInterval: 1)
let fileNum = fileNum
return fileNum
}
---
Start
1
2
3
End
출력
파일을 읽어오는 함수가 있다고 가정했을 때, 순차적으로 코드가 실행됩니다.
그러나 이런방식으로 불러오는 데이터의 양이 많을 때는 당연히 그만큼 시간이 많이 걸리게 됩니다.
왜냐하면 이러한 방식은 작업이 끝날때까지 다른 작업을 처리하지 못하므로 전체 처리시간이 비효율적이기 때문입니다.
따라서 이러한 조합은 일반적으로 간단한 테스크나 데이터의 양이 적은경우에 사용됩니다.
async - Non Blocking
다른 작업이 진행되는 동안에도 자신의 작업을 처리하고 (Non-Blocking)
다른 작업의 결과를 바로 처리하여 작업을 순차대로 수행(Sync)하는 방식입니다.
다른 작업의 결과가 자신의 작업에 영향을 주지 않은 경우에 사용합니다.
func asyncNonBlocking() {
print("Strart function A")
DispatchQueue.main.async {
readFile(fileNum: 1) { num in
print(num)
}
}
print("End function A")
}
func readFile(fileNum: Int, completion: ((Int) -> Void)) {
let fileNum = fileNum
completion(fileNum)
}
---
Start function A
End function A
1
출력
functionA()가 호출되면 비동기 작업이 DispatchQueue.main.async로 예약됩니다.
이 비동기 작업은 메인 스레드에서 실행되며, 논블로킹 방식으로 작동합니다.
DispatchQueue.main.async는 비동기적으로 readFile(fileNum: 1) 호출을 예약합니다. 이 예약이 완료되면, 다음 코드(즉, print("End function A"))로 즉시 넘어갑니다.
이때, DispatchQueue.main.async로 던져진 비동기 작업이 끝나기를 기다리지 않습니다. 따라서, 비동기 + 논블로킹 방식으로 작동합니다.
이후, 예약된 readFile(fileNum: 1)이 실행되고 completion 핸들러가 즉시 호출되어 1을 출력합니다.
하지만 이 작업은 비동기적으로 예약되었기 때문에, End function A가 먼저 출력된 후 1이 출력됩니다.
해당 코드는 DispatchQueue.main.async 가 실행될 때 비동기적으로 작업을 던지고 완료 여보와 관계없이 바로 다음 코드를 실행합니다. 따라서 이는 코드의 흐름대로 순서가 보장되지도 않고(Async), 비동기 작업후 프로그램의 블로킹 없이 다음 코드가 실행 되므로 Non-Blocking 입니다.
Sync - Non Blocking
다른 작업이 진행되는 동안에도 자신의 작업을 처리하고 (Non-Blocking)
다른 작업의 결과를 바로 처리하여 작업을 순차대로 수행(Sync)하는 방식입니다.
// 비동기로 실행할 작업을 수행할 함수 정의
func myTask() {
print("Hello from a thread!")
}
func syncNonBlocking() {
// Thread 객체 생성 및 실행
let thread = Thread {
myTask() // 비동기 작업 실행
}
// 스레드 시작
thread.start()
// Non-blocking이므로 다른 작업 계속 가능
print("Main thread is running...")
// Sync를 위해 스레드의 작업 완료 여부 확인
while thread.isExecuting {
print("Waiting for the thread to finish...")
Thread.sleep(forTimeInterval: 0.5) // 스레드 상태를 주기적으로 확인
}
print("Thread finished!")
print("Run the next tasks")
}
---
Main thread is running...
Hello from a thread!
Waiting for the thread to finish...
Waiting for the thread to finish...
Waiting for the thread to finish...
Waiting for the thread to finish...
Thread finished!
Run the next tasks
스레드를 사용하여 병렬적으로 요청했지만 while 반복문 수행으로 완료 여부를 계속 확인하기 때문에
결과적으로는 전체 테스크는 동기적으로 수행됩니다.
이러한 sync - Non blocking 프로그램의 예시로 게임로딩을 들 수 있습니다. 게임에서 맵을 이동할 때, 맵 데이터를 모두 다운로드 해야하고, 그 동안 화면에는 로딩바를 채우는 프로그램이 수행됩니다.
이는 제어권이 맵 다운로드하는 함수에 주지 않고 로딩율을 채우면서, 지속적으로 맵데이터 다운로드율을 조회합니다.
진행 중이던 작업을 계속하고 있지만, 다른 작업과의 동기를 위해 계속해서 다른 작업을 조회하기 때문에, sync- Non Blocking 방식이라고 할 수 있습니다.
여기까지 동기와 비동기 & 블로킹 논블로킹 함수에 대해서 알아봤습니다. 솔직히 너무 방대하고 비슷한 부분이 많은 개념이라 말로풀어서 설명하는 게 쉽지는 않았는데, concurrency 에 대해서 자세하게 공부할 차례가 올 것 같아서 그 때 Swift의 비동기함수에 대해서 더 자세하게 다룰 생각입니다.
피드백은 언제나 환영입니다!
참조
'iOS' 카테고리의 다른 글
[iOS] Fastlane을 사용한 In House(ipa) CI/CD 구축 (1) | 2024.11.06 |
---|---|
객체 지향 프로그래밍적 사고 - 3 : OOP의 4가지 특징 (1) | 2024.08.30 |
객체지향 프로그래밍적 사고 - 2 (0) | 2024.08.16 |
객체지향 프로그래밍적 사고 (0) | 2024.08.13 |
.DS_Store 파일이란? (0) | 2024.04.16 |
동기와 비동기에 대해 알아보면서 비슷한 개념인 블로킹 / 논블로킹에 대해서도 알아보겠습니다.
동기와 비동기 란?
- 어원
- Synchronous 의 Syn는 그리스어로 ‘함께’의 뜻
- chorono는 ‘시간’ 이라는 뜻
—> 요청한 작업에 대해 완료 여부를 따져 순차대로 처리하는 뜻 - 정의
- 즉, 동기(Synchronous)는 작업을 순차적으로 대로 처리하는 것을 말합니다. 비동기는(Asynchronous) 는 작업의 순서가 지켜지지 않을 수 있다는 것을 말합니다.
- 핵심개념
- 프로그래밍에서 동기( Synchronous)는 다른작업을 처리 할 수 없고 기다린다 는 개념입니다.
- 비동기(Asynchronous)는 요청을 작업을 다른 쓰레드에서 시키고 끝날 때 까지 기다리지 않는다는 개념입니다.
동기
func functionA() {
print("Start Func A")
funcB()
print("End Func A")
}
func funcB() {
print("Start Func B")
funcC()
print("End Func B")
}
func funcC() {
print("Start Func C")
print("End Func C")
}
functionA()
비동기 코드
func functionA() {
print("Start Func A")
DispatchQueue.main.async {
funcB()
}
print("End Func A")
}
func funcB() {
print("Start Func B")
DispatchQueue.main.async {
funcC()
}
print("End Func B")
}
func funcC() {
print("Start Func C")
print("End Func C")
}
functionA()
블로킹과 논블로킹이란?
- 어원
- block : 차단하다 / 막다
- 정의
- 어원에서 알 수 있듯 다른요청의 작업을 처리하기 위해 현재 작업을 차단/대기(block) 한다는 의미
- 핵심개념
- 동기 / 비동기가 작업의 순차적 흐름에 대한 유무라면 블로킹 논 블로킹은 전체적인 작업의 흐름을 막느냐 막지 않느냐의 유무 ex) 파일을 읽을 때, 블로킹 방식으로 읽으면 파일을 다 읽을때까지 대기하고, 논블로킹 방식은 파일을 다 읽지 않고 다른 작업을 진행할 수 있다.
비동기와 논블로킹의 헷갈림 포인트와 차이
글을 작성하면서 비동기와 논블로킹의 개념이 다른것 같으면서도 비슷한 것 같아 계속 혼동됐습니다.
이를 구분하기 위해 조금 더 자세하게 설명해보겠습니다.
- 동기 / 비동기는 출력순서 에 관련된 개념이고 블로킹 논블로킹은 병렬실행 즉, 제어권에 관련된 개념입니다.
(A 함수가 B함수를 호출했을 떄 제어권을 B에게 주면 blocking / 안주면 non-blocking) - 제어권을 넘기고나면 A함수는 실행을 멈춥니다.
- B함수가 완료되고 제어권을 돌려주면 A함수는 그 다음 작업을 실행합니다.
즉, 동기 / 비동기는 시간적 개념, 블로킹 / 논블로킹은 동작방식의 개념입니다.
비동기는 작업의 결과를 언제 받을 수 있을지 모르기 때문에 기다리지 않고 다른일을 할 수 있도록 처리하고 콜백, 알림 등을 통해서 결과를 전달 받습니다. 이 과정에서 비동기는 블로킹일수도, 논블로킹일수도 있습니다.
그러나 논블로킹의 초점은 요청 후 프로그램이 멈추지않는 것에 더 맞춰져있습니다. 요청에 대한 응답이 즉시 돌아옵니다. 이는 작업의 결과가 아니라 작업 요청에 대한 응답입니다. 즉, 작업 자체는 아직 완료되지 않았더라도, 작업 요청자체가 즉시 반환된다는 의미입니다.
즉시반환이란, 작업이 완료되지 않아도 작업 요청을 보내는 시점에서 바로 다른 작업을 수행할 수 있다는 것을 의미합니다.
그러니까 정리하자면, 요청 이후 결과를 기다리지않고 다른 일을 할 수 있다는 개념에 대해서는 공통점이 있지만 비동기는 완료시점에 결과를 받아서 처리하고 논블로킹은 결과가 나중에 나올 수 있지만 기다리지 않고 계속 프로그램이 진행된다는 점에서 차이점을 가집니다.
간단한 예시를 들어본다면,
- 논블로킹
- 주문을 하고 주문이 바로 접수되었다는 알림을 받는 것과 같습니다. 음식은 아직 나오지 않았지만 다른 일을 할 수 있습니다. 음식이 준비되었든 아니든 그 순간에 다른일을 할 수 있습니다.
- 비동기
- 마찬가지로 비동기 또한 주문 후 다른일을 할 수 있지만, 음식이 준비되면 반드시 준비가됐다는 알림을 받고 그때 가서 음식을 받아야합니다(콜백, 컴플리션, 알림 등). 또한 이 음식을 받아야 다음 단계를 진행 할 수 있습니다.
동기 / 블로킹 조합
sync - blocking
다른 작업이 진행되는 동안 자신의 작업을 처리하지 않고 (blocking), 완료 여부를 받아 순차적으로(sync) 처리하는 방식입니다. 다른 작업의 결과가 자신의 작업에 영향을 주는 경우에 사용합니다.
func syncBlocking() {
print("Start")
let data1 = readFile(fileNum: 1)
print(data1)
let data2 = readFile(fileNum: 2)
print(data2)
let data3 = readFile(fileNum: 3)
print(data3)
print("End")
}
func readFile(fileNum: Int) -> Int{
Thread.sleep(forTimeInterval: 1)
let fileNum = fileNum
return fileNum
}
---
Start
1
2
3
End
출력
파일을 읽어오는 함수가 있다고 가정했을 때, 순차적으로 코드가 실행됩니다.
그러나 이런방식으로 불러오는 데이터의 양이 많을 때는 당연히 그만큼 시간이 많이 걸리게 됩니다.
왜냐하면 이러한 방식은 작업이 끝날때까지 다른 작업을 처리하지 못하므로 전체 처리시간이 비효율적이기 때문입니다.
따라서 이러한 조합은 일반적으로 간단한 테스크나 데이터의 양이 적은경우에 사용됩니다.
async - Non Blocking
다른 작업이 진행되는 동안에도 자신의 작업을 처리하고 (Non-Blocking)
다른 작업의 결과를 바로 처리하여 작업을 순차대로 수행(Sync)하는 방식입니다.
다른 작업의 결과가 자신의 작업에 영향을 주지 않은 경우에 사용합니다.
func asyncNonBlocking() {
print("Strart function A")
DispatchQueue.main.async {
readFile(fileNum: 1) { num in
print(num)
}
}
print("End function A")
}
func readFile(fileNum: Int, completion: ((Int) -> Void)) {
let fileNum = fileNum
completion(fileNum)
}
---
Start function A
End function A
1
출력
functionA()가 호출되면 비동기 작업이 DispatchQueue.main.async로 예약됩니다.
이 비동기 작업은 메인 스레드에서 실행되며, 논블로킹 방식으로 작동합니다.
DispatchQueue.main.async는 비동기적으로 readFile(fileNum: 1) 호출을 예약합니다. 이 예약이 완료되면, 다음 코드(즉, print("End function A"))로 즉시 넘어갑니다.
이때, DispatchQueue.main.async로 던져진 비동기 작업이 끝나기를 기다리지 않습니다. 따라서, 비동기 + 논블로킹 방식으로 작동합니다.
이후, 예약된 readFile(fileNum: 1)이 실행되고 completion 핸들러가 즉시 호출되어 1을 출력합니다.
하지만 이 작업은 비동기적으로 예약되었기 때문에, End function A가 먼저 출력된 후 1이 출력됩니다.
해당 코드는 DispatchQueue.main.async 가 실행될 때 비동기적으로 작업을 던지고 완료 여보와 관계없이 바로 다음 코드를 실행합니다. 따라서 이는 코드의 흐름대로 순서가 보장되지도 않고(Async), 비동기 작업후 프로그램의 블로킹 없이 다음 코드가 실행 되므로 Non-Blocking 입니다.
Sync - Non Blocking
다른 작업이 진행되는 동안에도 자신의 작업을 처리하고 (Non-Blocking)
다른 작업의 결과를 바로 처리하여 작업을 순차대로 수행(Sync)하는 방식입니다.
// 비동기로 실행할 작업을 수행할 함수 정의
func myTask() {
print("Hello from a thread!")
}
func syncNonBlocking() {
// Thread 객체 생성 및 실행
let thread = Thread {
myTask() // 비동기 작업 실행
}
// 스레드 시작
thread.start()
// Non-blocking이므로 다른 작업 계속 가능
print("Main thread is running...")
// Sync를 위해 스레드의 작업 완료 여부 확인
while thread.isExecuting {
print("Waiting for the thread to finish...")
Thread.sleep(forTimeInterval: 0.5) // 스레드 상태를 주기적으로 확인
}
print("Thread finished!")
print("Run the next tasks")
}
---
Main thread is running...
Hello from a thread!
Waiting for the thread to finish...
Waiting for the thread to finish...
Waiting for the thread to finish...
Waiting for the thread to finish...
Thread finished!
Run the next tasks
스레드를 사용하여 병렬적으로 요청했지만 while 반복문 수행으로 완료 여부를 계속 확인하기 때문에
결과적으로는 전체 테스크는 동기적으로 수행됩니다.
이러한 sync - Non blocking 프로그램의 예시로 게임로딩을 들 수 있습니다. 게임에서 맵을 이동할 때, 맵 데이터를 모두 다운로드 해야하고, 그 동안 화면에는 로딩바를 채우는 프로그램이 수행됩니다.
이는 제어권이 맵 다운로드하는 함수에 주지 않고 로딩율을 채우면서, 지속적으로 맵데이터 다운로드율을 조회합니다.
진행 중이던 작업을 계속하고 있지만, 다른 작업과의 동기를 위해 계속해서 다른 작업을 조회하기 때문에, sync- Non Blocking 방식이라고 할 수 있습니다.
여기까지 동기와 비동기 & 블로킹 논블로킹 함수에 대해서 알아봤습니다. 솔직히 너무 방대하고 비슷한 부분이 많은 개념이라 말로풀어서 설명하는 게 쉽지는 않았는데, concurrency 에 대해서 자세하게 공부할 차례가 올 것 같아서 그 때 Swift의 비동기함수에 대해서 더 자세하게 다룰 생각입니다.
피드백은 언제나 환영입니다!
참조
'iOS' 카테고리의 다른 글
[iOS] Fastlane을 사용한 In House(ipa) CI/CD 구축 (1) | 2024.11.06 |
---|---|
객체 지향 프로그래밍적 사고 - 3 : OOP의 4가지 특징 (1) | 2024.08.30 |
객체지향 프로그래밍적 사고 - 2 (0) | 2024.08.16 |
객체지향 프로그래밍적 사고 (0) | 2024.08.13 |
.DS_Store 파일이란? (0) | 2024.04.16 |