티스토리 뷰

Swift

[Swift] Swift Concurrency와 GCD

Esiwon 2022. 12. 14. 02:32

안녕하세요.
저번 글에서 Race Condition과 Thread Safe에 대해 알아봤었는데 비동기에 대해서 공부하다 보니 자연스럽게 WWDC 2021에서 소개된 Swift Concurrency에 대해서도 관심이 생겼습니다. 그래서 이번 기회에 GCD Swift Concurrency에 대해 간단하게 알아보고 사용법을 정리해 볼까합니다.

GCD, Swift Concurrency 란???

GCD

기존 Swift에서 동시성 프로그래밍을 구현하기 위해 단연코 GCD API를 주로 사용했습니다.
GCD는 비동기로 실행되고자 하는 코드 블록을 DispatchQueue라는 선입선출 Queue에 넣어 스레드에 작업을 할당하는 방식입니다. GCD는 클로저를 사용해 DispatchWorkItem라는 실행해야 할 작업 객체를 만들기 때문에 CallBack을 받기 위해서 CompletionHandler 라고 하는 @escaping 클로저가 많이 사용됩니다.

Swift Concurrency

2021 WWDC에서 발표된 따끈한 최신 동시성 프로그래밍 API입니다. 탄생 이유는 좀 더 쉽게 비동기 코드를 작성하기 위해서라고 WWDC에서 소개하고 있습니다. async로 비동기 코드임을 나타내고, await로 해당 비동기 코드가 완료될 때까지 기다린다는 의미를 가지고 있습니다. 특징으로는 GCD에 비해 적은 코드 수와 가독성의 차이가 가장 심한 것 같습니다.
그 외에도 많은 장점이 있습니다. 간단하게 알아보겠습니다.

Swift Concurrency의 장점

  • 가독성 향상
  • Race Condition 처리
  • 우선순위 관리
  • 스레드 관리

가독성 향상

Swift Concurrency가 GCD과 다르게 @escaping 클로저를 사용할 필요가 없기 때문에 가독성 향상과 코드의 량이 줄어든다는 장점은 많이들 알려져 있는 것 같습니다.

WWDC의 샘플 코드로 차이를 한번 확인해 보겠습니다

  • GCD
  • Swift Concurrency

위 그림과 같이 두 메서드의 기능은 같지만 코드의 량과 가독성의 차이는 꽤나 심하다고 볼 수 있습니다.
두 메서드가 차이가 나는 가장 큰 이유는 @escaping 클로저가 없어졌습니다.
그리고 두 메서드는 에러 핸들링도 많은 차이를 보입니다.

위 그림처럼 GCD 같은 경우, competion을 통해 에러가 발생한 위치에 .failure를 전달해 줘야 했습니다. 이 같은 경우 개발자가 실수로 에러 처리를 하지 않아도 컴파일러는 이를 오류라고 판단해 주지 않습니다.

 

하지만, Swift Concurrency는

위 그림처럼, try, throw 키워드로 간편하게 에러 핸들링을 해준 걸 볼 수 있고, 이는 개발자가 실수로 처리하지 않으면 컴파일러가 오류라고 판단하기 때문에 개발자가 바로 코드를 수정할 수 있습니다.

Race Condition 처리

"Race Condition과 Thread Safe"해당 글에서도 언급했지만, 비동기 코드를 작성하다 보면 여러 스레드가 하나의 자원에 접근하는 일이 생길 수 있습니다. 때문에 GCD 같은 경우 NSLock, DispatchSemaphore, Serial Queue 등으로 이를 처리해 줘야 합니다.

 

예제 코드를 통해 둘의 차이를 보겠습니다.

 

먼저,

위와 같이 bookList가 있습니다.

  • GCD
  • Swift Concurrency

각 같은 기능을 하는 메서드를 GCD와 Swift Concurrency로 작성해 보았습니다.

 

메서드에 대해서 설명하자면, bookList 배열에서 인덱스를 가지고 책을 다운로드해 books라는 공유자원에 다운로드한 책을 담아서 반환하는 메서드입니다.
다운로드하는 느낌을 더 주기 위해 0.5초의 sleep을 주었습니다.

 

이는 제가 토이 프로젝트를 하면서, 겪었던 상황을 간략하게 재연해 본 것입니다.

 

참고로 두 메서드는 따로 Race Condition에 대한 처리를 하지 않았습니다.

 

그럼 두 메서드의 결과를 확인해 볼까요??

  • GCD

음???🤔 뭔가 이상하네요. 분명 10개의 책을 가져와야 하는데

한 번만 더해보겠습니다.

  • GCD 2트

이번엔 3개의 책이 나왔네요. 이는 여러 스레드가 books에 접근하면서, Race Condition이 발생해 생긴 문제입니다.

 

그럼 이번엔 NSLock을 이용하여 위 문제를 해결해 보겠습니다.

  • Race Condition문제를 해결한 GCD 코드와 결과
    • 코드
    • 결과

위 그림처럼 books에 접근하는 곳에서 Lock을 걸어주었고, 결과적으로 원하는 값을 얻을 수 있었네요.

 

그럼 이번엔 Swift Concurrency로 구현한 메서드의 결과를 확인해 볼까요??

  • Swift Concurrency
    • 코드
    • 결과

