GCD를 시작하기 전에 이에 대한 개념을 먼저 살펴보겠습니다.
이는 동시 작업 관리를 위한 Low-Level API
입니다. 앱의 스레드별로 직렬 또는 동시적으로 작업을 수행합니다. 이를 사용하여 비동기 프로그래밍을 쉽고 빠르게 만들 수 있습니다. 또 이 친구는 FIFO
로 작업을 관리합니다.
DispatchQueue.main.async {
// Run async code on the main queue
}
세 가지의 큐가 존재합니다.
아래 사진을 보면 더 이해하기 쉬울겁니당
우선순위를 정해줄 수 있으며 위와 같은 단계가 존재합니다. 아래로 갈수록 우선순위가 낮음
DispacthQueue.global(qos: .userInteractive).async {
// code block
}
DispacthQueue.global(qos: .userInitiated).async {
// code block
}
DispacthQueue.global(qos: .default).async {
// code block
}
...
커스텀 큐(직렬/동시)를 만드는 방식을 살펴보겠습니다.
let serialQueue = DispatchQueue(label: "Swift.Korea")
serialQueue.async {
print("task 1 Executing")
print("task 1 Finished")
}
serialQueue.async {
print("task 2 Executing")
print("task 2 Finished")
}
// OUTPUT
// task 1 Executing
// task 1 Finisihed
// task 2 Executing
// task 2 Finished
let concurrentQueue = DispatchQueue(label: "Swift.Korea", attributes: .concurrent)
concurrentQueue.async {
print("task 1 started")
print("task 1 finished")
}
concurrentQueue.async {
print("task 2 started")
print("task 2 finished")
}
// OUTPUT
// task 1 started
// task 2 started
// task 1 finisihed
// task 2 finisihed
위 사진에서 볼 수 있지만 비동기적으로 실행되는 경우 총 걸리는 시간이 더 짧다는 점을 볼 수 있다. 근데 이렇게 하려면 잘 설계가 되야겠져 ?
동기, 비동기, 직렬, 동시에 관해 좀 헷갈릴 수 있다(제가 그럼) 아래 링크 답변을 보고 이해가 잘되어서 하나 첨부함다
https://stackoverflow.com/questions/19179358/concurrent-vs-serial-queues-in-gcd
동시큐라고 전부 동시로 실행한다기보다 여러가지 상황을 생각해야 하는 부분이 인상 깊었다. 구체적으로는, 결국 어떤 큐든지 간에 내부 코드 블록 실행은 직렬로 실행이되고, 같은 큐의 비동기 실행도 직렬로 실행이 된다. 자세한 건 위 링크를 참조 바람다. 매우 좋았으 개인적으론
이는 일련의 작업을 그룹화할 수 있고 작업이 완료될 때까지 기다리거나 완료된 후 알림을 받도록 할 수 있습니다.
이번엔 이미지를 패치하는 예제로 살펴보겠습니다.
위 코드들은 깃헙 유저 url을 바탕으로 이미지를 들고오는 코드입니다. 디스패치 그룹을 활용해서 동시적으로 실행을하고 있는 모습을 볼 수 있습니다.
여기서 group.wait()
로 모든 작업이 완료될 때까지 호출자 스레드를 차단합니다.
출력을 살펴보면 다음과 같습니다.
다른 방식인 notify()
를 활용하는 코드를 살펴보겠습니다. wait()
과 좀 비슷하지만, 스레드를 차단하지 않고 내부 그룹의 작업이 완료가 되면 알려주는 차이가 있습니다. 디스패치 그룹을 사용한다면 notify()
를 활용하라고 추천하심
제한 없는 작업은 데드락을 유발할 수 있습니다. 그래서 우리는 세마포어를 이용해 제한을 할 수 있습니다.
let concurrentTasks = 3
let queue = DispatchQueue(label: "Concurrent queue", attributes: .concurrent)
let sema = DispatchSemaphore(value: concurrentTasks)
for _ in 0..<999 {
queue.async {
// Do work
sema.signal() // semaphore 증가
}
// semaphore 감소
sema.wait
}
세마포어에 대한 지식이 부족하므로 다른 링크 하나 참조해씀다
세마포어란? 이는 정수 변수로서, 멀티프로그래밍 환경에서 공유 자원에 대한 접근을 제한하는 방법으로 사용된다.
스레드가 공유 자원의 배타적인 사용을 보장받기 위해 임계 구역에 들어가거나 나올때는 세마포어 같은 동기화 매커니즘이 사용됩니다.
위 코드에서는 공유 자원에 접근 가능한 작업 수를 3개로 제한하는걸 볼 수 있습니다.
공유 자원에 접근 가능한(혹은 한 번에 실행 가능한) 작업 수를 명시하고, 임계 구역에 들어갈때는 wait()
를 나올때는 signal()
를 호출합니다.
wait()
는 세마포어 값을 -1 하고, 0이 되면 값이 증가할 때까지 대기합니다. 프로세스가 자원을 사용할테니 기다리라는 의미입니다.
signal()
은 세마포어 값을 +1 하고, 프로세스가 자원을 사용하고 나왔다는 신호를 의미합니다.
함정이란 뜻이라네요 첨 봐쓰 ~
멀티 스레드를 사용시에, 같은 값에 접근하고 수정하는 경우에 문제가 생길 수 있다.
let concurrent = DispatchQueue(label: "Swift.Korea", attributes: .concurrent)
var array = [1,2,3,4,5]
func race() {
for _ in 0...10 {
concurrent.asnyc {
for i in array {
// read access
print(i)
}
}
concurrent.asnyc {
for i in 0..<10 {
// write access
array.append(i)
}
}
}
}
다른 함정으로는