안녕하세요.
이번 포스트는 GCD의 개념 중 하나인 DispatchWorkItem, DispatchGroup, DispatchSemaphore에 대한 개념을 정리하려고 합니다.
GCD나 concurrency의 개념은 링크에서 개념 정리 했습니다!!
GCD 관련 포스트로 개념 정리했습니다.
Dispatch queue에 task를 보내는 방법으로 async, sync가 있었습니다. () -> Void 타입의 클로저의 형태를 async의 인자값에 넣음으로 해당 task가 queue에 추가되서 thread에서 실행 됬습니다. DispatchWorkItem도 이와 유사합니다. 추가적으로 많은 기능을 지원합니다. task에 관해 더 많은 설정을 하고 싶을 때 DispatchWorkItem을 사용합니다. enqueued된 task의 수행을 delay, canel등등..
1. DispatchWorkItem's concept
GCD의 task를 효율적으로 다루고 수행하기 위한 개념 DispatchWorkItem. class입니다. DispatchWorkItem을 사용해 캡슐화를 해서 dispatch queue나 dispatch group에 추가해서 캡슐화된 task를 수행하는 개념입니다. DispatchSource event, registration, cancel관련 처리를 할 수 있습니다.
디폴트로 dispatch queue's async, sync 함수를 사용할 때와 같이 사용하면 됩니다. block은 @escaping 입니다. dispatch queue나 dispatch group에 .async로 제출 시 실행 context를 capture합니다.
qos는 이전 포스트에서 다뤘습니다. block이 dispatch queue에 추가될 때 우선순위에 의해 먼저 수행하는 개념입니다.
DispatchWorkItemFlags는 block에 대한 qos, barrier, spawn등을 설정할 수 있습니다.
block에 task가 들어갑니다. async, sync함수와 마찬가지로 () -> Void 타입의 클로저를 입력하면 됩니다.
DispatchWorkItemFlags에선 barrier, assignCurrentContext가 잘 쓰이는 것 같습니다.
assignCurrentContext를 사용할 경우 dispatch queue나 group에 DispatchWorkItem을 보낼 때 지정된 QoS가 아니라 DispatchWorkItem이 생성될 때 지정된 QoS가 capture되어 사용됩니다.
Barrier의 경우 GCD. concurrent 수행에서 일어날 수 있는 shared resource를 동시에 읽으면서 쓸 때의 문제가 발생할 사용됩니다. + 싱글톤을 만들 경우에도 사용됩니다. barrier를 통해 concurrency 상황에서 특정한 task만 serial하게 실행시키도록 할 수 있습니다. barrier를 사용해서 한 개 또는 여러 개의 task에 대해 dispatch queue에서 sync한 수행을 하도록 사용할 수 있습니다. concurrency queue에서 사용해야 합니다. 여러 개의 task가 동시에 실행되는 dispatch's concurrent queue에서 DispatchWorkItem의 barrier task가 추가된다면 barrier 이전에 수행되던 task가 전부 실행 될 때까지 barrier task의 실행을 지연합니다. Barrier이전의 task가 전부 실행되면 비로소 단독으로 barrier task가 실행됩니다. Barrier task가 실행될 때에 한해서만 serial queue처럼 동작합니다. Barrier task가 끝나면 다시 concurrency하게 task들이 수행됩니다.
즉 concurrent 타입의 dispatch queue에서 synchronization 실행이 가능합니다. with DispatchWorkItem(flags: .barrier){ ... }
2. How to use DispatchWorkItem?
async()함수에 task 추가하는 방법
let queue = DispatchQueue(label: "async_func")
queue.async {
print("task 추가")
}
DispatchWorkItem을 통해 dispatch queue에 task 추가 하는 방법
/// 인자값 옵션 세팅 할 경우 DispatchWorkItem(qos: .background, flags: .barrier) { ... }
let workItem = DispatchWorkItem { [weak self] in
print(" task 로직 추가! ")
}
DispatchQueue.global().async(execute: workItem)
DispatchQueue.global().asyncAfter(deadline: .now(), execute: workItem)
/// task 취소
workItem.cancel()
async(execute:)
asyncAfter(deadline:execute:)
등의 함수로 task(DispatchWorkItem)를 추가할 수 있습니다. 이는 위의 async 코드와 같은 원리로 dispatch queue의 task로 enqueue됩니다.
cancel() 함수를 통해 task를 취소할 수 있습니다.
queue에서 아직 thread를 통해 실행되지 않은 경우 cancel을 사용하면 workItem이 제거됩니다. task가 진행중일 경우 취소가 안됩니다.
notify(queue:execute:)를 통해 다음 task 예약도 가능합니다.
3. DispatchWorkItem and barrier!!
동시성, 멀티 쓰레딩에서 발생할 수 있는 문제 중 하나로 readers-writers problem이 있다고 했습니다. singleton 객체의 공유 자원을 사용할 때 반드시 해결해야 합니다. Fabrizio Brancati 저자가 쓴 singleton에서 공유 자원 문제가 발생했을 때 barrier를 통해 해결했는데 이를 풀이해보려고 합니다.(원래 잘 설명되어있습니다.. _ 거의 하단 page 입니다.)
Q> 싱글톤은 언제 사용되는가?
싱글톤의 개념은 여러 viewController에서 사용되는 인스턴스의 객체가 오직 1개 인 것을 의미합니다. 누구나 같은 인스턴스를 사용하기 위해 동시에 접근하려고 할 때 만들면 좋습니다. 하지만 단순히 클래스에 선언 -> static으로 인스턴스화 해서 만들고 동시에 접근해서 reader가 되는 것은 문제가 없습니다. 싱글톤 객체의 자원을 읽는 사람이 있는데 동시에 수정하려고 한다면 문제가 발생합니다.
Thread-safe의 개념이 지켜진다면 어디에서 호출되던 코드의 실행 결과는 동일합니다. thread-safe의 개념이 지켜지기 위해선 공유자원을 최대한 줄이거나, 세마포어등의 lock을 통해 사용중이 명확하게 식별되야 합니다.
싱글톤에서 thread-safe에 대해 주의해야 할 점은
1. 인스턴스를 초기화 할 때(여러곳에서 초기화 하게 된다면 기존에 있던 정보 날라가게 됩니다.)
2. 인스턴스에 대한 읽기 쓰기 문제가 발생한 경우
두 가지 입니다.
이전에 초기화 관련해서 고민을 했었습니다. Static에 대한 개념을 여기서도 활용할 수 있게 되어 다행이네요.Static 객체의 초기화는 단 한번됩니다. 프로그램 시작시 되는 것은 아니고 main thread가 해당 코드의 초기화 구문을 실행했을 때 단 한번 메모리에 할당되어 초기화가 됩니다. 그 이후에 초기화가 안됩니다. 이미 메모리에 할당됬기 때문입니다. 그래서 static은 thread-safe 입니다.
하지만 concurrency한 환경에선 NSLock을 사용하지 않는다면 문제가 발생합니다. 싱글톤의 공유 자원에 접근하려는 메서드 관련해서는 computed property를 통한 읽기 전용, barrier를 통한 concurrent -> synchronization으로 변경해서 작업을 수행해야 합니다.
final class PhotoManager {
private init() {}
static let shared = PhotoManager()
private var _photos: [Photo] = []
}
위에서 언급한 바와 같이 static은 단 한번 초기화가 됩니다. PhotoManager()는 단 한번 실행이 됩 니다. 그리고 메모리에 할당되어 더이상 PhotoManager()를 호출하지 않습니다.
extension PhotoManager{
var photo: [Photo] {
return _photo
}
func addPhoto(_ photo: Photo) {
/// 1.
_photos.append(photo)
DisptchQueue.main.saync { [weak self] in
self?.postContentAddedNotification()
}
}
}
_photos는 변경 가능한 array입니다. Collection type인 array, dictionary는 삽입, 삭제가 가능한 var의 경우에 not thread safe타입이 됩니다. computed property를 사용하는 것은 읽기, 쓰기가 가능 했던 _photo변수를 오직 읽기 전용으로 만듭니다. var타입의 변수가 let 타입의 변수가 되는 것과 마찬가지입니다. getter만 있는 computed peroperty이기 때문에 read-only가 됩니다. 즉 concurrent task들은 읽기만 가능해서 thread safe입니다. (공유 자원이 변경되지 않기 때문입니다.)
하지만 PhotoManager 내 addPhoto함수에서는 주석1 아래 라인의 코드를 통해 수정이 가능합니다. computed property를 사용한 이유가 없습니다. 읽는 동시에 값이 변경된다면 이상한 결과를 초래할 수 있습니다. concurrency한 task가 동시에 수정을 할 수 있기 때문입니다.
그래서 위에서 언급한 바와 같이 NSLock을 통해서 문제를 해결하거나, 세마포어를 만든다. 또는 dispatch barrier를 이용하는 방법이 있습니다. 수정할 때 아무도 리소스를 사용하지 않을 때 까지 기다렸다가 수정하는 방법(using barrier). 읽을 때 한 사람씩 읽는 방법,,
final class PhotoManager{
...
private let photoQueue = DispatchQueue(
label:"com.raywenderlich.GooglyPuff",
attributes: .concurrent)
}
이 dispatchqueue를 통해 photo 관련 기능을 수행하는 싱글 톤을 사용할 때의 task들을 관리합니다. ( 물론 이 싱글톤을 사용하는 task가 몰리면 reader-writer문제가 발생합니다. 이를 barrier를 통해 해결합니다.
func addPhoto(_ photo: Photo) {
/// 1.
let item = DispatchWorkItem(flags: .barrier) { [weak self] in
guard let self = self else { return }
/// 2.
self._photo.append(photo)
DispatchQueue.main.async { [weak self] in
self?.postContentAddedNotification()
}
}
/// 3.
photoQueue.async(execute: item)
}
1. DispatchWorkItem 생성.
2. DispatchWorkItemFlags 타입이 .barrier이기 때문에 이 task가 실행될 때는 _photo 배열을 읽는 사람x. reader-writer 문제 x
3. photoQueue에 추가.
var photos: [Photo] {
var photosCopy: [Photo] = []
///1.
photoQueue.sync {
///2.
photosCopy = self._photos
}
return photosCopy
}
1. 읽기 작업을 수행한다면 photoQueue에 추가합니다( 나 읽고 있어요~)
2. read-only로 inner property _photos 값을 반환합니다.
음.. 운영체제를 다시 복습해야겠다는 생각이 드네요.
참고 자료:
https://www.kodeco.com/28540615-grand-central-dispatch-tutorial-for-swift-5-part-1-2
https://medium.com/geekculture/concurrency-in-ios-gcd-c3a69acd7f31
https://developer.apple.com/videos/play/wwdc2016/720/?time=881
https://tecoble.techcourse.co.kr/post/2020-11-07-singleton/
https://stackoverflow.com/questions/58038406/how-can-we-make-static-variables-thread-safe-in-swift
'iOS > Concurrency' 카테고리의 다른 글
[Swift] GCD 개념 정리 | No7. GCD (0) | 2023.02.01 |
---|---|
[Swift] DispatchGroup으로 tasks 관리하기 | No6. GCD (0) | 2023.01.31 |
[Swift] GCD와 Concurrency. main thread와 dispatch queue global 개념 뿌수기!! | No4. GCD (0) | 2023.01.31 |
[Swift] Hi GCD(GrandCentralDispatch). GCD's concept deep dive!!! | No3. GCD (2) | 2023.01.30 |