GCD와 Operation Queue, 동기와 비동기, 직렬과 동시
Youngminah opened this issue · 0 comments
Youngminah commented
아래의 Main.sync의 데드락 발생 원인을 정확히 이해한다면 당신은 이미 GCD고수...
결론부터 미리보기 (이거 이해했으면 이 글 안읽어도됨 하지만 읽어줘...)
- 우선 직렬, 동시의 명시로
들어온 작업
을 순차적큐(메인큐) 로 보내던가 동시큐(글로벌큐) 로 보내던지큐로 보내는걸 결정
함. - 직렬큐 또는 동시큐로 보내면 항상 그 작업은
맨 뒤
로 보내지게 됨 - 그리고
작업을 보낸 현재의 그 스레드
!! 에서 보낸 작업이 끝날때 까지 다른 작업을 할것인지 안할것인지 (동기 비동기) 결정함. - 위의 박스친 말들이 제대로 나온 문서가 거~~~~~의 없다
- 그러니까 헷갈릴수 밖에 없지..
애플에서의 동기, 비동기 큐
- 쉽고 편한 멀티 스레딩 처리를 위해 애플은 세가지의 API를 제공
- GCD(Grand Central Dispatch)라는 C기반의 저수준 API
- NSOperation이라는 Obj-C 기반으로 만들어진 고수준 API
- NSOperation은 GCD보다 약간의 오버헤드가 더 발생되고 느리지만
- GCD에서는 직접 처리해야 하는 작업들을 지원 하고 있기 때문에 (KVO관찰,
작업취소
등등) - 그정도는 감수하고 (NSOperation를)사용할만하다.
- 마지막으로 Swift 5.5에서 생긴
Async/await
API - async/await는 순수한 swift 언어로 구성됨.
- async/await는 따로 정리함 링크바로가기
GCD API
- main queue : 메인 스레드(UI 스레드)에서 사용 되는 Serial Queue로 모든 UI 처리는 메인 스레드에서 처리❗️
DispatchQueue.main.sync {...} // 대부분에서 사용 불가능 뒤에서 설명
DispatchQueue.main.async {...}
- global queue : Concurrent Queue임❗️
DispatchQueue.global().sync {}
DispatchQueue.global(qos: .background).async {}
- Custom Dispatch Queue : Serial Queue, Concurrent Queue 정의 생성가능. default는 serial
Dispatch Queue(label: "com.serialQueue").async {}
Dispatch Queue(label: "com.concurrentQueue",
qos: .default,
attributes: .concurrent,
autoreleaseFrequency: .inherit,
target: nil).async {}
Operation Queue API
- GCD와 비교했을땐 추가적인 오버해드가 있으나,
- 다양한 작업들 가운데 의존성을 추가할 수 있고, 재사용, 취소, 중지시킬 수 있다.
- Operation을 일시 중지, 다시 시작 및 취소를 할 수 있다.🖍
- KVO를 사용할 수 있는 많은 프로퍼티들이 있다.
isCancelled, isAsynchronous, isExecuting, isFinished, isReady, dependencies, queuePriority, completionBlock
동기 vs 비동기
동기 (Sync)
- 스레드에서 보낸 작업이 끝날 때까지 현재 스레드는 다른 작업을 하지 않고 기다린다.
- 해당 작업이 끝날 때 까지 스레드는 다른 작업 하지 않고 Block상태를 유지
- 동기는 설계가 간단하고 직관적이지만,
- 결과가 주어질 때 까지 대기를 해야하는 단점
- 동기의 경우 하나의 작업이 Queue에서 빠져나갈 때까지 기다리기 때문에, Serial 이냐 Concurrent냐의 차이는 없다.
비동기 (Async)
- 보낸 작업이 끝나는 것을 기다리지 않고 현재 스레드는 다른 작업을 시작.
- 비동기는 설계는 복잡하지만
- 자원을 효율적으로 사용할 수 있다는 장점
- 동기 방식보다 대기시간을 줄여줄 수가 있어서 효율적인 면이 있다.
- 병렬 처리와 스레드 풀에 기반을 둔 비동기 방식을 구현 (그래서 병렬이라기보단 비동기라고 얘기한다. 비동기가 더 큰 개념)
직렬 vs 동시
- 위에서 비동기, 동기로 보낸 그 작업 들을
순차적
❗️으로 처리할지 결정하는 것 - 정확히는 직렬큐(순차처리큐) 또는 동시큐(순차고려x큐)의 맨뒤로 보내는 것
직렬 (Serial)
단 하나의 쓰레드
로만 작업을 보내는 대기열- GCD에서 mainQueue에서 mainthread로 보냄
- 즉 순차적으로 처리됨
동시 (Concurrent)
여러개의 다른 쓰레드
로 작업을 보내는 대기열- GCD에서 globalQueue또는 customQueue에서 여러 스레드로 나눠 보냄
- 순서는 상관없게됨
비동기(async)와 동시(concurrent)는 비슷해보이지만 아예 다른 개념❗️
직렬, 동시
는 큐로 들어온 작업들을 순차적으로 처리할지 순서 상관 없이 실행할지 결정. (순차 처리 or 순서 상관 x)동기, 비동기
큐로 보내고 현재의 스레드는 다른 작업을 할 수 있게 할지 말지 결정. (다른 작업 가능 or 불가능)
직렬(Serial)을 사용하는 이유
- 바로 작업의 '순서'가 중요할 때 직렬이 사용된다,
- 동시는 여러개의 쓰레드에서 분산 작업하기 때문에 순서를 상관하지 않고 쓰레드에 할당된 작업은 각 쓰레드가 모조리 처리한다.
- 따라서, 만약 어떤 작업이 꼭 먼저 이뤄져야 한다면, 그럴때는 직렬을 사용하는게 좋다.
- 프로그램의 성능/반응성 을 올리고 최적화를 하기 위해서 하는 것이 동시성 프로그래밍
네트워크 작업
Alamofire 라이브러리
- 네트워크를 처리할 때에는 보통 비동기로 처리한다.
- Alamofire에서는 자동으로 비동기로 처리해준다.
라이브러리 사용을 안한다면?
DispatchQueue.global().async {
if let url = URL(string: self.url), let data = try? Data(contentsOf: url), let image = UIImage(data: data) {
DispatchQueue.main.async {
self.imageView.image = image
}
}
}
- 비동기 처리 뒤, 메인쓰레드의 UI를 처리하는 것들은
- 다시 코드로
DispatchQueue.main.async
를 이용하여 메인쓰레드에서 처리할 수 있도록 한다. - 보통 비동기 처리를 하지 않았을 경우에는 메인쓰레드에서 처리하기때문에
- 메인쓰레드 처리 코드는 넣지 않아도 된다.!!
main.sync가 오류가 나는 이유❗️
코드로 살펴보기
@IBAction func mainAsync(_ sender: UIButton) {
print("HELLO WORLD") //1블럭
for i in 1...100 {
print(i, terminator: " ") //2블럭
}
DispatchQueue.main.sync { // 3 블럭
for i in 101...200 {
print(i, terminator: " ")
}
}
print("\nBYE BYE WORLD") // 4블럭
}
- 버튼을 눌렀을때 실행되어야 하는 코드는 위와 같이 짜보았다.
- 3블럭이라고 주석친 부분들만
main.sync
로 실행해보았다.
- 2블럭까지는 잘 실행 되다 그 이후에 오류뜬 모습 !!
왜 오류가 뜨는 것이야 ❗️
- 미래의 나에게 설명을 돕기위해 그림으로 그려보았다.
- DispatchQueue.main.sync의 뜻을 생각해보자
main
: main 스레드에서 실행하도록 하는 직렬큐 (순차적 실행큐) 로 배정sync
: 큐로 보낸 작업이 끝날때까지 다른애들은 동시에 일을 할 수 없음
- 그렇기 때문에 3번째 블럭은 mainQueue에 맨 뒷부분으로 자리를 잡게되고 (큐의 특징 맨뒤에서 배정임)
- sync 때문에
현재 작업을 큐로 보낸 스레드( 여기서는 메인스레드)
는 3번째가 처리 될 때까지 일을 하지 않는다. - 따라서 4번째 블럭은 절대로 실행되지 않는
DeadLock
발생- 두 개 이상의 작업이 서로 상대방의 작업이 끝나기 만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태
- 4번째 블럭 때문이라 쉽게 이해하기 위해 말했지만 꼭 4번째 블럭이 실행안돼서 3번째 블럭이 실행 안되는건 아님
- main 스레드는 모든 UI 처리를 담당하는 스레드 인건 상식으로 알고 있쥬?
- 그렇기 때문에 꼭 4번째 블럭말고도 처리해야 하는 UI가 있을수 있으므로,
- 애플은 Main Thread를
Thread-unsafe
(교착상태가 발생할 수 있는 스레드)로 명시한다.
결론
- Main 스레드는 unsafe스레드 이기 때문에
- 메인스레드에서
main.sync
하는 것은 데드락을 발생시키므로 에러가 뜸. - 끊임없이 앱의 이벤트 처리를 하고 있던 main thread가 sync호출에 의해 멈추게 되고
deadlock
발생- 상태란 두 개 이상의 작업이 서로 상대방의 작업이 끝나기 만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태
- 즉 메인스레드에서 main.sync호출하면 deadlock ❗️
DispatchQueue.main.sync 가 반드시 안되는 것은 아님 ⭐️
백그라운드 쓰레드
에서 이루어지는 작업들 사이에 순서에 맞게- main 쓰레드에서 작업이 이루어져야 할 때 사용
- Background thread 내에서 사용하는 것이 아니면 DispatchQueue.main.sync 사용 노노
그리고 팁: 코드로 Main Thread에서 global.sync는 사용을 거의 안한다. ( customQueue도 동시라면 마찬가지)
- GCD에 대해 명확히 이해했다면 너무 당연한 이야기 아님..?
- 메인스래드가 동기로 일을 다른 쓰레드에게 처리하라고 주면,
- 어차피 메인 스레드는 그 일이 동시큐에서 처리 되어야 실행가능
- 즉, 메인스레드에서 global.sync로 작업을 주면
- 그냥 메인스레드에서 실행하는 거랑 ❗️똑같은❗️ 실행순서와 결과가 나옴
왜 병렬이라고 안하고 동시라고 말을 할까? 둘의 차이는 무엇?
- 동시성(concurrency) 은 동시에 실행되는 작업을 말하며,
- 병렬(parallel) 은 하나의 작업을 작은 단위로 나눠서 동시에 실행함을 의미한다.
- 즉 동시가 더 큰 개념이라고 한다!