์•ž์—์„œ๋Š” ์ƒˆ๋กญ๊ฒŒ ๋‚˜์˜จ ๊ฐœ๋…๋“ค์„ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์•˜๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ์™œ ์ด๋ ‡๊ฒŒ ์„ค๊ณ„ํ–ˆ๋Š”์ง€, ์‹ค์ œ๋กœ๋Š” ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ์•Œ์•„๋ณด์ž.

Threading Model

New feed reader ์•ฑ์„ ๋งŒ๋“ ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ณด์ž. ๊ณ ์ˆ˜์ค€์—์„œ ์–ด๋– ํ•œ ๊ฒƒ๋“ค์ด ํ•„์š”ํ• ์ง€ ์ƒ๊ฐํ•ด๋ณด์ž.

  1. User Interface๋ฅผ ์ฒ˜๋ฆฌํ•  main thread๊ฐ€ ์žˆ๋‹ค.
  2. User๊ฐ€ ๊ตฌ๋…ํ•œ news feed๋ฅผ ์ถ”์ ํ•  Database๋„ ์žˆ๋‹ค.
  3. ๋งˆ์ง€๋ง‰์œผ๋กœ feed๋กœ ๋ถ€ํ„ฐ ์ตœ์‹  content๋ฅผ ๋ฐ›์•„์˜ฌ ๋„คํŠธ์›Œํฌ ์ฒ˜๋ฆฌ๋‹จ์ด ์žˆ๋‹ค.

Grand Central Dispatch

User๊ฐ€ ์ƒˆ๋กœ์šด news feed๋ฅผ ๊ฐ€์ ธ์˜ค๋ผ๋Š” gesture๋ฅผ ํ–ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ณด์ž. GCD๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฒ˜๋ฆฌํ–ˆ์—ˆ๋‹ค.

User์˜ gesture๋ฅผ ๋ฐ›์•„ Serial Dispatch Queue์— async ํ•˜๊ฒŒ ๋™์ž‘์„ ๋„˜๊ธด๋‹ค.

  • ๋‹ค๋ฅธ Dispatch Queue์—์„œ ์ž‘์—…์„ ๋ฝ‘์•„์˜ด์œผ๋กœ์จ ๋งŽ์€ ์–‘์˜ ์ž‘์—…์ผ์ง€๋ผ๋„ main thread๊ฐ€ user์˜ ๋™์ž‘์„ ๊ณ„์† ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด์„œ
  • serial queue๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ database์˜ ์ ‘๊ทผ์— ์žˆ์–ด ์ƒํ˜ธ ๋ฐฐ์ œ๋ฅผ ๋ณด์žฅ

Database Queue์•ˆ์—์„œ user๊ฐ€ ๊ตฌ๋…ํ•œ feed๋“ค์„ iterateํ•˜๋ฉฐ, network ์š”์ฒญ์„ ํ•˜๋„๋ก URL Session์— ๋„˜๊ธด๋‹ค.

Network ๊ฒฐ๊ณผ๊ฐ€ ๋“ค์–ด์˜ค๋ฉด, URL Session callback์ด delegate queue์—์„œ ์‹คํ–‰๋œ๋‹ค.

  • ์ด ๋•Œ Queue๋Š” conccurent queue๋กœ ์ง€์ •ํ•˜์—ฌ parallelism์„ ์ ์šฉ์‹œ์ผœ ํšจ์œจ์ ์œผ๋กœ ๋™์ž‘์‹œํ‚ค๋„๋ก ํ•ด๋ณด์ž.

callback์—์„œ๋Š” ๋ฐ›์€ ๊ฒฐ๊ณผ๋ฅผ Database Queue(Serial)์— ์ž‘์—…์„ ๋„˜๊ธด๋‹ค.

  • ์ˆœ์ฐจ์ ์œผ๋กœ ๋ฐ˜์˜๋˜๊ธฐ ๋•Œ๋ฌธ์— data race ์ƒํ™ฉ์€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • ๊ฐ€์žฅ ์ตœ์‹ ์˜ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐ˜์˜๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์ด ๊ณผ์ •์—์„œ cache๋ฅผ ์ ์šฉํ•  ์ˆ˜๋„ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ main thread์— ํ•ด๋‹น ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜์˜ํ•˜์—ฌ UI๋ฅผ ์—…๋ฐ์ดํŠธํ•œ๋‹ค.

์ด ๊ณผ์ •์€ ๋งค์šฐ ํ•ฉ๋ฆฌ์ ์ธ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค. main thread๋ฅผ ๋ง‰์ง€ ์•Š์•„ UI ์—…๋ฐ์ดํŠธ๋„ ์ผ์–ด๋‚˜๊ฒŒ ํ•˜์˜€์œผ๋ฉฐ, network ์š”์ฒญ๋„ concurrentํ•˜๊ฒŒ ์ˆ˜ํ–‰ํ•˜๋ฉฐ, data base์—์„œ๋Š” serial queue๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ data race ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก ํ–ˆ๋‹ค. ์ด๋ฒˆ์—๋Š” ์ฝ”๋“œ๋ฅผ ๋ด๋ณด์ž.

