Skip to content

Swift의 동시성(Concurrency) 프로그래밍에 대해 설명해주세요. #69

@ujhong7

Description

@ujhong7

Swift의 동시성(Concurrency) 프로그래밍

동시성 프로그래밍은 여러 작업을 효율적으로 처리하기 위해 동시에 실행되도록 설계된 프로그래밍 방식입니다. Swift에서는 Grand Central Dispatch(GCD), OperationQueue, **Swift Concurrency(Async/Await)**를 사용해 동시성 처리를 수행합니다.


Grand Central Dispatch(GCD)

주요 개념

  1. DispatchQueue

    작업을 실행할 스레드 관리하는 큐입니다. GCD는 작업 단위를 **클로저**로 정의하여 큐에 추가하고 적절한 스레드에서 실행합니다.

    • Serial Queue: 작업을 순차적으로 실행.
    • Concurrent Queue: 작업을 동시에 실행하되 완료 순서는 보장하지 않음.
  2. Main Queue

    • 앱의 메인 스레드에서 실행되며, UI 작업은 반드시 메인 큐에서 실행해야 합니다.
  3. Global Queue

    • 시스템에서 제공하는 기본 동시 큐로, 우선순위별로 여러 개의 큐를 제공.
  4. QoS (Quality of Service)

    작업의 중요도와 우선순위를 설정하여 시스템 리소스를 적절히 배분합니다.

    예: .userInteractive, .userInitiated, .background 등.


GCD 사용 방법

1. 비동기 작업 실행

DispatchQueue.global().async {
    print("비동기 작업 실행")
}

2. 동기 작업 실행

DispatchQueue.global().sync {
    print("동기 작업 실행")
}

3. 메인 큐에서 작업 실행

DispatchQueue.main.async {
    print("UI 업데이트")
}

4. 작업 지연 실행

DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
    print("2초 후 실행")
}

OperationQueue와 DispatchQueue의 차이점

Feature | OperationQueue | DispatchQueue -- | -- | -- 레벨 | 객체 지향 방식 (NSOperation) | 함수 기반 방식 작업 정의 | Operation 클래스를 상속받아 작업을 정의 | 클로저로 작업 정의 우선순위 설정 | 작업 간 우선순위 설정 가능 | 작업 자체의 우선순위 설정은 불가능 종속성 설정 | 작업 간의 종속성을 지정할 수 있음 | 종속성 설정 불가능 취소 | 작업을 취소할 수 있음 (isCancelled 속성 활용) | 클로저는 취소 불가능 적용 사례 | 복잡한 작업 스케줄링 및 종속성 관리가 필요한 경우 | 간단한 동시성 처리

OperationQueue 사용 예

let queue = OperationQueue()

let operation1 = BlockOperation {
print("작업 1 실행")
}
let operation2 = BlockOperation {
print("작업 2 실행")
}

operation2.addDependency(operation1) // 작업 2는 작업 1이 끝난 후 실행
queue.addOperations([operation1, operation2], waitUntilFinished: false)


동시성 프로그래밍에서 발생할 수 있는 문제와 해결 방법

1. Race Condition

  • 문제: 두 개 이상의 스레드 동시 동일한 자원 접근 및 수정하려고 할 때 발생.
  • 해결 방법:
    • 동기화(Synchronization)DispatchSemaphore 또는 NSLock 사용.
    • Serial Queue: 하나의 작업만 순차적으로 실행.

예: Race Condition

var sharedResource = 0

DispatchQueue.concurrentPerform(iterations: 10) { _ in
sharedResource += 1 // Race Condition 발생
}

해결 방법

let lock = NSLock()
var sharedResource = 0

DispatchQueue.concurrentPerform(iterations: 10) { _ in
lock.lock()
sharedResource += 1
lock.unlock()
}


2. Deadlock

  • 문제: 두 개 이상의 스레드가 서로의 작업 끝나길 기다리 무한 대기 상태에 빠짐.
  • 해결 방법:
    • 동기 호출 지양: 비동기 호출을 통해 작업 순환 방지.
    • Lock 순서 정의: Lock을 항상 동일한 순서로 획득.

