Contents

  1. 프로세스와 스레드

  2. 동기 vs 비동기 프로그래밍

  3. 비동기 프로그래밍 구현 방법

    • GCD (Grand Central Dispatch)
    • async await
  4. async await 를 사용할 때 주의점

프로세스와 스레드, 교착상태 이해하기

라면을 끓이는 과정으로 비유해서 이해할 수 있다.

프로세스와 스레드

  • CPU = 요리사:

    • 요리사는 CPU처럼 주방에서 여러 요리를 처리하는 사람입니다.
  • 프로세스 = 라면을 끓이는 과정:

    • 라면을 끓이는 과정이 하나의 독립된 작업, 즉 프로세스입니다. 각 라면 요리는 독립적으로 수행되며, 다른 요리(프로세스)와 직접적으로 영향을 주지 않습니다.
  • 스레드 = 작은 업무:

    • 스레드는 프로세스 내에서 작은 작업들입니다. 예를 들어, 라면을 끓이는 과정에서 면을 끓이고, 국물을 만드는 것이 각각의 스레드라고 할 수 있습니다.
    • 또 다른 예로, 게임을 다운받는 동안 브라우저의 다른 탭에서 작업하는 것이 스레드입니다.
    • 이때, 스레드는 ‘주방 칼’처럼 하나의 자원을 공유하며 작업을 수행합니다.

교착 상태(데드락) 비유

  • 파를 써는 과정 = 스레드:

    • 요리사가 파를 써는 과정은 하나의 스레드입니다. 이 스레드는 칼이라는 자원을 사용합니다.
  • 양파를 써는 과정:

    • 동일한 스레드에서 파를 쓰는 동안 양파를 썰어야 하는 경우를 생각해봅시다. 파를 써는 작업이 끝나지 않았지만 양파를 쓸기 위해 칼(자원)이 필요합니다. 그러나 칼이 이미 파를 써는 데 사용되고 있기 때문에, 양파를 쓸 수 없습니다.
  • 교착 상태(데드락):

    • 이 상황이 교착 상태입니다. 파를 써는 스레드가 칼을 계속 사용하고 있는 동안, 동일한 칼이 필요하지만 사용할 수 없는 상태가 됩니다. 두 작업 모두 완료되지 못하고 멈추게 됩니다.

식사하는 철학자 문제

  • 식사하는 철학자 문제는 컴퓨터 과학에서 교착 상태와 자원 할당 문제를 설명하는 전형적인 문제입니다. 철학자들이 식사하고 생각하는 행위를 반복하며, 식사하기 위해 포크 두 개가 필요합니다. 하지만 포크는 공유 자원이기 때문에 철학자들이 동시에 포크를 잡으려 하면 교착 상태가 발생할 수 있습니다.

요약

  1. 프로세스: 독립적인 작업(라면을 끓이는 과정)이며, 각 프로세스는 독립된 자원과 메모리를 가집니다.
  2. 스레드: 프로세스 내에서 작은 작업들(파를 써는 과정)이며, 자원을 공유합니다.
  3. 교착 상태: 자원을 공유하는 스레드가 서로 필요한 자원을 점유하고 있어 작업이 진행되지 않는 상태(파를 써는 동안 양파를 써야 하는 경우).
  4. 식사하는 철학자 문제: 교착 상태와 자원 할당 문제를 설명하는 전형적인 예.

이 비유를 통해 프로세스, 스레드, 그리고 교착 상태에 대한 개념을 더 명확하게 이해할 수 있습니다.

동기와 비동기 프로그래밍

동기 프로그래밍

  • 정의: 작업이 순차적으로 실행되는 방식.
  • 특징:
    • 하나의 작업이 완료될 때까지 다음 작업이 시작되지 않음.
    • 코드가 직관적이고 이해하기 쉬움.
    • 긴 작업이 있을 경우, 메인 스레드를 블로킹할 수 있음.

비동기 프로그래밍

  • 정의: 작업이 동시에 실행될 수 있는 방식.
  • 특징:
    • 작업이 완료되기를 기다리지 않고 다음 작업을 시작할 수 있음.
    • 메인 스레드의 블로킹을 방지하여 응답성을 향상시킴.
    • 비동기 작업의 결과는 콜백, 프로미스, async/await 등을 통해 처리.

비동기 프로그래밍의 구현 방법

GCD (Grand Central Dispatch)

  • 정의: Apple이 제공하는 라이브러리로, 비동기 작업을 큐에 추가하여 병렬로 실행할 수 있음.
  • 특징:
    • 글로벌 큐와 메인 큐를 사용하여 작업을 관리.
    • 작업의 우선순위를 설정할 수 있음.
  • 문제점:
    • 콜백 지옥(callback hell) 문제가 발생할 수 있음.
    • 코드가 복잡하고 가독성이 떨어짐.

예제 코드 (GCD 사용)

func fetchDataFromServer(completion: @escaping (Data?) -> Void) {
    DispatchQueue.global().async {
        sleep(2) // 네트워크 요청 시뮬레이션
        let data = "Server Data".data(using: .utf8)
        completion(data)
    }
}

fetchDataFromServer { data in
    // 데이터를 처리하는 코드
}

async와 await

정의

  • async: 비동기 함수임을 나타냄.
  • await: 비동기 함수의 완료를 기다림.

특징

  • 비동기 코드를 동기 코드처럼 작성할 수 있어 가독성이 향상됨.
  • 비동기 작업을 순차적으로 실행할 수 있음.
  • 에러 처리가 간편해짐.

예제 코드 (async/await)

import Foundation

func fetchDataFromServer() async throws -> Data {
    try await Task.sleep(nanoseconds: 2 * 1_000_000_000) // 네트워크 요청 시뮬레이션
    guard let data = "Server Data".data(using: .utf8) else {
        throw NSError(domain: "Invalid Data", code: 1, userInfo: nil)
    }
    return data
}

Task {
    do {
        let data = try await fetchDataFromServer()
        print("Fetched Data: \(data)")
    } catch {
        print("Failed to fetch data: \(error)")
    }
}

async let

async let을 사용하면 비동기 작업을 동시에 시작하고 그 결과를 나중에 사용할 수 있습니다. 이를 통해 동시성을 쉽게 구현할 수 있습니다.

import Foundation

// Helper function to simulate network fetch
func fetchPage(url: String) async -> String {
    print("Fetching \(url)...")
    try? await Task.sleep(nanoseconds: 2 * 1_000_000_000) // Simulate network delay
    return "Content of \(url)"
}

// Main function to fetch multiple pages concurrently
func fetchMultiplePages() async {
    // Start fetching pages concurrently
    async let page1 = fetchPage(url: "https://example.com/page1")
    async let page2 = fetchPage(url: "https://example.com/page2")
    async let page3 = fetchPage(url: "https://example.com/page3")

    // Await the results
    let result1 = await page1
    let result2 = await page2
    let result3 = await page3

    // Print the results
    print("Result 1: \(result1)")
    print("Result 2: \(result2)")
    print("Result 3: \(result3)")
}

// Execute the function
Task {
    await fetchMultiplePages()
}

async await 를 사용할 때 주의점(스레드 관리)

아래 글을 참조

async, await 를 사용하면서 겪은 UI thread(Main Thread) 관리 문제

결론

  • SwiftUI는 UI에서 비동기 코드를 호출하는 것을 최대한 자연스럽게 만드는 메커니즘을 제공한다.
  • .task 뷰 수정자를 사용하여 뷰가 나타날 때 비동기 코드를 실행할 수 있으며, 뷰가 사라지면 SwiftUI가

자동으로 작업을 취소한다.

  • .refreshable과 .searchable 뷰 수정자는 클로저에 비동기 컨텍스트를 생성하므로 내부에서 쉽게 비동기 코드를 호출할 수 있다.