func deserializeArticles(from data: Data) throws -> [Article] { /* ... */ }
func updateDatabase(with articles: [Article], for feed: Feed) { /* ... */ }
 
// 1. ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ์œ„ํ•œ URLSession(Concurrent Queue)
let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: concurrentQueue) 
 
// 2. Database์—์„œ ๊ฐ€์ ธ์˜จ feed ๋ชฉ๋ก์— ๋Œ€ํ•ด ๋ชจ๋‘ network ์š”์ฒญํ•œ๋‹ค.
for feed in feedsToUpdate {
    // 3. completion handler์— ์ ํžŒ task๋Š” system์—์„œ ์š”์ฒญ์„ ๋ฐ›์€ ํ›„์— ์ฒ˜๋ฆฌ๋œ๋‹ค.
    // ๊ฒฐ๊ณผ๋Š” delegate queue, ์—ฌ๊ธฐ์„œ๋Š” concurrent queue์—์„œ ๋ฐ›๊ฒŒ ๋œ๋‹ค.
    let dataTask = urlSession.dataTask(with: feed.url) { data, response, error in
        // ...
        guard let data = data else { return }
        do {
            // 4. ๋ฐ›์€ ๊ฒฐ๊ณผ์— ๋Œ€ํ•ด deserialization ํ•œ๋‹ค.
            let articles = try deserializeArticles(from: data)
            // 5. ํ™”๋ฉด์— ์—…๋ฐ์ดํŠธ ๋˜๊ธฐ์ „์— ๊ฒฐ๊ณผ์— ๋Œ€ํ•ด database queue์— task๋ฅผ sync๋กœ ๋„˜๊ฒจ ๋ฐ˜์˜ํ•œ๋‹ค.
            databaseQueue.sync {
                updateDatabase(with: articles, for: feed)
            }
        } catch { /* ... */ }
    }
    dataTask.resume()
}

์•„๊นŒ ์ƒ๊ฐํ•œ ํ๋ฆ„์— ๋Œ€ํ•ด ์ฝ”๋“œ๋กœ ์Šค๋ฌด์Šคํ•˜๊ฒŒ ์ ํ˜”๋‹ค. ํ•˜์ง€๋งŒ ์ด ์ฝ”๋“œ๋Š” ์„ฑ๋Šฅ ์ธก๋ฉด์—์„œ ์ˆจ๊ฒจ์ง„ ํ•จ์ •์ด ์žˆ๋‹ค.

GCD and thread bring up

์ด ๋ฌธ์ œ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” GCD Queue๊ฐ€ ์–ด๋–ป๊ฒŒ work item์„ ์ฒ˜๋ฆฌํ•˜๋Š”์ง€ ์•Œ์•„์•ผ ํ•œ๋‹ค.

  1. concurrent queue๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ work item์„ ํ•œ๋ฒˆ์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์‹œ์Šคํ…œ์€ CPU Core์ˆ˜๊ฐ€ ํฌํ™”๋˜๋Š” ์ˆ˜์ค€๊นŒ์ง€ ์—ฌ๋Ÿฌ๊ฐœ์˜ thread๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
  2. ๊ทธ๋Ÿฐ๋ฐ ๋งŒ์•ฝ, Thread๊ฐ€ Block๋˜๋ฉด, (๊ทธ๋ฆฌ๊ณ  ๋” ๋งŽ์€ ์ˆ˜ํ–‰๋  work๋“ค์ด ์žˆ๋‹ค๋ฉด) System์€ ํ•ด๋‹น CPU Core๋ฅผ ์ฑ„์šธ ์ˆ˜ ์žˆ๋Š” thread๋ฅผ ๋” ๊ฐ€์ ธ์˜จ๋‹ค.
    • ๋‹ค๋ฅธ Thread์—๊ฒŒ ์ฒ˜๋ฆฌ ๊ถŒํ•œ์„ ๋„˜๊ฒจ์คŒ์œผ๋กœ์จ ๋‹ค๋ฅธ Thread๊ฐ€ ์ผ์„ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด์„œ
    • Blocked๋œ Thread๋Š” ๋‹ค์Œ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด semaphore์ฒ˜๋Ÿผ ๋‹ค์Œ resource๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ์— ๋‹ค์Œ ์ž‘์—…์ด ์ฒ˜๋ฆฌ๋˜์–ด์•ผ block์ด ํ•ด์ œ๋  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹์„ ์ฑ„ํƒํ–ˆ๋‹ค.
    • ์ด๋ ‡๊ฒŒ ์ƒˆ๋กญ๊ฒŒ ๋งŒ๋“ค์–ด์ง„ thread๋Š” resource๋ฅผ unlockํ•˜์—ฌ ์ž‘์—…์„ ๊ณ„์† ์ด์–ด๋‚˜๊ฐˆ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.