예: Deadlock

let queue = DispatchQueue(label: "deadlock.queue")

queue.sync {
queue.sync {
print("Deadlock 발생") // 내부 작업이 끝나길 기다리며 멈춤
}
}

해결 방법

let queue = DispatchQueue(label: "no.deadlock.queue")

queue.async {
queue.sync {
print("Deadlock 해결")
}
}


3. Priority Inversion

  • 문제: 낮은 우선순위의 작업이 높은 우선순위 작업에 의해 블록될 때 발생.
  • 해결 방법:
    • QoS를 적절히 설정하여 우선순위를 관리.

Async/Await

Swift 5.5부터 도입된 Async/Await는 비동기 작업을 더 읽기 쉽고 직관적으로 작성할 수 있도록 설계된 동시성 프로그래밍 모델입니다. 기존의 클로저 기반 또는 콜백 지옥 문제를 해결하며, 비동기 코드를 마치 동기 코드처럼 작성할 수 있게 해줍니다.


Async/Await의 주요 특징

  1. 코드 가독성: 비동기 작업을 순차적으로 작성 가능.
  2. 에러 처리: **try-catch**를 사용해 비동기 함수에서 발생하는 오류를 처리.
  3. 동기 코드와의 자연스러운 통합: 비동기 작업과 동기 작업을 혼합하여 직관적인 코드 작성 가능.

Async/Await 사용 방법

1. 비동기 함수 정의

  • async 키워드를 사용하여 비동기 함수 선언.
  • 반환 타입은 일반적으로 **async ->로 표시.
func fetchData() async -> String {
// 네트워크 요청 등 비동기 작업 처리
return "Data fetched"
}

2. 비동기 함수 호출

  • 비동기 함수는 반드시 await 키워드로 호출.
  • 호출부는 async 환경에서만 가능.
Task {
let data = await fetchData()
print(data)
}

3. 비동기 함수와 오류 처리

  • throws**를 사용해 비동기 작업 중 발생하는 오류 처리 가능.
  • 호출부에서 try await 키워드 사용.
func fetchData() async throws -> String {
if Bool.random() {
throw URLError(.badServerResponse)
}
return "Data fetched"
}

Task {
do {
let data = try await fetchData()
print(data)
} catch {
print("Error fetching data: \(error)")
}
}


4. 병렬 처리

  • **async let**을 사용하면 비동기 작업을 병렬로 실행 가능.
func fetchImage() async -> String {
"Image Data"
}

func fetchUserProfile() async -> String {
"User Profile Data"
}

Task {
async let image = fetchImage()
async let profile = fetchUserProfile()

let results = await (image, profile)
print("Results: \\(results)")

}


5. Task 그룹

  • 여러 비동기 작업을 그룹화하여 관리.
func fetchData(for ids: [Int]) async -> [String] {
await withTaskGroup(of: String.self) { group in
for id in ids {
group.addTask {
return "Data for ID: \(id)"
}
}

    var results = [String]()
    for await result in group {
        results.append(result)
    }
    return results
}

}


Async/Await의 장점

  1. 가독성 향상: 비동기 작업을 순차적 흐름처럼 작성 가능.
  2. 에러 처리 통합try-catch로 동기 코드와 동일하게 에러 처리.
  3. 병렬 작업 지원async let 및 TaskGroup을 통한 효율적인 병렬 처리.

Async/Await의 주의점

  1. 호출 컨텍스트 제한: **await**는 반드시 비동기 환경에서 호출되어야 함.
  2. 하위 호환성 문제: iOS 13 이하에서는 사용할 수 없음.
  3. 적절한 사용 필요: 모든 비동기 작업에 적용하기보다 적합한 경우에만 사용.

Async/Await는 비동기 작업을 보다 쉽게 관리할 수 있도록 해주는 강력한 도구이며, 동시성 코드를 작성할 때 기본적인 선택이 되고 있습니다.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions