Slide 1

Slide 1 text

実装で解き明かす並行処理の歴史 Swift ConcurrencyからNSThreadまで遡ろう 2025/10/03 extension DC 2025 Day3 株式会社ZOZO
 ZOZOTOWN開発本部 ZOZOTOWN開発2部 iOSブロック
 テックリード
 森口 友也 Copyright © ZOZO, Inc. 1

Slide 2

Slide 2 text

© ZOZO, Inc. 株式会社ZOZO ZOZOTOWN開発本部 ZOZOTOWN開発2部 iOSブロック テックリード lap/らぷ id: laprasdrum 2023年 中途入社 ZOZOTOWN iOSチーム内の要件設計・コーディングのフォ ローアップ、社内外への情報発信をメインに活動中 現在テックリードとして iOS に関する全社横断活動を兼務 2

Slide 3

Slide 3 text

© ZOZO, Inc. https://zozo.jp/ 3 ● ファッションEC ● 1,600以上のショップ、9,000以上のブランドの取り扱い ● 常時107万点以上の商品アイテム数と毎日平均2,700点以上の新着 商品を掲載(2025年6月末時点) ● ブランド古着のファッションゾーン「ZOZOUSED」や コスメ専門モール「ZOZOCOSME」、シューズ専門ゾーン 「ZOZOSHOES」、ラグジュアリー&デザイナーズゾーン 「ZOZOVILLA」を展開 ● 即日配送サービス ● ギフトラッピングサービス ● ツケ払い など

Slide 4

Slide 4 text

© ZOZO, Inc. 4 まずは Swift Concurrency から

Slide 5

Slide 5 text

© 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

Slide 6

Slide 6 text

© 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

Slide 7

Slide 7 text

© 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 で伝搬、明示的なロックが不要

Slide 8

Slide 8 text

© 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

Slide 9

Slide 9 text

© 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() } } }

Slide 10

Slide 10 text

© 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 }

Slide 11

Slide 11 text

© ZOZO, Inc. 11 Operation(NSOperation) Mac OS X 10.5 Leopard(2007)で初導入(& iOS 2.0(2008)から) タスクをオブジェクトでカプセル化し、並行処理に関する高レベルの API を提供

Slide 12

Slide 12 text

© 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 }

Slide 13

Slide 13 text

© 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 インタフェース

Slide 14

Slide 14 text

© 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() } }

Slide 15

Slide 15 text

© 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 }

Slide 16

Slide 16 text

© 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

Slide 17

Slide 17 text

© 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.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 }

Slide 18

Slide 18 text

© 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経由でポインタ化 )

Slide 19

Slide 19 text

© 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 }

Slide 20

Slide 20 text

© ZOZO, Inc. 20

Slide 21

Slide 21 text

© ZOZO, Inc. 21 再掲

Slide 22

Slide 22 text

© 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 で伝搬、明示的なロックが不要

Slide 23

Slide 23 text

© ZOZO, Inc. 23

Slide 24

Slide 24 text

© ZOZO, Inc. 24 ちなみに

Slide 25

Slide 25 text

© ZOZO, Inc. 25

Slide 26

Slide 26 text

© ZOZO, Inc. 26

Slide 27

Slide 27 text

© ZOZO, Inc. 27 https://github.com/laprasdrum/ConcurrencyLab

Slide 28

Slide 28 text

© ZOZO, Inc. 28

Slide 29

Slide 29 text

© ZOZO, Inc. 29 まとめ

Slide 30

Slide 30 text

© ZOZO, Inc. 30 歴史あっての今の Swift Concurrency みんなで理解を深め合って上手く付き合っていこう

Slide 31

Slide 31 text

No content