์ด์ œ ๋™์ž‘์„ ์ข€ ์ดํ•ดํ–ˆ์œผ๋‹ˆ ๋‹ค์‹œ news feed์•ฑ์œผ๋กœ ๋Œ์•„๊ฐ€๋ณด์ž.

CPU Execution

URLSession์˜ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋™์ž‘์—์„œ Apple watch ์ฒ˜๋Ÿผ 2๊ฐœ์˜ ์ฝ”์–ด๊ฐ€ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ณด์ž.

  1. Core๊ฐ€ 2๊ฐœ์ด๋ฏ€๋กœ GCD๋Š” feed update result๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด 2๊ฐœ์˜ thread๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
  2. ๊ทธ ์•ˆ์—์„œ databaseQueue.sync๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์žˆ๊ธฐ ๋Œ€๋ฌธ์—, ํ•ด๋‹น task๋Š” block๋œ๋‹ค.
  3. GCD๋Š” ๋‹ค์Œ thread๋ฅผ ๊ฐ€์ ธ์™€ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.
  4. ๊ทธ ์•ˆ์—๋„ databaseQueue.sync๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— block๋œ๋‹ค.
  5. 2~3์ด loop๋ฅผ ๋‹ค ๋Œ๋•Œ๊นŒ์ง€ ๋ฐ˜๋ณต๋œ๋‹ค.

์ด ๊ณผ์ •์—์„œ CPU๋Š” ๋‹ค๋ฅธ Thread๊ฐ„์˜ Context Switching์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์ฆ‰, ์šฐ๋ฆฌ๊ฐ€ ์ž‘์„ฑํ•œ ์ฝ”๋“œ์—์„œ ์‰ฝ๊ฒŒ ๋งŽ์€ ์ˆ˜์˜ thread๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ , ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ์ €ํ•˜๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ๋‹ค.

Excessive concurrency

Thread ์ˆ˜๊ฐ€ ๋งŽ์•„์ง€๋Š” ๊ฒƒ์€ Application์— ์ข‹์ง€ ์•Š์€ ์˜ํ–ฅ์„ ๋ผ์นœ๋‹ค.

  • CPU Core๋ณด๋‹ค ๋งŽ์€ ์ˆ˜์˜ thread๋Š” ์‹œ์Šคํ…œ์˜ ๋™์ž‘์„ ๋‚ญ๋น„ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
  • Thread Explosion
    • 6๊ฐœ์˜ core๊ฐ€ ์žˆ๋Š” iphone์„ ์ƒ๊ฐํ•ด๋ดค์„ ๋•Œ 100๋ฒˆ์˜ feed update๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค๋ฉด, ์šฐ๋ฆฌ๋Š” core์ˆ˜๋ณด๋‹ค 16๋ฐฐ๋‚˜ ๋งŽ์€ thread๋ฅผ ์ƒ์„ฑํ•œ ๊ฒƒ์ด๋‹ค.
    • Memory overhead
    • Scheduling overhead

๊ฒฐ๊ตญ, core ์ˆ˜๋ณด๋‹ค ๋งŽ์€ ์–‘์˜ thread๋ฅผ ํ†ตํ•ด ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์€ memory, scheduling์— ์žˆ์–ด ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚จ๋‹ค.

Memory overhead

์‰ฝ๊ฒŒ ์•Œ ์ˆ˜ ์žˆ๊ฒ ์ง€๋งŒ, block๋œ thread๋Š” ๊ฒฐ๊ตญ ๋‚˜์ค‘์— ๋Œ์•„์™€์„œ ๋ณธ์ธ์˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•œ๋‹ค. ์ด์ ์—์„œ ์ž‘์—…์ด ์ค‘๋‹จ๋œ ์ƒํƒœ๋ฅผ ๊ธฐ์–ตํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•˜๊ณ , ์ด๋Š” ๊ณง memory์— ์ €์žฅ๋˜๊ฒŒ ๋œ๋‹ค.