따로 Race Condition 처리를 하지 않아도 원하는 값을 얻을 수 있네요!!! 신기합니다.
그럼 addTask 안에서 강제로 books에 접근하면 어떻게 될까요??


위와 같이 컴파일러가 오류가 발생했다고 알려줍니다. 아주 편하죠??? 그전까진 직접 해야 할 일을 컴퓨터가 대신해주는 느낌??? 아주 좋습니다.

우선순위 관리

GCD 같은 경우 QOS를 이용하여 우선순위를 관리합니다. 비동기 작업을 하고 있을 때 진행하고 있는 작업보다 높은 우선순위의 작업이 들어오면, 높은 우선순위의 작업부터 완료하는 기능입니다.

 

하지만, GCD는 DispatchQueue에 작업을 담아 스레드에게 뿌려준다고 위에서 언급했습니다. 이러한 특성 때문에 우선순위가 높을 작업을 먼저 실행해야 하는 성능이 다소 떨어지게 됩니다.

  • GCD
    • 코드
    • 결과

결과와 같이 높은 우선순위의 작업을 할당하였지만, DispatchQueue의 선입선출이라는 특성 때문에 마지막에 들어간 userInitiatedTask가 거의 마지막쯤에 끝나는 것을 볼 수 있습니다.

 

하지만, Swift Concurrency는 어떨까요??

  • Swift Concurrency
    • 코드
    • 결과

Swift Concurrency 같은 경우 원하는 결과를 얻었다고 할 수 있겠네요.
우선순위가 높은 작업이 먼저 시작하고, 끝났습니다.

 

위와 같이 GCD와의 차이가 나는 이유는 Swift Concurrency는 Queue을 사용하지 않기 때문입니다. Swift Concurrency 같은 경우 작업 진행 중에 우선 수위가 더 높은 작업이 들어오면 늦게 들어왔더라도 우선순위가 높은 작업을 먼저 실행하고, 다시 우선순위가 낮은 작업을 실행하게 됩니다.

스레드 관리

비동기적으로 코드를 작성하게 되면, 여러 개의 스레드가 생성되게 됩니다.
때문에 우리는 여러 작업을 동시에 실행할 수 있게 됩니다. 하지만, 우리는 동시에 실행되는 것처럼 보이지만, 컴퓨터 입장에선 아닙니다. 컴퓨터 즉 CPU 더 들어가 코어는 하나의 작업만 할 수 있습니다. 결론부터 말하면, 코어는 여러 개의 스레드를 빠르게 이동하면서 작업을 처리하는 것입니다. 코어가 스레드를 이동하는 것을 컨텍스트 스위칭(context switching)이라고 합니다. 즉, 스레드가 많다는 것은 그만큼 많은 컨텍스트 스위칭이 일어난다고 생각해 볼 수 있습니다.

 

서론은 여기까지고, 그래서 스레드가 많이 생성되면 어떠한 문제점이 생기는지 알아보겠습니다.
스레드는 stack이라는 메모리 영역을 할당받습니다. 때문에 스레드가 많아지면, 메모리 오버헤드가 발생할 수 있고, 이를 처리하기 위해 컨텍스트 스위칭 또한 많이 발생한다 했습니다.
컨텍스트 스위칭 많이 발생하면, 스위칭 하는 과정에서 약간의 아무 일도 하지 않는 공백이 생기게 됩니다. 이를 스케줄링 오버헤드라고 합니다.

 

결론은 스레드는 적을 수록 성능적으로 유리합니다.

 

하지만 GCD 같은 경우 하나의 작업 블록 즉, DispatchWorkItem마다 스레드를 생성하여 해당 스레드에서 작업을 처리합니다. 동시 처리 작업이 많을수록 많은 스레드를 생성한다고 보면 될 것 같습니다.

 

하지만, Swift Concurrency 사용할 경우 thread explosion을 미연에 방지해 줍니다.
Swift Concurrency는 다음과 같은 특징을 가지고 있습니다.

  • CPU 코어 수 이상으로 스레드를 생성하지 않는다.
  • 스레드간 스위칭이 아닌 Continuation이라는 것을 둬 Continuation간 스위칭을 하여 작업을 처리한다.
  • await 키워드가 읽힌 스레드는 멈추는것이 아닌 다른 async작업이 할당된다.

위와 같은 특징이 있어 Swift Concurrency는 스레드를 안전하고 효율적으로 사용할 수 있습니다.

 

이부분은 실험이 쉽지 않아 Swift Concurrency에 대해서라는 블로그를 읽어보시면 좋을 것 같습니다. 해당 글에서 이 부분에 대한 실험도 보실수 있으니 확인해 보면 좀 더 체감이 오실 것 같습니다.

 

마지막으로, 이번 글을 작성하면서 참고 및 공부한 링크를 걸어두겠습니다. 참고해 보세요.

'Swift' 카테고리의 다른 글

Combine 뽀개기 2장: Subscriber  (0) 2023.07.26
Combine 뽀개기 1장: Publisher  (0) 2023.07.26
[Swift] Race Condition과 Thread Safe  (0) 2022.12.12
[Swift] Protocol  (0) 2022.02.23
[Swift] 객체지향(OOP)  (0) 2022.02.23
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
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
글 보관함