Actor isolation

Actor์˜ isolation์€ actor type์˜ ๊ทผ๋ณธ์ ์ธ ๋™์ž‘์ด๋‹ค. Swift language model์—์„œ ์–ด๋–ป๊ฒŒ Actor๊ฐ€ actor ๋ฐ”๊นฅ์ชฝ์—์„œ ๋“ค์–ด์˜ค๋Š” ๋น„๋™๊ธฐ interaction์— ๋Œ€ํ•ด ๊ณ ๋ฆฝ์„ ๋ณด์žฅํ•˜๋Š”์ง€ ์—๋Œ€ํ•ด ์•Œ์•„๋ณด์ž. ์—ฌ๊ธฐ์„œ ๊ณ ๋ฆฝ์€ ์•ž์—์„œ ๋งํ•œ ์—ฌ๋Ÿฌ ๋น„๋™๊ธฐ Task์—์„œ actor์˜ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋”๋ผ๋„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค.

Protocol

๋‹ค๋ฅธ ํƒ€์ž…๋“ค๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๊ณ  actor๋Š” protocol์„ ์ฑ„ํƒํ•  ์ˆ˜ ์žˆ๋‹ค.

actor LibraryAccount {
    let idNumber: int
    var booksOnLoan: [Book] = []
}
 
extension LibraryAccount: Equatable {
    static func ==(lhs: LibraryAccount, rhs: LibraryAccount) -> Bool {
        lhs.idNumber == rhs.idNumber
    }
}

Equatable protocol์„ ์ฑ„ํƒํ–ˆ๊ณ , static function์„ ๊ตฌํ˜„ํ–ˆ๋‹ค. static function์ด๊ธฐ ๋•Œ๋ฌธ์— ๋‚ด๋ถ€์—์„œ๋Š” actor์—์„œ ์ •์˜๋œ instance๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. ๊ทธ ๋Œ€์‹ ์— ์ธ์ˆ˜๋กœ actor type์„ ๋ฐ›๋Š”๋‹ค. ๊ทธ๋ฆฌ๊ณ  idNumber์— ์ ‘๊ทผํ•˜์ง€๋งŒ, ๋ณ„ ๋ฌธ์ œ๋Š” ์—†๋‹ค. immutable state์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

actor LibraryAccount {
    let idNumber: int
    var booksOnLoan: [Book] = []
}
 
extension LibraryAccount: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(idNumber) // โŽ actor-isolated method 'hash(into:)' cannot satisfy synchronous requirement
    }
}

๊ทธ๋ ‡๋‹ค๋ฉด ์ด๋ฒˆ์—๋Š” Hashable protocol์„ ์ฑ„ํƒํ•ด๋ณด์ž. ๊ทธ๋Ÿฐ๋ฐ ์ด๋ฒˆ์—๋Š” compiler๊ฐ€ ์—๋Ÿฌ๋ฅผ ๋ฟœ๋Š”๋‹ค. ์ด๊ฒŒ ๋ญ˜๊นŒ?

์ผ๋‹จ ์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ Hashable์„ ์ฑ„ํƒํ•˜๋ฉด, ์ด๋Š” ๋ถ„๋ช… ๋ฐ”๊นฅ์—์„œ ํ˜ธ์ถœ์ด ๊ฐ€๋Šฅํ•ด์ง„๋‹ค. ๊ทผ๋ฐ, actor์•ˆ์— ์ •์˜๋œ ํ•จ์ˆ˜๋Š” ์•”๋ฌต์ ์œผ๋กœ multi thread์—์„œ ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ด๋ฅผ asyncํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ์–ด์•ผ actor๋ฅผ isolation ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด actor๋Š” ๋‚ด๋ถ€์— ์ •์˜๋œ ํ•จ์ˆ˜์— ๋Œ€ํ•ด synchronousํ•˜๊ฒŒ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ์—†๋‹ค. ํ•˜์ง€๋งŒ Protocol์„ ์ฑ„ํƒํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, asyncํ•˜๊ฒŒ ๋งŒ๋“ค ๋ฐฉ๋„๊ฐ€ ์—†๋‹ค. ์ฆ‰, isolation์ด ๋ถˆ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.

extension LibraryAccount: Hashable {
    nonisolated func hash(into hasher: inout Hasher) {
        hasher.combine(idNumber)
    }
}