๊ฐ๊ฐ์˜ thread์—์„œ๋Š” ์‹คํ–‰ ํ๋ฆ„์„ ์ €์žฅํ•˜๋Š” stack์ด ์žˆ๊ณ , ๊ทธ์™€ ๊ด€๋ จ๋œ kernal ์ž๋ฃŒ๊ตฌ์กฐ๊ฐ€ thread๋ฅผ ์ถ”์ ํ•˜๊ณ  ์žˆ๋‹ค. ๋ช‡๋ช‡ ์Šค๋ ˆ๋“œ๋Š” lock์œผ๋กœ ๋ถ™์žกํ˜€์žˆ์–ด, ๋‹ค๋ฅธ thread์˜ ๋™์ž‘์ด ํ•„์š”ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์ฆ‰, ์ด๋Ÿฐ ๋‹น์žฅ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” thread๋“ค์ด ๋งŽ์€ ์–‘์˜ memory์™€ ์ž์›์„ ๋ถ™์žก๋Š” ์ƒํƒœ๊ฐ€ ๋ฒŒ์–ด์ง„๋‹ค.

๋‚ด์šฉ์ด ํ—ท๊ฐˆ๋ฆฐ๋‹ค๋ฉด 07: ์“ฐ๋ ˆ๋“œ(Thread)์„ ์ฐธ๊ณ ํ•˜์ž.

Scheduling overhead

Thread๊ฐ€ ๋งŽ์•„์ง€๋Š” ๊ฒƒ์€ ๋ฉ”๋ชจ๋ฆฌ ์ธก๋ฉด์—์„œ๋งŒ ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. ๊ฒฐ๊ตญ ์ด๋Ÿฐ Thread๋“ค์ด ์–ธ์ œ ์ฒ˜๋ฆฌ๋  ๊ฒƒ์ธ์ง€๋ฅผ ๊ด€์žฅํ•˜๋Š” ์Šค์ผ€์ฅด๋ง๋„ ํ•ด์•ผ ํ•˜๋Š”๋ฐ, ๋งŽ์•„์งˆ ์ˆ˜๋ก ์ด๋Ÿฐ ๊ณผ์ •์€ ๋”์šฑ ๋ณต์žกํ•ด์ง€๊ธฐ ๋งˆ๋ จ์ด๋‹ค.

์ œํ•œ๋œ Core์ˆ˜๋ฅผ ๊ฐ€์ง„ ์ƒํƒœ์—์„œ Thread explosion์ด ๋ฐœ์ƒํ•˜๋ฉด, ๊ณผ๋„ํ•œ Context Switching์„ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. ์ด๋ ‡๊ฒŒ ๋Šฆ์–ด์ง„ ์ฒ˜๋ฆฌ๋Š” ๊ฒฐ๊ตญ ์ค‘์š”ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ๋Šฆ๊ฒŒํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ CPU๊ฐ€ ํšจ๊ณผ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉํ•ดํ•œ๋‹ค.

Swift Concurrency

์œ„์—์„œ ์‚ดํŽด๋ณธ ๊ฒƒ๊ณผ ๊ฐ™์ด Thread explosion์€ GCD์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ์ด๋‹ค. ๊ทธ๋Ÿผ์—๋„ ์ด ๋ถ€๋ถ„์„ ์„ฌ์„ธํ•˜๊ฒŒ ์บ์น˜ํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ ๋†“์น˜๊ธฐ ์‰ฌ์šด ๋ถ€๋ถ„์ด๋‹ค. ์ด๋Ÿฐ ๋ถ€๋ถ„์—์„œ Swift์˜ ์–ธ์–ด์„ค๊ณ„๋Š” concurrency๋ฅผ ์„ค๊ณ„ํ•˜๋Š”๋ฐ ์žˆ์–ด ๋‹ค๋ฅธ ์ ‘๊ทผ์„ ๋„์ž…ํ–ˆ๋‹ค.

  1. Core ์ˆ˜์— ๋งž๋Š” Thread๋งŒ ์‚ฌ์šฉํ•œ๋‹ค.
    • Blocking Thread๊ฐ€ ์—†์–ด์ง„๋‹ค.
    • Full Thread Context Switching์ด ์—†์–ด์ง„๋‹ค.
  2. ๋Œ€์‹  ๊ฐ€๋ฒผ์šด Object์ธ Continuation์„ ์‚ฌ์šฉํ•œ๋‹ค.
    • ์ž‘์—…์˜ ์žฌ๊ฐœ ์—ฌ๋ถ€๋ฅผ ์ถ”์ ํ•œ๋‹ค.
    • Continuation๊ฐ„์˜ switching์ด ๋ฐœ์ƒํ•œ๋‹ค.
    • Full Thread Context Switching๋Œ€์‹  function call๋กœ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฆ‰, Swift concurrency์—์„œ๋Š” runtime์— CPU Core ์ˆ˜๋งŒํผ์˜ Thread๋งŒ ์ƒ์„ฑํ•˜์—ฌ ์ €๋ ดํ•˜๊ณ  ํšจ์œจ์ ์œผ๋กœ work item์ด block๋˜์—ˆ์„ ๋•Œ Switching์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์ด๋Ÿฐ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” runtime์— Thread๋ฅผ ์ฐจ๋‹จํ•˜์ง€ ์•Š๋Š” Contract์ด ํ•„์š”ํ•˜๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์–ธ์–ด ์—ญ์‹œ ์ด๋ฅผ ์ง€์›ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ–ˆ๋‹ค.

