Swift 의 sleep() 함수 정복하기 - Swift Concurrency

민규·2024년 12월 23일
0
post-thumbnail

sleep() 에 대해 알아보자!

안녕하세요!

이번 글에서는 Swift 에서의 sleep() 함수들에 대해 알아보고, Swift Concurrency 의 Task.sleep() 를 어떻게 하면 더 잘 사용할 수 있는지 같이 고민해보도록 하겠습니다!

Blocking 방식의 sleep()

1. sleep() 함수

sleep() 함수는 C 언어를 배우면서 한 번쯤 만나게 되는 근본 함수 중 하나입니다. 이 함수는 현재 스레드의 작업을 딜레이 시키는 간단한 기능을 가지고 있습니다.

print("시작")
sleep(10)
print("10초 지남")

Swift에서도 이 C 언어 기반의 sleep() 함수를 사용할 수 있습니다.

Swift는 macOS 및 iOS 같은 Darwin 기반의 OS 위에서 실행되며, 이들 시스템은 POSIX(Portable Operating System Interface) 표준을 따르기 때문이죠.

POSIX는 C 언어 기반의 함수 라이브러리(unistd.h 등)를 제공하며, 여기서 Swift의 sleep() 함수도 도입된 것입니다.

따라서 Swift에서의 sleep()은 C 언어의 sleep()을 그대로 활용하며, 동일한 방식으로 초 단위의 대기를 처리합니다.

2. usleep() 함수

usleep() 함수는 sleep() 함수의 자매품입니다.

차이점은 초 단위의 대기를 처리하는 sleep() 함수와 달리 usleep() 함수는 마이크로초 단위의 대기를 처리할 수 있어 더 세밀한 시간 조작이 가능하다는 것입니다.

print("시작")
usleep(10000000)
print("10초 지남")

usleep() 함수도 sleep() 함수와 함께 unistd.h 에 포함되어 있습니다.

3. Thread.sleep() 함수

sleep() 함수처럼 호출한 현재 스레드를 차단합니다.

이전의 C 언어 기반의 sleep() 함수와 다르게 Foundation 프레임워크에 포함되어 있으며 조금 더 Swift 스러운 설계로 구현된 함수입니다.

구현부를 살펴보면 두 종류의 방식으로 호출할 수 있습니다.

sleep(until: data: Date)

print("시작")
Thread.sleep(until: .now + 10)
print("10초 지남")

sleep(forTimeInterval ti: TimeInterval)

print("시작")
Thread.sleep(forTimeInterval: 10)
print("10초 지남")

여기까지 스레드를 Blocking 시켜 작업에 딜레이를 주는 sleep() 함수에 대해 알아보았습니다.

하지만 스레드를 Blocking 시키는 방식의 sleep() 에는 크나큰 단점이 있습니다. 바로 비동기 프로그래밍에는 적합하지 않다는 것입니다.

Non-Blocking 방식의 sleep()

1. asyncAfter() 함수

asyncAfter() 함수는 GCD(Grand Central Dispatch) 에 포함된 함수입니다.

// Dispatch > DispatchQueue
@preconcurrency public func asyncAfter(deadline: DispatchTime, qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], execute work: @escaping @Sendable @convention(block) () -> Void

asyncAfter() 함수는 sleep 라는 이름을 가지고 있지는 않지만, 이 글의 메인 주제인 Swift Concurrency 의 Task.sleep() 가 도입되기 전까지 비동기적인 대기를 구현하기 위한 거의 유일한 방법이였습니다.

호출 방법도 간단한데, deadline 에 작업이 종료될 시점을 작성하고 work 의 클로저에 실행할 코드를 작성 후 호출하면 됩니다.

print("시작")
DispatchQueue.global().asyncAfter(deadline: .now() + 10) {
	print("10초 지남")
}

asyncAfter() 의 장점인 Non-Blocking 방식의 비동기적 대기에 대해 조금 더 알아보겠습니다.