์ด๋Ÿฐ ๊ฒฝ์šฐ non-isolationํ•˜๊ฒŒ ๋งŒ๋“ค๋ฉด ๋œ๋‹ค. ์‚ฌ์‹ค ์ด ํ•จ์ˆ˜๋Š” ๊ทธ๋Ÿฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. isolation์€ ์‹ค์ œ multi thread์—์„œ ํ˜ธ์ถœํ•˜์—ฌ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ์— ์ฒ˜๋ฆฌํ•ด์ฃผ๋ฉด ์ข‹์€ ๊ฒƒ์ด๋‹ค. ์ด์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ผ์ด ์—†๋Š” ๊ฒฝ์šฐ๋Š” ๊ณ ๋ฆฝ์‹œํ‚ฌ ํ•„์š”๊ฐ€ ์—†๋‹ค.

โ€๊ทธ๋Ÿฌ๋ฉด, non-isolated function์—์„œ mutable state๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด ๋™์‹œ์„ฑ ๋ฌธ์ œ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ ์•„๋‹˜?!, ๋ฐ–์—์„œ ๋ง‰ ์‚ฌ์šฉํ•ด๋„ ๋œ๋‹ค๋Š” ๋ง์ด์ž–์•„!โ€ ๋งž๋‹ค. ๊ทธ๋ž˜์„œ ์ด๋ ‡๊ฒŒ ํ‘œ์‹œ๋˜๋ฉด, actor์•ˆ์— ์žˆ๋Š” mutable state๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ณ  ์žˆ์œผ๋ฉด ์•ˆ๋œ๋‹ค. ์œ„์˜ ๊ฒฝ์šฐ๋Š” ๊ดœ์ฐฎ์€๋ฐ, immutable property์— ์ ‘๊ทผํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

extension LibraryAccount: Hashable {
    nonisolated func hash(into hasher: inout Hasher) {
        hasher.combine(booksOnLoan) // โŽ actor-isolated property 'booksOnLoan' cannot be referenced from outside the actor
    }
}

์ด๋ ‡๊ฒŒ ๊ณต์œ ๋˜๋Š” ๋ณ€์ˆ˜์— ์ ‘๊ทผํ•˜๋ฉด ์—๋Ÿฌ๋ฅผ ๋ฟœ๋Š”๋‹ค.

Closures

extension LibraryAccount {
    func readSome(_ book: Book) -> Int { ... }
 
    func read() -> Int {
        booksOnLoad.reduce(0) { book in
            readSome(book)
        }
    }
}

๋จผ์ €, Closure๋Š” ์ผ์ข…์˜ ํ•จ์ˆ˜๋ผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ์ •ํ™•ํ•˜๊ฒŒ ๋งํ•˜๋ฉด ํ•จ์ˆ˜๊ฐ€ Closure์˜ ์ผ์ข…์ด๋‹ค. ๋‹ค๋งŒ, ํŠน์ • ํ•จ์ˆ˜ ๋‚ด์—์„œ ์ •์˜๋  ์ˆ˜๋„ ์žˆ๊ณ , ๋‹ค๋ฅธ ํ•จ์ˆ˜๋กœ ๋„˜๊ฒจ ์ถ”ํ›„์— ํ˜ธ์ถœ๋  ์ˆ˜๋„ ์žˆ๋‹ค๋Š” ์ฐจ์ด์ ์ด ์žˆ๋‹ค.

์ผ๋‹จ ํ•จ์ˆ˜์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ closure ์—ญ์‹œ, actor-isolated๊ฑฐ๋‚˜ non-isolated ๋  ์ˆ˜ ์žˆ๋‹ค. ์œ„์˜ ์˜ˆ์‹œ์—์„œ readSome ํ•จ์ˆ˜ ์•ž์— await๊ฐ€ ์—†๋Š” ๊ฒƒ์€ ์–ด์ฐŒ๋ณด๋ฉด ๋‹น์—ฐํ•˜๋‹ค. ์™œ๋ƒํ•˜๋ฉด, reduce๋ผ๋Š” ํ•จ์ˆ˜๊ฐ€ ๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋  ๊ฒƒ์ด ๋ถ„๋ช…ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น ํด๋กœ์ €๋Š” ๋ฐ”๊นฅ์œผ๋กœ ํƒˆ์ถœ(escape) ํ• ์ˆ˜ ์—†๋‹ค. ์ฆ‰, ์ด ์ž์ฒด๋กœ actor-isolated ๋˜์–ด ์žˆ๋‹ค.