Language features

์ด์— Swift์˜ Concurrency์—์„œ๋Š” ์ด Runtime Contract๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ๋งˆ๋ จํ–ˆ๋‹ค. ์–ธ์–ด ์ฐจ์›์—์„œ ์ œ๊ณตํ•˜๋Š” ๋‘๊ฐ€์ง€ ๊ธฐ๋Šฅ์„ ์„ค๋ช…ํ•˜๊ฒ ๋‹ค.

  • await์˜ ์‚ฌ์šฉ๊ณผ thread๋ฅผ non-block์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.
  • Swift runtime์— task์˜ ์˜์กด์„ฑ์„ ์ถ”์ ํ•œ๋‹ค.

์ด์ „์˜ news feed ์ฝ”๋“œ๋ฅผ Swift concurrency๋ฅผ ์ ์šฉํ•ด ๋‹ค์‹œ ์ ์–ด๋ณด์ž.

func deserializeArticles(from data: Data) throws -> [Article] { /* ... */ }
func updateDatabase(with articles: [Article], for feed: Feed) async { /* ... */ }
 
// 1. Concurrent Queue์—์„œ network ๊ฒฐ๊ณผ๋ฅผ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๊ณ , concurrency๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด `TaskGroup`์„ ์‚ฌ์šฉํ•œ๋‹ค.
await withThrowingTaskGroup(of: [Article].self) { group in
    for feed in feedsToUpdate {
        // 2. `TaskGroup`์—์„œ Child task๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ๊ฐ์˜ feed๊ฐ€ update๋˜์–ด์•ผ ํ•จ์„ ๋ช…์‹œํ•œ๋‹ค.
        group.async {
            // 3. feed์˜ url์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋„คํŠธ์›Œํฌ ์š”์ฒญํ•œ๋‹ค.
            let (data, response) = try await URLSession.shared.data(from: feed.url)
            // 4. ๊ฒฐ๊ณผ๋ฅผ deserializeํ•œ๋‹ค.
            let articles = try deserializeArticles(from: data)
            // 5. async function์ธ updateDatabase๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์—…๋ฐ์ดํŠธํ•œ๋‹ค.
            await updateDatabase(with: articles, for: feed)
            return articles
        }
    }
}

์—ฌ๊ธฐ์„œ ์ฃผ๋ชฉํ•  ๋ถ€๋ถ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. async function์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ await ํ‚ค์›Œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.
  2. await ํ‚ค์›Œ๋“œ๋Š” ํ˜„์žฌ ์ž‘์—…ํ•˜๊ณ  ์žˆ๋Š” Thread๋ฅผ Blockํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋‹ค. non-blocking์ด๋‹ค.
  3. block์ด ์•„๋‹ˆ๊ณ  ํ•ด๋‹น ์ž‘์—…์„ suspend์‹œํ‚ค๊ณ , ๋‹ค๋ฅธ ์ž‘์—…์„ ํ•˜๋Ÿฌ ๊ฐ„๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ๊ฐ€๋งŒ ์žˆ์–ด๋ณด์ž. ์–ด๋–ป๊ฒŒ ์ž‘์—…์ด thread๋ฅผ ํฌ๊ธฐํ•˜๊ณ  ๋‚˜๊ฐˆ ์ˆ˜ ์žˆ์„๊นŒ?

await and non-blocking of threads

async function์ด ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„๋˜์–ด ์žˆ๋Š”์ง€ ์•Œ์•„๋ณด๊ธฐ ์ „์—, ์–ด๋–ป๊ฒŒ non-async function์ด ๋™์ž‘ํ•˜๋Š”์ง€ ์•Œ์•„๋ณด์ž.

Non-async functions

Program์—์„œ ๋™์ž‘ํ•˜๋Š” ๋ชจ๋“  Thread๋Š” ํ•จ์ˆ˜ call์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ stack์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

ํ•˜๋‚˜์˜ stack์„ ์‚ดํŽด๋ณด์ž. ํ•˜๋‚˜์˜ ํ•จ์ˆ˜๊ฐ€ call๋˜๋ฉด ์ƒˆ๋กœ์šด frame์ด stack์— push๋œ๋‹ค. ์ƒˆ๋กญ๊ฒŒ ๋งŒ๋“ค์–ด์ง„ frame์€ ์ง€์—ญ ๋ณ€์ˆ˜ ์ €์žฅ, ๋ฐ˜ํ™˜ ์ฃผ์†Œ ์ „๋‹ฌ, ๋‹ค๋ฅธ ๊ธฐํƒ€ ์šฉ๋„๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค. ์ผ๋‹จ ํ•จ์ˆ˜๊ฐ€ ๋™์ž‘์„ ๋ฐ”์น˜๊ณ  ๋ฐ˜ํ™˜ํ•˜๋ฉด, stack frame์€ pop๋œ๋‹ค.

