[Swift] Concurrency Programming - 2

Martin Kim·2022년 2월 9일
0

swift-concurrency

목록 보기
1/4

GCD

  • 애플이 제공하는 가장 대표적인 동시성 프로그래밍
  • Grand Central Dispatch의 약자로 멀티 코어 환경과 멀티 스레드 환경에서 최적화된 프로그래밍을 할 수 있도록 애플이 개발한 기술
  • GCD를 사용하기 위해 Dispatch 프레임워크를 사용하게 되는데 그중에서도 특히 DispatchQueue를 자주 사용한다.

DispatchQueue

  • Dispatch : 보내다, Queue: 대기열 -> 대기열에 보내다 라는 뜻
  • DispatchQueue에 작업을 넘기기 위해서는 2가지를 정해주어야 한다.
    • 단일스레드 / 다중스레드 -> (Serial / Concurrent)
    • 동기 / 비동기 -> (sync / async)
  • GCD와 DispatchQueue는 다른 개념이다. GCD가 더 넓은 개념이며 GCD는 Dispatch와 동치시킬수 있다.
  • Dispatch 프레임워크에는 DispatchQueue, DispatchWorkItem, DispatchGroup, DispatchQoS.... 등 여러가지 클래스들이 모여 있다.

Serial / Concurrent

  • Serial은 단일 스레드, Concurrent는 다중 스레드에서 작업을 처리
/ Serial Queue
DispatchQueue(label: "Serial")
DispatchQueue.main
// main은 전역적으로 사용되는 Serial DispatchQueue

// Concurrent Queue
DispatchQueue(label: "Concurrent", attributes: .concurrent)
DispatchQueue.global()

main은 프로퍼티고 global은 메서드인 이유?

  • main은 앱이 실행되는 동안 항상 위에 올라와 있는 스레드이고 global은 작업을 추가하면 새로운 스레드를 만들어 그곳에서 작업을 처리하게 되므로.
  • main에 작업을 추가하게 되면 Serial Queue인 main thread에서 작업을 처리하게 된다.
  • global 스레드는 main 스레드가 아닌, 작업을 처리하기 위해 발생한 스레드들을 말한다.
    • global 스레드는 main 스레드와는 달리 global()이 호출되면 작업을 처리하기 위해 메모리에 올라왔다가, 작업이 끝나고 나면 메모리에서 제거

sync / async

  • Serial(main) / Concurrent(global()) 중 하나를 정해주었다면 동기 / 비동기를 정해주면 된다.
// 동기, sync
DispatchQueue.main.sync {} 작업이 끝날 때까지 기다린다.
DispatchQueue.global().sync {} // main 스레드 말고 새로운 스레드를 만들어서 작업을 처리, 그런데 작업이 끝날 때까지 기다린다.

// 비동기, async
DispatchQueue.main.async {}
DispatchQueue.global().async {} //  main 스레드 말고 새로운 스레드를 만들어서 작업을 처리, 다음 작업이 있다면 기다리지 말고 처리

Main Thread

  • 앱의 생명주기와 같은 생명주기를 가지는, 앱이 실행되는 동안에는 늘 메모리에 올라와있는 기본 스레드
  • 메인 스레드는 늘 메모리에 올라온 상태로 존재하며 메인 스레드에서부터 필요한 만큼의 스레드가 파생된다.
  • 전역적으로 사용이 가능하다.
  • global 스레드들과는 다르게 Run Loop가 자동으로 설정되고 실행된다.
    • 메인 스레드에서 동작하는 Run Loop를 Main Run Loop라고 한다.
  • UI 작업은 메인 스레드에서만 작업할 수 있다.

main.async

import Foundation

DispatchQueue.main.async {
    for _ in 1...5 {
        print("😀😀😀😀😀")
        sleep(1)
    }
}

DispatchQueue.main.async {
    for _ in 1...5 {
        print("🥶🥶🥶🥶🥶")
        sleep(2)
    }
}