extension LibraryAccount {
    func readSome(_ book: Book) -> Int { ... }
 
    func read() -> Int { ... }
 
    func readLater() {
        Task.detached {
            await self.read()
        }
    }
}

์ด๊ฑด ์–ด๋–จ๊นŒ? ์ด๋ฒˆ์—๋Š” Task.detached๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค. detached Task๋Š” actor๊ฐ€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋™์•ˆ closure๋ฅผ ํ†ตํ•ด concurrentํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์ด closure๋Š” actor์— ์žˆ์„ ์ˆ˜ ์—†์œผ๋ฉฐ, data race๋ฅผ ์ผ์œผํ‚ฌ ๊ฒƒ์ด๋‹ค. ์ฆ‰, ์ด closure๋Š” not-isolated ๋˜์–ด ์žˆ๋‹ค. read method๋ฅผ ์‹คํ–‰ํ•˜๊ธธ ์›ํ•  ๋•Œ, await๋กœ ํ‘œ์‹œ๋œ ๊ฒƒ์œผ๋กœ ์•Œ ์ˆ˜ ์žˆ๋“ฏ ๋ฌด์กฐ๊ฑด์ ์œผ๋กœ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ˆ˜ํ–‰๋œ๋‹ค.

Actor Isolation and Data

์ง€๊ธˆ๊นŒ์ง€๋Š” code๊ฐ€ actor์˜ ์•ˆ์— ์žˆ๋Š๋ƒ, ๋ฐ–์— ์žˆ๋Š๋ƒ๋ฅผ ๊ธฐ์ค€์œผ๋กœ actor isolation์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์•˜๋‹ค. Data์™€ ํ•จ๊ป˜ ์•Œ์•„๋ณด์ž.

Struct

actor LibraryAccount {
    let idNumber: Int
    var booksOnLoan: [Book] = []
    func selectRandomBook() -> Book? { ... } โœ…
}
 
struct Book {
    var title: String
    var authors: [Author]
}
 
// Actor์˜ ๋ฐ”๊นฅ์ชฝ์— ์œ„์น˜
func visit(_ account: LibraryAccount) async {
    guard var book = await account.selectRandomBook() else {
        return
    }
    book.title = "\(book.title)!!!"
}

์ด์ „ ์˜ˆ์—์„œ ์šฐ๋ฆฌ๋Š” Book์ด ์–ด๋–ค ํƒ€์ž…์ธ์ง€ ์‚ฌ์‹ค ๋งํ•˜์ง€ ์•Š์•˜๋‹ค. ์ด ์ƒํ™ฉ์—์„œ ์ผ๋‹จ Struct๋ผ๊ณ  ์ƒ๊ฐํ•ด๋ณด์ž. ์ผ๋‹จ ๊ต‰์žฅํžˆ ์ข‹์€ ์„ ํƒ์ด๋‹ค. ์™œ๋ƒํ•˜๋ฉด libraryAccount Actor๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” instance์˜ ๋ชจ๋“  ์ƒํƒœ๊ฐ€ self-contained์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.(์ž๋ฆฝ์ ? ์™ธ๋ถ€์— ์˜์กด์ด ์—†๋‹ค๋Š” ๊ฑธ ๋งํ•˜๊ณ  ์‹ถ์€ ๋“ฏ) โœ… ํ‘œ์‹œํ•œ ํ•จ์ˆ˜๋Š” random์œผ๋กœ ์ฑ…์„ ์„ ํƒํ•˜๋Š” ๋ฉ”์†Œ๋“œ์ธ๋ฐ, ๋งŒ์•ฝ ํ•ด๋‹น method๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ํ•ญ์ƒ Book์˜ copy๋ฅผ ๋ฐ˜ํ™˜ ๋ฐ›๋Š”๋‹ค. ๋ฐ˜ํ™˜๋ฐ›์€ instance์— ๋Œ€ํ•ด ๋ณ€๊ฒฝ์„ ๊ฐ€ํ•˜๋”๋ผ๋„ actor์— ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š๋Š”๋‹ค.