Async functions

func updateDatabase(with articles: [Article], for feed: Feed) async throws {
    // skip old articles ...
    try await feed.add(articles) // 1. `updateDatabase` ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ `feed.add` ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค.
}
 
// on Feed
func add(_ newArticles: [Article]) async throws {
    let ids = try await database.save(newArticles, for: self) // await ์ดํ›„์— ํ•„์š”ํ•œ ๋ณ€์ˆ˜๋“ค์€ stack frame์— ์ €์žฅ๋œ๋‹ค.
    // 2. ์ด๋Ÿฐ ์ด์œ ๋กœ, `id`, `article`์€ stack frame์— ์ €์žฅ๋œ๋‹ค.
    for (id, article) in zip(ids, newArticles) { // 3, 4. newArticle์€ heap frame์—์„œ ์ถ”์ ๋œ๋‹ค.
        articles[id] = article
    }
}
 
// on Database
func save(_ newArticles: [Article], for feed: Feed) async throws -> [ID] { /* ... */ }

Database ๊ตฌํ˜„์ฒด์— save method๊ฐ€ ์žˆ๊ณ , Feed๊ตฌํ˜„์ฒด์— add method๊ฐ€ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ณด์ž.

  1. updateDatabase ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ feed.add ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค.
    • stack frame์€ ์ด suspension point์—์„œ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†๋Š” ์ง€์—ญ ๋ณ€์ˆ˜๋ฅผ ์ €์žฅํ•œ๋‹ค.
    • add ํ•จ์ˆ˜์˜ ์•ˆ์—๋Š” await๋กœ ํ‘œ์‹œ๋œ ํ•˜๋‚˜์˜ suspension point๊ฐ€ ์žˆ๋‹ค.
    • id, article์€ suspension point ์ •์˜๋œ ํ›„ loop body์•ˆ์—์„œ ์ฆ‰๊ฐ ์‚ฌ์šฉ๋œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์‚ฌ์ด์— suspension point๋„ ์—†๋‹ค.
  2. ์ด๋Ÿฐ ์ด์œ ๋กœ, id, article์€ stack frame์— ์ €์žฅ๋œ๋‹ค.
  3. ์ถ”๊ฐ€์ ์œผ๋กœ heap์—๋Š” ๋‘๊ฐœ์˜ async frame์ด ์žˆ๋‹ค.
    • updateDatabase, add
    • ์ด์˜ ์กด์žฌ ์ด์œ ๋Š”, suspendsion point๋ฅผ ๋„˜๋‚˜๋“ค์–ด ์ €์žฅํ•ด์•ผ ํ•˜๋Š” ์ •๋ณด๋ฅผ ๋‹ด๊ธฐ ์œ„ํ•จ์ด๋‹ค.
    • newArticles argument๋Š” await ์ „์— ์ •์˜๋˜์—ˆ์œผ๋‚˜ await ์ดํ›„์— ์‚ฌ์šฉ๋˜์–ด ์ง„๋‹ค.
  4. ์ด๋Ÿฐ ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•˜์—ฌ add async frame์— newArticles์ด ์ €์žฅ๋˜๋ฉฐ, ์ด๋Š” newArticles์„ ์ถ”์ ํ•  ๊ฒƒ์ด๋‹ค.
  5. ์ด์ œ save ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๋ฉด, add๋Š” save๋ฅผ ์œ„ํ•œ ์ƒˆ๋กœ์šด stack frame์œผ๋กœ ๋Œ€์ฒด๋œ๋‹ค.
    • ์ƒˆ๋กœ์šด stack frame์„ pushํ•˜๋Š” ๊ฒƒ ๋Œ€์‹ , top์— ์žˆ๋Š” stack frame์€ ๋Œ€์ฒด๋œ๋‹ค.
    • ์ด๋Š” ํ›„์— ํ•„์š”ํ•œ ๋ณ€์ˆ˜(newArticles)๊ฐ€ ์ด๋ฏธ async frame list์— ์ €์žฅ๋๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

