撰寫一個簡單的代碼:
class App {
private var value = 0
func start() async throws {
await withTaskGroup(of: Void.self) { group in
for _ in 1...100 {
group.addTask(operation: self.increment) // 1
group.addTask {
await self.increment() // 2
}
group.addTask {
self.value = 1 // 3
}
}
}
}
@Sendable private func increment() async {
self.value = 1 // 4
}
}
我在第 2、3 行收到編譯時警告:
Capture of 'self' with non-sendable type 'App' in a @Sendable closure
但是,啟用 Thread Sanitizer 并洗掉了第 2 行和第 3 行,我在第 4 行收到了 ThreadSanitizer 運行時警告:
Swift access race in (1) suspend resume partial function for asyncTest.App.increment@Sendable () async -> () at 0x106003c80
所以我有問題:
- 使用這 3 種 .addTask 方式有什么區別?
- @Sendable 屬性有什么作用?
- 如何使 increment() 函式執行緒安全(無資料競爭)?
uj5u.com熱心網友回復:
為了說明如何實作執行緒安全,請考慮:
class Counter {
private var value = 0
func incrementManyTimes() async {
await withTaskGroup(of: Void.self) { group in
for _ in 1...1_000_000 {
group.addTask {
self.increment() // no `await` as this is not `async` method
}
}
await group.waitForAll()
if value != 1_000_000 {
print("not thread-safe apparently; value =", value) // not thread-safe apparently; value = 994098
} else {
print("ok")
}
}
}
private func increment() { // note, this isn't `async` (as there is no `await` suspension point in here)
value = 1
}
}
這說明它不是執行緒安全的,驗證了來自 TSAN 的警告。(注意,我增加了迭代次數,以便更容易表現出非執行緒安全代碼的癥狀。)
那么,你將如何使它成為執行緒安全的呢?使用演員:
actor Counter {
private var value = 0
func incrementManyTimes() async {
await withTaskGroup(of: Void.self) { group in
for _ in 1...1_000_000 {
group.addTask {
await self.increment()
}
}
await group.waitForAll()
if value != 1_000_000 {
print("not thread-safe apparently; value =", value)
} else {
print("ok") // ok
}
}
}
private func increment() { // note, this still isn't `async`
value = 1
}
}
如果你真的想使用一個類,添加你自己的老式同步。這里我使用了一個鎖,但你可以使用一個串行 GCD 佇列,或者任何你想要的。
class Counter {
private var value = 0
let lock = NSLock()
func incrementManyTimes() async {
await withTaskGroup(of: Void.self) { group in
for _ in 1...1_000_000 {
group.addTask {
self.increment()
}
}
await group.waitForAll()
if value != 1_000_000 {
print("not thread-safe apparently; value =", value)
} else {
print("ok") // ok
}
}
}
private func increment() {
lock.synchronize {
value = 1
}
}
}
extension NSLocking {
func synchronize<T>(block: () throws -> T) rethrows -> T {
lock()
defer { unlock() }
return try block()
}
}
或者,如果你也想創建Counter一個Sendable型別,確信你自己正確地進行了同步,就像上面一樣,你可以將它宣告為final并宣告它是Sendable,編譯器承認@unchecked:
final class Counter: @unchecked Sendable {
private var value = 0
let lock = NSLock()
func incrementManyTimes() async {
// same as above
}
private func increment() {
lock.synchronize {
value = 1
}
}
}
但是因為編譯器不可能對實際的“可發送性”本身進行推理,所以你必須將其指定為 a@unchecked Sendable讓它知道你已經親自驗證了它是真的Sendable。
但是actor是確保執行緒安全的首選機制,消除了對這種自定義同步邏輯的需要。
有關更多資訊,請參閱 WWDC 2021 視頻使用 Swift actor 保護可變狀態或 2022 的使用 Swift Concurrency 消除資料競爭。
有關該屬性的作用的討論,請參閱Sendable 檔案。@Sendable
順便說一句,我懷疑你知道這一點,但為了未來的讀者,雖然increment它可以用于說明目的,但它不是并行性的好候選者。這種并行化的再現實際上比簡單的單執行緒解決方案要慢。為了實作并行性的性能提升,您需要在每個執行緒上進行足夠的作業,以證明并行性所需的適度開銷是合理的。
此外,在測驗并行性時,請注意使用模擬器,這會顯著/人為地限制 Swift 并發使用的協作執行緒池。在 macOS 目標或物理設備上進行測驗。模擬器和游樂場都不是這種并行練習的好測驗平臺。
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/522254.html
標籤:迅速异步等待快速并发
上一篇:從userDefaults.standard.sring(forKey"name").components(seperatedby:"")我想制作一個串列