Class

actor LibraryAccount {
    let idNumber: Int
    var booksOnLoan: [Book] = []
    func selectRandomBook() -> Book? { ... } โœ…
}
 
class Book {
    var title: String
    var authors: [Author]
}
 
// Actor์˜ ๋ฐ”๊นฅ์ชฝ์— ์œ„์น˜
func visit(_ account: LibraryAccount) async {
    guard var book = await account.selectRandomBook() else { // ๐Ÿ˜… ๊ณ„์†ํ•ด์„œ reference๋ฅผ ๋˜์ ธ์ฃผ๊ฒŒ ๋œ๋‹ค.
        return
    }
    book.title = "\(book.title)!!!"
}

๊ทธ๋Ÿฐ๋ฐ ๋งŒ์•ฝ Class๋ผ๋ฉด ์–ด๋–จ๊นŒ. booksOnLoan property๋Š” ์ด์ œ Book instance์˜ ์ฃผ์†Œ๋ฅผ reference๋กœ ๊ฐ–๊ณ  ์žˆ๋‹ค. ์‚ฌ์‹ค ์ด ์ž์ฒด๋Š” ๋ฌธ์ œ๊ฐ€ ์—†๋‹ค. ๊ทธ๋Ÿฐ๋ฐ, selectRandomBook ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ฒŒ ๋˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ? reference๋ฅผ actor์—์„œ ๋˜์ ธ์ฃผ๊ธฐ ๋•Œ๋ฌธ์—, ์™ธ๋ถ€์—์„œ actor์˜ mutable state๋ฅผ ๊ฐ–๊ฒŒ ๋œ๋‹ค. ์ด๋Š” data race๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ์ด๋‹ค.

Senable Types

์œ„์—์„œ ๋ณด์•˜๋“ฏ์ด struct์˜ ๊ฒฝ์šฐ์—๋Š” concurrentํ•œ ๋™์ž‘์ด ์ž˜ ๋งž์ง€๋งŒ, class์˜ ๊ฒฝ์šฐ์—๋Š” ์—ฌ์ „ํžˆ ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค. Concurrentํ•˜๊ฒŒ ๋™์ž‘ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Sendable ํ•ด์•ผ ํ•œ๋‹ค.

  • concurrentlyํ•˜๊ฒŒ ๊ณต์œ ํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•ด ์•ˆ์ „ํ•œ Type์„ ๋งํ•œ๋‹ค.
    • ๋‹ค๋ฅธ actor ๊ฐ„์— ๊ฐ’์„ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ๊ฐ’์„ copy ํ•œ๋‹ค๋ฉด, ํ˜น์€ ์‚ฌ์šฉํ•˜๋Š” ์ธก์—์„œ ์„œ๋กœ ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๊ณ  ์‚ฌ๋ณธ์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ํ•ด๋‹น type์€ Sendable์ด๋ผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
  • Value types
    • ์‚ฌ๋ณธ์„ ๋ณต์‚ฌํ•˜์—ฌ ์ƒํ˜ธ๊ฐ„์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์Œ
  • Actor types
    • mutable states์— synchronizeํ•œ ๋ฐฉ์‹์œผ๋กœ ์ ‘๊ทผํ•˜๊ธฐ ๋•Œ๋ฌธ
  • Immutable classes
    • Sesndable ๋  ์ˆ˜ ์žˆ์ง€๋งŒ ์ถ”๊ฐ€์ ์ธ ์ž‘์—…์ด ํ•„์š”ํ•จ
    • ๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ immutble data๋งŒ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด ๊ฐ€๋Šฅ
  • Internally-synchronized class
    • ๋‚ด๋ถ€์ ์œผ๋กœ syncํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋„๋ก ๊ตฌํ˜„ํ•œ ๊ฒฝ์šฐ
    • lock
  • @Sendable function types

Checking Sendable

์ด๋Ÿฌํ•œ ํŠน์ง•์„ Swift Compiler๋Š” Checkingํ•œ๋‹ค. ๊ฒฐ๊ตญ, ์œ„์—์„œ ๋ณด์•˜๋˜ Class์˜ ์˜ˆ์‹œ๋Š” Compile Error๊ฐ€ ๋‚œ๋‹ค.