5๋ฒˆ๊นŒ์ง€ ์ง„ํ–‰ํ•œ ํ›„์—, save function์˜ ์‹คํ–‰์ด suspend๋˜์—ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ณด์ž. ์ƒํ™ฉ์ด ์ด๋ ‡๋‹ค๋ฉด, thread๊ฐ€ block๋˜์–ด ์žˆ๋Š” ๊ฒƒ๋ณด๋‹ค ๋‹ค๋ฅธ ์ž‘์—…์„ ํ•˜๊ธฐ ์œ„ํ•ด ์žฌ์‚ฌ์šฉ ๋˜๋Š” ๊ฒƒ์ด ๋ณด๋‹ค ์ข‹๋‹ค. ์œ„์˜ ์‚ฌ์ง„์€ stack์— ๋‹ค๋ฅธ ์ž‘์—…์ด ๋“ค์–ด์™€์žˆ๊ณ , ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ async frame์— ์ž‘์—… ์‚ฌํ•ญ์„ ์ €์žฅํ•˜๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋ƒˆ๋‹ค.

suspension point๋ฅผ ๊ฑธ์ณ ํ•„์š”ํ•œ ๋ชจ๋“  ์ •๋ณด๊ฐ€ heap์— ์ €์žฅ๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์—, conitinue ์‹คํ–‰์„ ํ†ตํ•ด ๋‚˜์ค‘์— ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋‹ค. ์ด async frame list๊ฐ€ conituation์˜ runtime ํ‘œํ˜„์ด๋‹ค.

์–ด๋Š์ •๋„ ์‹œ๊ฐ„์ด ์ง€๋‚œ ํ›„์—, database ์š”์ฒญ์ด ๋๋‚ฌ๊ณ , ๋ช‡๋ช‡ thread๊ฐ€ ๋น„์—ˆ๋‹ค. ์ด thread๋Š” ์ด์ „์— ์ž‘์—…์„ ์š”์ฒญํ•œ thread์ผ ์ˆ˜๋„ ์žˆ๊ณ  ๋‹ค๋ฅธ thread์ผ ์ˆ˜๋„ ์žˆ๋‹ค. (core๊ฐœ์ˆ˜์— ๋งž๊ฒŒ ์ƒ์„ฑ๋œ thread๋ฅผ ๋งํ•œ๋‹ค.) ์ด๋ ‡๊ฒŒ ๋นˆ thread์—์„œ ์ž‘์—…์ด ๊ณ„์†๋œ๋‹ค.

func updateDatabase(with articles: [Article], for feed: Feed) async throws {
    // skip old articles ...
    try await feed.add(articles)
}
 
// on Feed
func add(_ newArticles: [Article]) async throws {
    let ids = try await database.save(newArticles, for: self) // 1. 
    
    for (id, article) in zip(ids, newArticles) {
        articles[id] = article
    }
}
 
// on Database
func save(_ newArticles: [Article], for feed: Feed) async throws -> [ID] { /* ... */ }
  1. async frame์—์„œ ๊ฐ€์žฅ ์ตœ๊ทผ ์ž‘์—…์„ stack์œผ๋กœ ๋ถ€๋ฅธ๋‹ค. ์‹คํ–‰๊ฒฐ๊ณผ๋กœ, IDs๋ฅผ ๋ฐ˜ํ™˜ ๋ฐ›๋Š”๋‹ค.
  2. save๋ฅผ ์œ„ํ•œ stack frame์€ add๋ฅผ ์œ„ํ•œ stack frame์œผ๋กœ ๋Œ€์ฒด๋œ๋‹ค.
  3. ์ด์ œ thread๋Š” zip ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. zip ์—ฐ์‚ฐ์€ nonasync ์ž‘์—…์ด๊ธฐ ๋•Œ๋ฌธ์—, ์ƒˆ๋กœ์šด stack frame์„ ๋งŒ๋“ ๋‹ค.
    • Swift๋Š” ์—ฌ์ „ํžˆ OS์˜ stack๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋Œ€๋ฌธ์—, async, nonasyncํ•œ Swift code๋ชจ๋‘ ํšจ์œจ์ ์œผ๋กœ C ์™€ Objective-C๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ๊ทธ ๋ฐ˜๋Œ€๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.
  4. zip function์ด ๋๋‚˜๋ฉด, stack frame์€ pop๋˜๊ณ  ๊ณ„์†๋œ๋‹ค.

Tracking of dependencies in Swift task model

์ง€๊ธˆ๊นŒ์ง€์˜ ๋‚ด์šฉ์„ ๋ณด๋ฉด, ํ•จ์ˆ˜๊ฐ€ await๋ฅผ ๊ธฐ์ค€์œผ๋กœ continuations๋กœ ๊นจ์งˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ฐฐ์› ๋‹ค. ๋ฌผ๋ก  ์ž‘๋™ํ–ˆ๋˜ thread๋กœ ๋Œ์•„๊ฐˆ ์ˆ˜๋„ ์žˆ๋‹ค. ์ด๋Ÿฐ ์ ์—์„œ ํ•ด๋‹น ์ง€์ ์€ potential suspenstion point๋ผ ๋ถˆ๋ฆฐ๋‹ค.