asyncAfter() 의 작동은 sleep() 처럼 단순 스레드의 딜레이(대기) 가 아닌 작업의 suspend(중지) 입니다.

(작업 실행을 예약 하고 예약 시작 전까진 잠깐 suspend(중지), 단순히 예약이기 때문에 그 동안 다른 작업을 할 수 있게 됨)

하지만 Blocking 방식의 함수들은 스레드 자체를 딜레이 시키는 것이기 때문에 해당 스레드에서 일어나는 모든 작업이 멈추게 됩니다.

실제 동작을 간단히 두 가지의 sleep() 함수를 호출해서 비교하는 예제 코드로 살펴보며 더 자세히 알아보도록 하겠습니다!

먼저 Blocking 방식의 함수였던 sleep() 를 호출해보겠습니다.

print("시작")
sleep()
print("10초 지남")
print("다른 작업 시작")

시작
(10초 대기 후)
10초 지남
다른 작업 시작

스레드를 Blocking 해서 대기를 했기 때문에 10초가 대기되는 동안 다른 작업도 함께 10초 동안 suspend 하게 됩니다.

이번에는 Blocking 방식의 함수였던 sleep() 를 호출해보겠습니다.

print("시작")
DispatchQueue.global().asyncAfter(deadline: .now() + 10) {
	print("10초 지남")
}
print("다른 작업 시작")

시작
다른 작업 시작
(10초 대기 후)
10초 지남

스레드가 Non-Blocking 상태였기 때문에 10초간 suspend 하는 중 다른 작업을 실행하게 됩니다. 그래서 다른 작업이 비동기적으로 잘 실행되었고 10초 후 "10초 지남" 도 잘 출력되게 되었습니다.

하지만 asyncAfter() 는 콜백 방식이라는 명확한 한계 때문에 클로저가 쌓이면서 가독성이 안좋아지는 "콜백 지옥"이라는 문제점을 야기하였고, 또 중간에 취소할 수 없다는 단점을 가지고 있었습니다.

2. Task.sleep() 함수

그래서!! Swift Concurrency 에서는 asyncAfter() 의 단점을 완벽히 개선하여, 같은 sleep 기능을 제공하지만 훨씬 더 Swift 적인 Task.sleep() 가 나오게 되었습니다!

// _Concurrency > Task
public static func sleep(nanoseconds duration: UInt64) async throws

Task.sleep() 는 호출시 호출한 Task 를 suspend 시킵니다.

이는 스레드를 차단하는 것이 아니고 Task 를 잠깐 일시 정지하는 것이기 때문에 최적화된 Non-Blocking 방식을 제공하며, 심지어 Task 기 때문에 작업 도중 언제든 취소할 수 있습니다.

Task.sleep() 를 사용하는 경우, 반드시 Task 컨텍스트 내부에서 사용해야하며 async 함수이기 때문에 await 와 함께 사용해야합니다.

print("시작")
Task {
	try? await Task.sleep(nanoseconds: 10 * 1_000_000_000)
    print("10초 지남")
}

(이거이거 Task.sleep() 맛 봐야겟지?)

3. 정리

함수Blocking 여부시간 단위비동기 지원취소 가능성
Thread.sleep()Blocking초/밀리초
usleep()Blocking마이크로초
sleep()Blocking
DispatchQueue.asyncAfter()Non-Blocking
Task.sleep()Non-Blocking나노초

사실 딜레이를 구현하는 방법이 더 있긴 하지만 약간 사파 느낌이라 다루지 않았습니다ㅎ (ex. Timer, Combine)

마무리

Swift Concurrency 2편이였지만 Swift Concurrency 스러운 내용을 크게 담지 못하여서 조금 아쉬웠습니다. 그래도 이 글을 통해 sleep() 함수에 대해 흥미를 가지는 분이 생긴다면 전 그것만으로도 만족할 것 같습니다ㅎㅎ

긴 글 읽어주셔서 감사합니다!

profile
iOS & macOS & visionOS 개발하는 사람

0개의 댓글