actor LibraryAccount {
    let idNumber: Int
    var booksOnLoan: [Book] = []
    func selectRandomBook() -> Book? { ... } โœ…
}
 
class Book {
    var title: String
    var authors: [Author]
}
 
// Actor์˜ ๋ฐ”๊นฅ์ชฝ์— ์œ„์น˜
func visit(_ account: LibraryAccount) async {
    guard var book = await account.selectRandomBook() else { // โŽ call to actor-isolated method 'selectRandomBook' returns non-Sendable type 'Book?'
        return
    }
    book.title = "\(book.title)!!!"
}

Adopting Sendable

๊ทธ๋Ÿผ ์–ด๋–ป๊ฒŒ ํ•ด์„œ Sendable Type์œผ๋กœ ๋งŒ๋“ค์–ด์ค„ ์ˆ˜ ์žˆ์„๊นŒ? ์ผ๋‹จ Sendable์€ Protocol์ด๋‹ค.

struct Book: Sendable {
    var title: String
    var authors: [Author] // โŽ error: Sendable type ;Book; has non=Sendable stroed property 'authors' of type '[Author]'
}
 
class Author {
    ...
}

์ด๋ฅผ ์ค€์ˆ˜ํ•˜๊ฒŒ ๋˜๋ฉด, swift compiler๋Š” ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ type๋“ค์ด Sendableํ•œ์ง€ ์ฒดํฌํ•œ๋‹ค. title์€ ๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ, author๊ฐ€ ์–ด๋–ค ํƒ€์ž…์ธ์ง€์— ์˜ํ•ด Book์€ Sendable์ด ๋ ์ˆ˜๋„ ์•„๋‹ ์ˆ˜ ๋„ ์žˆ๋‹ค. ์•„๋ž˜์— ๋ณด๋‹ˆ class์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ์ž‘์—…๋“ค์ด ์ž˜ ๋˜์–ด ์žˆ์ง€ ์•Š์•˜๋‹ค.(sync, immuable) ๊ทธ ๊ฒฐ๊ณผ, compile error๊ฐ€ ๋‚˜๊ฒŒ ๋œ๋‹ค.

struct Pari<T, U> {
    var first: T
    var second: U
}
 
extension Pair: Sendable where T: Sendable, U: Sendable {
 
}

generic์˜ ๊ฒฝ์šฐ์—๋Š” ํ•ด๋‹น Type์˜ Sendable ์—ฌ๋ถ€๊ฐ€, generic argument์— ์˜ํ•ด ์ •ํ•ด์ง„๋‹ค. ์ด ๋–„, ๋‚ด๋ถ€์— ๋“ค์–ด์˜ค๋Š” Type ์ž์ฒด์— constranint๋ฅผ ๊ฑธ์–ด, ๋“ค์–ด์˜ค๋Š” Type์ด Sendable ํ•˜์ง€ ์•Š์„ ๋•Œ Compile error๋ฅผ ๋‚˜๊ฒŒ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

@Sendable functions

function ์ž์ฒด๋„ Sendable ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋Š” actors ๋“ค์‚ฌ์ด๋กœ ๋˜์ ธ๋„ ์•ˆ์ „ํ•˜๋‹ค๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค. ์ด๋Š” ์ค‘์š”ํ•œ๋ฐ, closure์—์„œ Data race๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๊ฒƒ์„ ์‚ฌ์ „ ์ฐจ๋‹จํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, Sendable closure์˜ ๊ฒฝ์šฐ, mutable local ๋ณ€์ˆ˜๋ฅผ captureํ•  ์ˆ˜ ์—†๋‹ค. capture ํ›„์— ๋‚ด๋ถ€์—์„œ ๋ณ€๊ฒฝํ•œ๋‹ค๋ฉด, data race๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ด์™€ ๊ฐ™์ด compiler ๋‹จ์—์„œ ๋ฌธ์ œ๋ฅผ ๋ฐœ๊ฒฌํ•˜๊ฒŒ ํ•ด์ค€๋‹ค. ํŠน์ง•์€ ๋‹ค์Œ์™€ ๊ฐ™๋‹ค.

  • mutable capture๊ฐ€ ๋ถˆ๊ฐ€ํ•˜๋‹ค.
  • Capture ํ•  ์ˆ˜ ์žˆ๋Š” ๋…€์„๋“ค์€ Sendable ํ•ด์•ผ๋งŒ ํ•œ๋‹ค.
  • Cannot be both sunchronous and actor-isolated

