Upgrade to Pro — share decks privately, control downloads, hide ads and more …

実装で解き明かす並行処理の歴史

 実装で解き明かす並行処理の歴史

2025/10/03 にextension DC 2025 Day3 @ LINEヤフーで発表した登壇資料です。
https://lycorptech-jp.connpass.com/event/362636/

スライド本文中の URL はこちらです。
https://github.com/laprasdrum/ConcurrencyLab

株式会社ZOZO
ZOZOTOWN開発本部
ZOZOTOWN開発2部 iOSブロック
森口 友也 / lap (@laprasdrum)

Avatar for ZOZO Developers

ZOZO Developers PRO

October 03, 2025
Tweet

More Decks by ZOZO Developers

Other Decks in Technology

Transcript

  1. © ZOZO, Inc. 株式会社ZOZO ZOZOTOWN開発本部 ZOZOTOWN開発2部 iOSブロック テックリード lap/らぷ id:

    laprasdrum 2023年 中途入社 ZOZOTOWN iOSチーム内の要件設計・コーディングのフォ ローアップ、社内外への情報発信をメインに活動中 現在テックリードとして iOS に関する全社横断活動を兼務 2
  2. © ZOZO, Inc. https://zozo.jp/ 3 • ファッションEC • 1,600以上のショップ、9,000以上のブランドの取り扱い •

    常時107万点以上の商品アイテム数と毎日平均2,700点以上の新着 商品を掲載(2025年6月末時点) • ブランド古着のファッションゾーン「ZOZOUSED」や コスメ専門モール「ZOZOCOSME」、シューズ専門ゾーン 「ZOZOSHOES」、ラグジュアリー&デザイナーズゾーン 「ZOZOVILLA」を展開 • 即日配送サービス • ギフトラッピングサービス • ツケ払い など
  3. © ZOZO, Inc. 5 Swift Concurrency Swift の並行処理に関する Built-in support

    のゴールの1つが低レベルのデータ競合まで拡張された メモリ安全性の確保 明示的に並行処理を記述しない限り基本的にシングルスレッド 逐一「並行かもしれない」と仮定するより、「ここは逐次実行」と明示できればデータ競合がないこ とを証明できる言語デザインを目指している 詳しくは visions/approachable-concurrency.md参照 https://github.com/swiftlang/swift-evolution/blob/main/visions/approachable-concurrency.md
  4. © ZOZO, Inc. 6 Task Concurrency Manifesto:https://gist.github.com/lattner/31ed37682ef1576b16bca1432ea9f782 Task Concurrency Manifesto

    https://gist.github.com/lattner/31ed37682ef1576b16bca1432ea9f782 ご存知 Swift の祖 Chris Lattner が2017年に執筆した gist GCD と共存しつつ非同期処理がコールバックネストになりがちな問題を言語デザインで解決 この時点で Actor に言及しており、ロックやアトミック操作(synchronizedなど)の改善より 共有可変状態に依存しない設計を目指していた Swift Concurrency Roadmap(2020)で await に関する議論が白熱(Lattner 氏も当時参加) https://forums.swift.org/t/swift-concurrency-roadmap/41611/4
  5. © ZOZO, Inc. 7 Swift Concurrency による複数 I/O 処理 try

    await withThrowingTaskGroup(of: [Post].self) { group in group.addTask { try await fetch(myURL) } group.addTask { try await fetch(favoriteURL) } group.addTask { try await fetch(repostURL) } for try await posts in group { collect(posts) } } TaskGroup による構造化、エラーは throw で伝搬、明示的なロックが不要
  6. © ZOZO, Inc. 8 GCD: Grand Central Dispatch Mac OS

    X 10.6 Snow Leopard(2009)にて初導入(& iOS 4(2010)から) 当時マルチコア CPU が浸透し始め、CPU リソースの最適化をソフトウェアで吸収しようとした • 最適なスレッド数の算出 • 使用コアの偏り解消 キューにタスクを投げるだけでよい開発体験 WWDC 2009: Programming with Blocks and Grand Central Dispatch https://github.com/swiftlang/swift-corelibs-libdispatch
  7. © ZOZO, Inc. 9 let queue = DispatchQueue(label: "feed.gcd", attributes:

    .concurrent) let group = DispatchGroup() let lock = NSLock() var firstError: Error? // GCDのasyncクロージャは throw を伝搬できないため、エラーは変数に保存する。 func fetch(_ url: URL) { group.enter() queue.async { defer { group.leave() } do { let posts = try requestSync(url) // GCDでは並列アクセスの排他制御が必要 → lockでガード lock.lock(); collect(posts); lock.unlock() } catch { // GCDでは自分でエラーを保持して後で確認するしかない lock.lock(); if firstError == nil { firstError = error }; lock.unlock() } } }
  8. © ZOZO, Inc. 10 // GCDのasyncクロージャは throw を伝播できないため、エラーは変数に保存する。 func fetch(_

    url: URL) { group.enter() queue.async { defer { group.leave() } do { let posts = try requestSync(url) // GCDでは並列アクセスの排他制御が必要 → lockでガード lock.lock(); collect(posts); lock.unlock() } catch { // GCDでは自分でエラーを保持して後で確認するしかない lock.lock(); if firstError == nil { firstError = error }; lock.unlock() } } } fetch(myURL) fetch(favoriteURL) fetch(repostURL) // GCDは完了を待機し、 // その後まとめて結果やエラーを確認する。 // firstError をチェックして手動で throw する。 group.wait() if let error = firstError { throw error }
  9. © ZOZO, Inc. 11 Operation(NSOperation) Mac OS X 10.5 Leopard(2007)で初導入(&

    iOS 2.0(2008)から) タスクをオブジェクトでカプセル化し、並行処理に関する高レベルの API を提供
  10. © ZOZO, Inc. 12 let queue = OperationQueue() // 並列度を設定

    queue.maxConcurrentOperationCount = 3 let lock = NSLock() var firstError: Error? func fetch(_ url: URL) -> BlockOperation { BlockOperation { do { let posts = try requestSync(url) // GCD同様、明示的に排他ロックを掛ける。 lock.lock(); collect(posts); lock.unlock() } catch { lock.lock(); if firstError == nil { firstError = error }; lock.unlock() } } } let operations = [fetch(myURL), fetch(favoriteURL), fetch(repostURL)] // 非同期に実行する場合は waitUntilFinished: false queue.addOperations(operations, waitUntilFinished: true) // GCD同様、待機後に手動でエラーを確認して扱う。 if let error = firstError { throw error }
  11. © ZOZO, Inc. 13 Thread(NSThread) GCD / Operation 普及前にアプリ側が直接スレッドを作成する代表的手段 https://developer.apple.com/documentation/foundation/thread

    “Use this class when you want to have an Objective-C method run in its own thread of execution.” POSIX threads(Pthreads) に対する Cocoa インタフェース
  12. © ZOZO, Inc. 14 // Swift Concurrency の withThrowingTaskGroup や

    // GCD の DispatchGroup に相当するクラス。 // 複数スレッドを起動したあと、 // すべての処理が終わるまで待機する責務を持つ。 final class ThreadJoiner { // スレッド間同期に使う条件変数 private let cond = NSCondition() // 残りスレッド数カウンタ private var remaining: Int // 起動するスレッド数を指定して初期化 init(count: Int) { self.remaining = count } // スレッド側から完了を通知する func done() { cond.lock() remaining -= 1 cond.signal() // 待機中のスレッドを1つ起動する cond.unlock() } // すべてのスレッドが終わるまでブロッキングして待機 func waitAll() { cond.lock() // 他のスレッドが signal するまでスリープ while remaining > 0 { cond.wait() } cond.unlock() } }
  13. © ZOZO, Inc. 15 // 並行度を設定 let joiner = ThreadJoiner(count:

    3) let lock = NSLock() var capturedError: Error? func fetch(_ url: URL) { let t = Thread { defer { joiner.done() } do { let posts = try requestSync(url) // 明示ロック lock.lock(); collect(posts); lock.unlock() } catch { lock.lock(); if firstError == nil { firstError = error }; lock.unlock() } } t.start() // スレッド起動 } fetch(myURL) fetch(favoriteURL) fetch(repostURL) // 同期ブロッキング joiner.waitAll() if let error = firstError { throw error }
  14. © ZOZO, Inc. 16 おまけ:Pthreads IEEE で規格化され(1995)、このとき定義された C API 群

    https://standards.ieee.org/ieee/1003.1c/1393/ opensource.apple.com でコード公開されている https://github.com/apple-oss-distributions/libpthread
  15. © ZOZO, Inc. 17 import Darwin // Swiftから複数値を安全にCポインタで // 渡すために必要となる。

    final class Box { let url: URL let lock: NSLock init(url: URL, lock: NSLock) { self.url = url; self.lock = lock } } private var firstError: Error? // @_cdecl を付けてSwift関数を // pthread_create から直接呼び出し可能にする @_cdecl("pthread_worker_entry") func pthread_worker_entry( _ arg: UnsafeMutableRawPointer? ) -> UnsafeMutableRawPointer? { // passRetained で渡された Box を // takeRetainedValue で参照カウンタを1下げる(release) let box = Unmanaged<Box>.fromOpaque(arg!).takeRetainedValue() do { let posts = try requestSync(box.url) // 明示ロック box.lock.lock(); collect(posts); box.lock.unlock() } catch { box.lock.lock(); if firstError == nil { firstError = error }; box.lock.unlock() } return nil }
  16. © ZOZO, Inc. 18 var threads = [pthread_t?](repeating: nil, count:

    3) // pthread_attr_t: スタックサイズ、スケジューリングポリシー、 // デタッチ状態などを指定 var attr = pthread_attr_t() let lock = NSLock() let urls = [myURL, favoriteURL, repostURL] // スレッド属性の初期化 pthread_attr_init(&attr) for (i, url) in urls.enumerated() { // Box を retain してスレッドへ所有権を渡す // スレッド側で takeRetainedValue で解放 let box = Box(url: url, lock: lock) let unmanaged = Unmanaged.passRetained(box) let result = pthread_create( &threads[i], // 作成したスレッドIDを格納 &attr, // スレッド属性 pthread_worker_entry, // C互換の関数ポインタ unmanaged.toOpaque() // スレッドに渡す引数 // BoxをUnmanaged経由でポインタ化 )
  17. © ZOZO, Inc. 19 if result != 0 { //

    スレッド作成失敗時は所有権を呼び戻して解放しておく unmanaged.release() lock.lock() if firstError == nil { // エラーを作成する必要がある firstError = NSError( domain: "PThreadError", code: Int(result), userInfo: [ NSLocalizedDescriptionKey: "pthread_create failed for \(url) (\(result))" ] ) } lock.unlock() } } // すべてのスレッド完了を待機 for t in threads { if let th = t { pthread_join(th, nil) } } // スレッド属性の破棄 pthread_attr_destroy(&attr) if let error = firstError { throw error }
  18. © ZOZO, Inc. 22 Swift Concurrency による複数 I/O 処理 try

    await withThrowingTaskGroup(of: [Post].self) { group in group.addTask { try await fetch(myURL) } group.addTask { try await fetch(favoriteURL) } group.addTask { try await fetch(repostURL) } for try await posts in group { collect(posts) } } TaskGroup による構造化、エラーは throw で伝搬、明示的なロックが不要