func deserializeArticles(from data: Data) throws -> [Article] { /* ... */ }
func updateDatabase(with articles: [Article], for feed: Feed) async { /* ... */ }
 
await withThrowingTaskGroup(of: [Article].self) { group in
    for feed in feedsToUpdate {
        group.async {
            let (data, response) = try await URLSession.shared.data(from: feed.url) // โœ…
 
            // โ€ผ๏ธ
            let articles = try deserializeArticles(from: data) 
            await updateDatabase(with: articles, for: feed)
            return articles
        }
    }
}

์ด ๊ฒฝ์šฐ, โœ… ๋ถ€๋ถ„์€ async function์ด๊ณ , โ€ผ๏ธ ๋ถ€๋ถ„์€ โœ… ์ž‘์—…์—์„œ continuation์ด ๋ฐœ์ƒํ•œ ํ›„ ๊ณ„์†๋˜๋Š” ์ž‘์—…์ด๋‹ค. continuation์€ async function์ด ์™„๋ฃŒ๋œ ํ›„์—๋งŒ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋‹ค. ์ด๊ฒƒ์ด Swift concurrency runtime์—์„œ ์ถ”์ ํ•˜๋Š” ์˜์กด์„ฑ์ด๋‹ค.

๋น„์Šทํ•˜๊ฒŒ, ์ตœ์ƒ๋‹จ์— ๋ณด์ด๋Š” TaskGroup์—์„œ๋„ parent task๋Š” ํ•˜์œ„์— child task๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š”๋ฐ, ๊ฐ๊ฐ์˜ child task๊ฐ€ ์™„๋ฃŒ๋˜์–ด์•ผ parent task๊ฐ€ ๊ณ„์†๋  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ ‡๊ฒŒ scope๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ฝ”๋“œ์— ์˜์กด์„ฑ์ด ํ‘œํ˜„๋  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฐ ๊ฒƒ๋“ค์€ Swift compiler์™€ runtime์— ๋ช…์‹œ์ ์œผ๋กœ ์•Œ๋ ค์ง„๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๋Ÿฐ Task๋Š” Swift runtime์— ์•Œ๋ ค์ง„ task(Continuation, child task)๋งŒ await ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ ‡๊ฒŒ ์ œ์•ฝ์ด ๊ฑธ๋ ค์žˆ๊ธฐ ๋•Œ๋ฌธ์—, Swift concurrency ์›์‹œ ํƒ€์ž…์œผ๋กœ ๋งŒ๋“ค์–ด์ง„ Swift concurrency๋Š” runtime์— task๊ฐ„์— dependency chain์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์ œ๊ณตํ•œ๋‹ค.

Cooperative thread pool

๊ฒฐ๊ตญ ์ด๋Ÿฐ ๊ณผ์ •์„ ํ†ตํ•ด, thread๋Š” task dependency์— ๋Œ€ํ•ด ์ถ”๋ก ํ•  ์ˆ˜ ์žˆ๊ณ , ๋‹ค๋ฅธ task๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฆ‰, ์œ„์˜ ๊ณผ์ •๋“ค์„ ํ†ตํ•ด์„œ thread๊ฐ€ ํ•ญ์ƒ ๋ฌด์–ธ๊ฐ€๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” runtime contract๋ฅผ ๋‹ฌ์„ฑํ–ˆ๋‹ค.

  • ์ด๋Š” Default executor๋กœ Swift concurrency๋ฅผ ์ง€์›ํ•˜๋Š” ์ƒˆ๋กœ์šด ํ˜•ํƒœ์˜ thread pool์ด๋‹ค.
  • ์ƒˆ๋กœ์šด thread pool์€ CPU ์ฝ”์–ด ์ˆ˜๋งŒํผ๋งŒ thread๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.(spawn)
  • ๋‚ฑ์•Œ์ฒ˜๋Ÿผ ํฉ์–ด์ง„ concurrency๋ฅผ ์ œ์–ดํ•œ๋‹ค.
    • Worker thread๋Š” block๋˜์ง€ ์•Š๋Š”๋‹ค.
    • thread explosion์„ ํ”ผํ•˜๊ณ , ๊ณผ๋„ํ•œ context switching์„ ๋ฐฉ์ง€ํ•œ๋‹ค.
    • workitem์ด block๋˜๋ฉด ๋” ๋งŽ์€ thread๋ฅผ spawnํ–ˆ๋˜ GCD์˜ concurrenct queue์™€ ๋‹ค๋ฅด๊ฒŒ Swift์˜ thread๋Š” ์•ž์œผ๋กœ ๋‚˜์•„๊ฐˆ ์ˆ˜ ์žˆ๋‹ค.

Reference