@Sendable closure restrictions

static func detached(operation: @Sendable () async -> Success) -> Task<Success, Never>
 
struct Counter {
    var value = 0
 
    mutating func increment() -> Int {
        value = value + 1
        return value
    }
}
 
var counter = Counter()
Task.detached {
    print(counter.increment()) // Mutation of cpatured var 'counter' in concurrently-executing code
}
 
Task.detached {
    print(counter.increment()) // Mutation of cpatured var 'counter' in concurrently-executing code
 

์šฐ๋ฆฌ๊ฐ€ ์•ž์—์„œ ์‚ฌ์šฉํ•ด๋ดค๋˜ detached task๋ฅผ ๋งŒ๋“ค์—ˆ๋˜ ๋…€์„์— Sendable closure๊ฐ€ ๋“ค์–ด๊ฐ„๋‹ค. ์—ฌ๊ธฐ์„œ ์šฐ๋ฆฌ๋Š” ๋‘๊ฐœ์˜ task์—์„œ ๊ฐ™์€ method๋ฅผ ๋™์‹œ์— ํ˜ธ์ถœ ํ–ˆ์—ˆ๋‹ค. mutable local ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด data race๋ฅผ ์ผ์œผํ‚ฌ ์ƒํ™ฉ์ด๋‹ค.

ํ•˜์ง€๋งŒ ์ด ๊ฒฝ์šฐ ์—๋Ÿฌ๊ฐ€ ๋‚˜๋Š”๋ฐ, @Sendable protocol์„ ์ค€์ˆ˜ํ•˜๋Š” closure์˜ ๊ฒฝ์šฐ, mutableํ•œ ๋ณ€์ˆ˜๋ฅผ captureํ•  ์ˆ˜ ์—†๋‹ค.

static func detached(operation: @Sendable () async -> Success) -> Task<Success, Never>
 
extension LibraryAccount {
    func readSome(_ book: Book) -> Int { ... }
 
    func read() -> Int { ... }
 
    func readLater() {
        Task.detached {
            self.read() // โŽ call to actor-isolated method 'read' must be 'async'
        }
    }
}

์ด ์˜ˆ์‹œ๋ฅผ ๋ณด์ž. readLater()๋Š” actor๋‚ด์— ์ •์˜๋œ ํ•จ์ˆ˜์ด์ง€๋งŒ, ๋‚ด๋ถ€์ ์œผ๋กœ๋Š” Task.detached๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, actor ์™ธ๋ถ€์—์„œ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ฒฐ๊ตญ, Task.detached ์—์„œ ์‚ฌ์šฉํ•˜๋Š” closure๋Š” actor ์™ธ๋ถ€์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— async ํ•˜๊ฒŒ ๋™์ž‘ํ•ด์•ผ actor-isolated๋ฅผ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋Ÿฐ ๋ถ€๋ถ„์„ compiler๊ฐ€ ์žก์•„์ฃผ๊ณ  ์žˆ๋‹ค.

Main actor

์ด์ œ actor์™€ ๊ด€๋ จ๋œ ํ•˜๋‚˜์˜ ์š”์†Œ๊ฐ€ ๋‚จ์•˜๋‹ค. ์ด๋…€์„์€ ์ข€ ํŠน๋ณ„ํ•œ ๋…€์„์ด๋‹ค.

Interacting with the main thread

main thread๋Š” app์—์„œ ์ค‘์š”ํ•œ ๋…€์„์ด๋‹ค. UI rendering์ด ์ผ์–ด๋‚˜๋ฉฐ, user์˜ interaction event๋„ ์ฒ˜๋ฆฌ๋œ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ถ„์— UI์™€ ๊ด€๋ จ๋œ ์ผ์€ main thread์—์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค.

func checkedOut(_ booksOnLoan: [Book]) {
    booksView.checkedOutBooks = booksOnLoan
}
 
DispatchQueue.main.async {
    checkedOut(booksOnLoan)
}

ํ•˜์ง€๋งŒ, ๋ชจ๋“  ์ž‘์—…์„ main thread์—์„œ ํ•  ํ•„์š”๋Š” ์—†๋‹ค. ๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ๋ณดํ†ต ๋‹ค๋ฅธ ์ž‘์—…๋“ค์„ ํ•˜๋‹ค๊ฐ€ DispatchQueue.main.async๋ฅผ ํ†ตํ•ด์„œ main thread์—์„œ ํ•  ๋™์ž‘์„ ๋„˜๊ฒจ์ฃผ๊ณค ํ–ˆ์—ˆ๋‹ค. ๊ทผ๋ฐ ์ž˜ ์ƒ๊ฐํ•ด๋ณด๋ฉด, ์ด๊ฑด actor๊ฐ€ ๋Œ์•„๊ฐ€๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜๊ณผ ๋น„์Šทํ•˜์ง€ ์•Š์„๊นŒ? main thread๋Š” syncํ•˜๊ฒŒ ๋™์ž‘ํ•ด์•ผ ํ•˜๋ฉฐ, ํ•œ์ „ํ•˜๊ฒŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ด์•ผ ํ•œ๋‹ค.

The Main Actor

์ด๋Ÿฐ ํ•„์š”์„ฑ์— ์˜ํ•ด main actor๊ฐ€ ๋‚˜์™”๋‹ค.

@MainActor func checkedOut(_ booksOnLoan: [Book]) {
    booksView.checkedOutBooks = booksOnLoan
}
 
await checkedOut(booksOnLoan)
  • main thread๋ฅผ ๋Œ€ํ‘œํ•œ๋‹ค.
  • ํ•ด๋‹น ํ•จ์ˆ˜๋กœ ๋“ค์–ด์˜ค๋Š” ๋ชจ๋“  task๋ฅผ main dispatch queue์—์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค.
  • main thread์—์„œ ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š” ์ฝ”๋“œ๋Š” ์—ฌ๊ธฐ์ €๊ธฐ ํฉ์–ด์ ธ์žˆ์—ˆ๋‹ค. main actor๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์„ ์–ธํ•˜๋Š” ๊ฒƒ์œผ๋กœ ํ•ด๊ฒฐ ๊ฐ€๋Šฅํ•˜๋‹ค.
@MainActor class MyViewController: UIViewController {
    func onPress(...) { ... } // ์•”๋ฌต์ ์œผ๋กœ @MainActor์ž„
 
    nonisolated func fetchLatestAndDisplay() async { ... }
}

type์— @MainActor๋ฅผ ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด, member๋“ค๊ณผ subclass ๋ชจ๋‘ main Actor๋กœ ๋™์ž‘ํ•œ๋‹ค. UI์™€ ์ƒํ˜ธ์ž‘์šฉํ•ด์•ผ ๋งŒํ•˜๊ฑฐ๋‚˜, ๋Œ€๋ถ€๋ถ„์ด main์— ๋Œ์•„๊ฐ€์•ผ ํ•œ๋‹ค๋ฉด ์œ ์šฉํ•˜๊ฒŒ ์“ธ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค. ์ด ๊ฒฝ์šฐ, ๊ฐœ๋ณ„์ ์œผ๋กœ actor ๊ฒฉ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์ง€ ์•Š์€ ๊ฒฝ์šฐ nonsolated ํ‚ค์›Œ๋“œ๋ฅผ ํ†ตํ•ด ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

๋งˆ๋ฌด๋ฆฌ

  • Actor type์„ ์‚ฌ์šฉํ•ด์„œ mutable state์— syncํ•˜๊ฒŒ ์ ‘๊ทผํ•˜์ž.
  • rerentrancy๋ฅผ ์œ„ํ•œ ์„ค๊ณ„๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
  • data race๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด value ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์ž.
  • ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๋ ค๋ฉด Sendable protocol์„ ์ฑ„ํƒํ•˜์—ฌ checking์„ ์ˆ˜ํ–‰ํ•˜์ž.
  • @MainActor๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ด์ „์— DispatchQueue.main.async๋กœ ์ˆ˜ํ–‰ํ–ˆ๋˜ ๊ฒƒ์„ ๋ฐ”๊ฟ”๋ณด์ž.

Reference