// 결과:
//😀😀😀😀😀
//😀😀😀😀😀
//😀😀😀😀😀
//😀😀😀😀😀
//😀😀😀😀😀
//🥶🥶🥶🥶🥶
//🥶🥶🥶🥶🥶
//🥶🥶🥶🥶🥶
//🥶🥶🥶🥶🥶
//🥶🥶🥶🥶🥶

async

import Foundation

DispatchQueue.main.async {
    for _ in 1...5 {
        print("😀😀😀😀😀")
        sleep(1)
    }
}

for _ in 1...5 {
    print("🥶🥶🥶🥶🥶")
    sleep(2)
}
  • 위 예제를 통해 작업이 끝나지 않고 기다리는 것을 알 수 있는데 대개 아래쪽 for문이 먼저 실행되곤 한다. (정확히는 무엇이 먼저 실행될지 모른다?!?)

global().async

import Foundation

DispatchQueue.global().async {
    for _ in 1...5 {
        print("😀😀😀😀😀")
        sleep(1)
    }
}

DispatchQueue.global().async {
    for _ in 1...5 {
        print("🥶🥶🥶🥶🥶")
        sleep(2)
    }
}

DispatchQueue.main.async {
    for _ in 1...5 {
        print("🥵🥵🥵🥵🥵")
        sleep(1)
    }
}

/* - 출력 (랜덤)
😀😀😀😀😀
🥶🥶🥶🥶🥶
🥵🥵🥵🥵🥵
😀😀😀😀😀
🥵🥵🥵🥵🥵
😀😀😀😀😀
🥶🥶🥶🥶🥶
🥵🥵🥵🥵🥵
😀😀😀😀😀
🥵🥵🥵🥵🥵
🥶🥶🥶🥶🥶
😀😀😀😀😀
🥵🥵🥵🥵🥵
🥶🥶🥶🥶🥶
🥶🥶🥶🥶🥶
*/
  • 이처럼 동시에 작업이 처리되기 위해서는 여러 개의 스레드가 필요하고, 비동기로 작업이 처리되어야 한다.
  • 아마도 여러 이미지 로딩 등에서 원하는 동작
  • 하지만 어떤 코드가 먼저 실행될지는 예측할 수가 없다.

global().sync

import Foundation

DispatchQueue.global().sync {
    for _ in 1...5 {
        print("😀😀😀😀😀")
        sleep(1)
    }
}

DispatchQueue.global().sync {
    for _ in 1...5 {
        print("🥶🥶🥶🥶🥶")
        sleep(2)
    }
}

for _ in 1...5 {
    print("🥵🥵🥵🥵🥵")
    sleep(1)
}

main.sync

  • main 스레드에서 직접 호출하면 안되는 코드
  • 직접 호출하게 되면 Deadlock 상태에 빠지게 된다.

    Deadlock: 두 개 이상의 작업이 서로 상대방의 작업이 끝나기 만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태 (위키피디아)

  • 작업이 끝나기를 기다리는 sync의 특성으로 인해 발생한다.
    1. main 스레드에서 main.sync를 호출하게 되면 main 스레드는 sync의 코드 블록이 수행되기를 기다리며 멈춘다.
    2. 그런데 이때 sync 블록도 멈추게 된다. main 스레드이기 때문에
    3. 결과적으로 main 스레드는 sync 블록이 끝나기를 기다리고, sync 블록은 main 스레드의 Block-wait이 끝나기를 기다리면서 교착상태(데드락)에 빠지게 된다.
  • 물론 global() 스레드에서는 실행 가능하다.
import Foundation

DispatchQueue.global().async {
    DispatchQueue.main.sync {
        for _ in 1...5 {
            print("😀😀😀😀😀")
            sleep(1)
        }
    }
}

for _ in 1...5 {
    print("🥶🥶🥶🥶🥶")
    sleep(2)
}

출처: 야곰닷넷 동시성 프로그래밍 강좌

profile
학생입니다

0개의 댓글