Swift?中的?Actors?使用及如何防止數(shù)據(jù)競(jìng)爭(zhēng)問題(示例詳解)
前言
Actors 是 Swift 5.5 引入的一種并發(fā)編程模型,用于管理共享數(shù)據(jù)并提供數(shù)據(jù)訪問的安全性。Actors 使用異步消息傳遞來保護(hù)數(shù)據(jù),防止數(shù)據(jù)競(jìng)爭(zhēng)和其他并發(fā)問題。在這篇回答中,我將解釋 Actors 的基本原理,并提供一些示例代碼來說明其用法和如何防止數(shù)據(jù)競(jìng)爭(zhēng)。
Swift 中的 Actors 旨在完全解決數(shù)據(jù)競(jìng)爭(zhēng)問題,但重要的是要明白,很可能還是會(huì)遇到數(shù)據(jù)競(jìng)爭(zhēng)。本文將介紹 Actors 是如何工作的,以及你如何在你的項(xiàng)目中使用它們。
Actors 的基本原理
Actors 是并發(fā)編程中的一種實(shí)體,它封裝了一組相關(guān)的數(shù)據(jù)和操作,并且只能通過異步消息進(jìn)行訪問。每個(gè) Actor 在任意時(shí)刻只能執(zhí)行一個(gè)任務(wù),從而避免了數(shù)據(jù)競(jìng)爭(zhēng)。其他代碼通過向 Actor 發(fā)送異步消息來請(qǐng)求數(shù)據(jù)或執(zhí)行操作,Actor 在收到消息后逐個(gè)處理它們。
Actors 使用 async 和 await 關(guān)鍵字定義異步函數(shù)和等待異步結(jié)果,從而支持并發(fā)操作。每個(gè) Actor 中的數(shù)據(jù)都是私有的,只能通過 Actor 提供的方法進(jìn)行修改和訪問,以保證數(shù)據(jù)的一致性和安全性。
下面是一個(gè)簡(jiǎn)單的示例,展示了如何定義和使用 Actor:
actor Counter { private var value = 0 func increment() { value += 1 } func getValue() async -> Int { return value } } // 創(chuàng)建 Counter Actor 實(shí)例 let counter = Counter() // 在異步任務(wù)中調(diào)用 Actor 方法 Task { await counter.increment() let result = await counter.getValue() print("Counter value: \(result)") }
在上面的代碼中,Counter 是一個(gè)簡(jiǎn)單的 Actor,包含一個(gè)私有的 value 變量和兩個(gè)方法 increment 和 getValue。increment 方法用于增加 value 的值,getValue 方法用于獲取當(dāng)前的 value 值。
在異步任務(wù)中,我們創(chuàng)建了一個(gè) Counter 的實(shí)例 counter,并通過 await 關(guān)鍵字調(diào)用了 increment 和 getValue 方法。注意,在調(diào)用 Actor 的方法時(shí),我們使用了 await 關(guān)鍵字來等待異步操作完成,并確保在訪問和修改 Actor 數(shù)據(jù)時(shí)的安全性。
Actor 是引用類型,但與類相比仍然有所不同
Actor 是引用類型,簡(jiǎn)而言之,這意味著副本引用的是同一塊數(shù)據(jù)。因此,修改副本也會(huì)修改原始實(shí)例,因?yàn)樗鼈冎赶蛲粋€(gè)共享實(shí)例。你可以在我的文章Swift 中的 Struct 與 class 的區(qū)別中了解更多這方面的信息。
然而,與類相比,Actor 有一個(gè)重要的區(qū)別:他們不支持繼承。
Swift 中的 Actor 幾乎和類一樣,但不支持繼承。
不支持繼承意味著不需要像便利初始化器和必要初始化器、重寫、類成員或 open
和 final
語句等功能。
然而,最大的區(qū)別是由 Actor 的主要職責(zé)決定的,即隔離對(duì)數(shù)據(jù)的訪問。
為什么會(huì)出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng)
數(shù)據(jù)競(jìng)爭(zhēng)是多線程/并發(fā)編程中常見的問題,它發(fā)生在多個(gè)線程同時(shí)訪問共享數(shù)據(jù),并且至少其中一個(gè)線程對(duì)該數(shù)據(jù)進(jìn)行了寫操作。當(dāng)多個(gè)線程同時(shí)讀寫共享數(shù)據(jù)時(shí),數(shù)據(jù)的最終結(jié)果可能會(huì)產(chǎn)生不確定性,導(dǎo)致程序出現(xiàn)錯(cuò)誤的行為。
數(shù)據(jù)競(jìng)爭(zhēng)發(fā)生的原因主要有以下幾個(gè)方面:
1、競(jìng)態(tài)條件(Race Condition):當(dāng)多個(gè)線程在沒有適當(dāng)同步的情況下并發(fā)地訪問共享數(shù)據(jù)時(shí),它們的執(zhí)行順序是不確定的。這可能導(dǎo)致數(shù)據(jù)的交錯(cuò)讀寫,從而導(dǎo)致數(shù)據(jù)的最終結(jié)果出現(xiàn)錯(cuò)誤。
2、缺乏同步:如果多個(gè)線程在沒有適當(dāng)?shù)耐綑C(jī)制的情況下訪問共享數(shù)據(jù),就會(huì)產(chǎn)生數(shù)據(jù)競(jìng)爭(zhēng)。例如,多個(gè)線程同時(shí)對(duì)同一個(gè)變量進(jìn)行寫操作,而沒有使用互斥鎖或其他同步機(jī)制來保證原子性。
3、共享資源的修改:當(dāng)多個(gè)線程同時(shí)對(duì)共享資源進(jìn)行寫操作時(shí),就會(huì)產(chǎn)生數(shù)據(jù)競(jìng)爭(zhēng)。如果沒有適當(dāng)?shù)耐綑C(jī)制來保護(hù)共享資源的一致性,就會(huì)導(dǎo)致數(shù)據(jù)競(jìng)爭(zhēng)。
4、可見性問題:多個(gè)線程可能具有各自的本地緩存或寄存器,這導(dǎo)致它們對(duì)共享數(shù)據(jù)的可見性存在延遲。當(dāng)一個(gè)線程修改了共享數(shù)據(jù),其他線程可能無法立即看到這個(gè)修改,從而導(dǎo)致數(shù)據(jù)競(jìng)爭(zhēng)。
數(shù)據(jù)競(jìng)爭(zhēng)可能導(dǎo)致程序出現(xiàn)各種問題,包括不確定的結(jié)果、崩潰、死鎖等。為了避免數(shù)據(jù)競(jìng)爭(zhēng),需要采取適當(dāng)?shù)牟l(fā)控制措施,例如使用鎖、互斥量、信號(hào)量等同步機(jī)制來保護(hù)共享數(shù)據(jù)的訪問,或者使用并發(fā)安全的數(shù)據(jù)結(jié)構(gòu)來代替共享數(shù)據(jù)。
在 Swift 中,引入了一些并發(fā)編程的機(jī)制,如 async/await 和 Actor,可以幫助開發(fā)者更容易地處理并發(fā)問題和避免數(shù)據(jù)競(jìng)爭(zhēng)。但仍需要開發(fā)者在編寫并發(fā)代碼時(shí)注意使用正確的同步機(jī)制和遵循最佳實(shí)踐,以確保數(shù)據(jù)的安全和正確性。
如何防止數(shù)據(jù)競(jìng)爭(zhēng)
Actors 通過限制同一時(shí)間只有一個(gè)任務(wù)可以訪問 Actor 中的數(shù)據(jù)來防止數(shù)據(jù)競(jìng)爭(zhēng)。這種限制確保了數(shù)據(jù)的一致性和線程安全性,而無需顯式使用鎖或其他同步機(jī)制。
Actors 還提供了數(shù)據(jù)的原子性訪問和修改操作。在 Actor 內(nèi)部,數(shù)據(jù)可以在異步環(huán)境中自由地修改,而不需要額外的同步操作。同時(shí),Actors 會(huì)保證 Actor 的內(nèi)部狀態(tài)在處理完一個(gè)消息之前不會(huì)被其他任務(wù)訪問,從而避免了并發(fā)問題。
在上面的示例中,我們可以看到在異步任務(wù)中通過 await 關(guān)鍵字調(diào)用 Counter 的方法。這樣做可以確保在不同的任務(wù)中對(duì) Counter 的訪問是串行的,從而避免了數(shù)據(jù)競(jìng)爭(zhēng)和其他并發(fā)問題。
Actors 是 Swift 中用于并發(fā)編程的一種模型,它通過異步消息傳遞來保護(hù)共享數(shù)據(jù),并防止數(shù)據(jù)競(jìng)爭(zhēng)。下面是一些闡述 Actors 的使用和防止數(shù)據(jù)競(jìng)爭(zhēng)的關(guān)鍵要點(diǎn):
1、定義 Actor:使用 actor 關(guān)鍵字來定義一個(gè) Actor 類,將要保護(hù)的數(shù)據(jù)和相關(guān)操作封裝在其中。例如:
actor MyActor { private var sharedData: Int = 0 func performTask() { // 對(duì)共享數(shù)據(jù)進(jìn)行操作 } }
2、異步訪問:通過 async 和 await 關(guān)鍵字來標(biāo)記異步函數(shù)和等待異步結(jié)果。只有通過異步函數(shù)或方法訪問 Actor 中的數(shù)據(jù)才是安全的。例如:
actor MyActor { private var sharedData: Int = 0 func performTask() async { sharedData += 1 await someAsyncOperation() let result = await anotherAsyncOperation() // 對(duì)共享數(shù)據(jù)進(jìn)行操作 } }
3、 發(fā)送異步消息:通過在 Actor 實(shí)例上使用 await 關(guān)鍵字來發(fā)送異步消息,并調(diào)用 Actor 中的方法。這樣做可以確保對(duì) Actor 的訪問是串行的,從而避免了數(shù)據(jù)競(jìng)爭(zhēng)。例如:
let myActor = MyActor() Task { await myActor.performTask() }
4、數(shù)據(jù)保護(hù):由于 Actors 限制了同一時(shí)間只能執(zhí)行一個(gè)任務(wù),因此可以保證對(duì)共享數(shù)據(jù)的訪問是串行的,從而避免了數(shù)據(jù)競(jìng)爭(zhēng)。Actors 還提供了內(nèi)部狀態(tài)的保護(hù),確保在處理一個(gè)消息之前不會(huì)被其他任務(wù)訪問。
使用 async/await 訪問數(shù)據(jù)
在 Swift 中,使用 async/await 關(guān)鍵字來進(jìn)行異步訪問數(shù)據(jù)是一種安全且方便的方式,特別適用于訪問 Actor 中的共享數(shù)據(jù)。下面是一些示例代碼來說明如何使用 async/await 訪問數(shù)據(jù):
actor MyActor { private var sharedData: Int = 0 func readData() async -> Int { return sharedData } func writeData(value: Int) async { sharedData = value } } // 創(chuàng)建 MyActor 實(shí)例 let myActor = MyActor() // 異步讀取共享數(shù)據(jù) Task { let data = await myActor.readData() print("Shared data: \(data)") } // 異步寫入共享數(shù)據(jù) Task { await myActor.writeData(value: 10) print("Data written successfully") }
在上面的示例中,readData 方法和 writeData 方法被標(biāo)記為 async,表示它們是異步的。通過 await 關(guān)鍵字,我們可以在異步任務(wù)中等待數(shù)據(jù)的讀取和寫入操作完成。
使用 await 關(guān)鍵字調(diào)用 readData 方法時(shí),任務(wù)會(huì)等待直到共享數(shù)據(jù)的讀取操作完成,并將結(jié)果返回。類似地,使用 await 關(guān)鍵字調(diào)用 writeData 方法時(shí),任務(wù)會(huì)等待直到共享數(shù)據(jù)的寫入操作完成。
需要注意的是,在使用 async/await 訪問數(shù)據(jù)時(shí),要確保訪問的方法或?qū)傩允钱惒降?。?duì)于 Actor 中的方法,可以在其聲明前加上 async 關(guān)鍵字,表示它們是異步的。對(duì)于屬性,可以將其聲明為異步的計(jì)算屬性。
防止不必要的暫停
在上面的例子中,我們正在訪問我們 Actor 的兩個(gè)不同部分。首先,我們更新吃食的雞的數(shù)量,然后我們執(zhí)行另一個(gè)異步任務(wù),打印出吃食的雞的數(shù)量。每個(gè) await
都會(huì)導(dǎo)致你的代碼暫停,以等待訪問。在這種情況下,有兩個(gè)暫停是有意義的,因?yàn)閮刹糠制鋵?shí)沒有什么共同點(diǎn)。然而,你需要考慮到可能有另一個(gè)線程在等待調(diào)用 chickenStartsEating
,這可能會(huì)導(dǎo)致在我們打印出結(jié)果的時(shí)候有兩只吃食的雞。
為了更好地理解這個(gè)概念,讓我們來看看這樣的情況:你想把操作合并到一個(gè)方法中,以防止額外的暫停。例如,設(shè)想在我們的 actor
中有一個(gè)通知方法,通知觀察者有一只新的雞開始吃東西:
extension ChickenFeeder { func notifyObservers() { NotificationCenter.default.post(name: NSNotification.Name("chicken.started.eating"), object: numberOfEatingChickens) } }
我們可以通過使用 await
兩次來使用此代碼:
let feeder = ChickenFeeder() await feeder.chickenStartsEating() await feeder.notifyObservers()
然而,這可能會(huì)導(dǎo)致兩個(gè)暫停點(diǎn),每個(gè) await
都有一個(gè)。相反,我們可以通過從 chickenStartsEating
中調(diào)用 notifyObservers
方法來優(yōu)化這段代碼:
func chickenStartsEating() { numberOfEatingChickens += 1 notifyObservers() }
由于我們已經(jīng)在 Actor 內(nèi)有了同步的訪問,我們不需要另一個(gè)等待。這些都是需要考慮的重要改進(jìn),因?yàn)樗鼈兛赡軙?huì)對(duì)性能產(chǎn)生影響。
非隔離(nonisolated)訪問
在 Swift 中,非隔離(nonisolated)訪問是指在一個(gè)異步函數(shù)內(nèi)部訪問類、結(jié)構(gòu)體或枚舉的非隔離成員。異步函數(shù)默認(rèn)情況下是隔離的,這意味著在異步函數(shù)內(nèi)部只能訪問該類型的隔離成員。但有時(shí)候我們需要在異步函數(shù)中訪問非隔離成員,這時(shí)就可以使用非隔離訪問。
為了進(jìn)行非隔離訪問,需要使用 nonisolated 關(guān)鍵字來修飾訪問權(quán)限。例如,假設(shè)有一個(gè)類 MyClass,其中有一個(gè)非隔離成員屬性 value:
class MyClass { nonisolated var value: Int = 0 }
現(xiàn)在,我們可以在異步函數(shù)內(nèi)部訪問 MyClass 的 value 屬性:
func asyncFunction() async { let instance = MyClass() await doSomething(with: instance.value) // 非隔離訪問 }
需要注意以下幾點(diǎn):
1、非隔離訪問只能在異步函數(shù)內(nèi)部進(jìn)行。在同步環(huán)境下或其他異步函數(shù)內(nèi)部,仍然需要使用隔離訪問。
2、非隔離訪問是一種權(quán)限放寬的操作,因此需要謹(jǐn)慎使用。確保在異步函數(shù)中對(duì)非隔離成員的訪問是安全的,并且不會(huì)引入數(shù)據(jù)競(jìng)爭(zhēng)或其他問題。
3、非隔離訪問只適用于可變性為非 nonmutating 的成員,即對(duì)可修改的成員進(jìn)行訪問。對(duì)于可讀的非隔離成員,可以直接使用隔離訪問。
為什么在使用 Actors 時(shí)仍會(huì)出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng)?
當(dāng)在你的代碼中持續(xù)使用 Actors 時(shí),你肯定會(huì)降低遇到數(shù)據(jù)競(jìng)爭(zhēng)的風(fēng)險(xiǎn)。創(chuàng)建同步訪問可以防止與數(shù)據(jù)競(jìng)爭(zhēng)有關(guān)的奇怪崩潰。然而,你顯然需要持續(xù)地使用它們來防止你的應(yīng)用程序中出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng)。
在你的代碼中仍然可能出現(xiàn)競(jìng)爭(zhēng)條件,但可能不再導(dǎo)致異常。認(rèn)識(shí)到這一點(diǎn)很重要,因?yàn)锳ctors 畢竟被宣揚(yáng)為可以解決一切問題的工具。例如,想象一下兩個(gè)線程使用 await
正確地訪問我們的 Actor 的數(shù)據(jù):
queueOne.async { await feeder.chickenStartsEating() } queueTwo.async { print(await feeder.numberOfEatingChickens) }
這里的競(jìng)爭(zhēng)條件定義為:“哪個(gè)線程將首先開始隔離訪問?”。所以基本上有兩種結(jié)果:
- 隊(duì)列一在先,增加吃食的雞的數(shù)量。隊(duì)列二將打?。?
- 隊(duì)列二在先,打印出吃食的雞的數(shù)量,該數(shù)量仍為:0
這里的不同之處在于我們?cè)谛薷臄?shù)據(jù)時(shí)不再訪問數(shù)據(jù)。如果沒有同步訪問,在某些情況下這可能會(huì)導(dǎo)致無法預(yù)料的行為。
總結(jié)
使用 async/await 關(guān)鍵字來訪問數(shù)據(jù)是 Swift 中一種安全且方便的方式。在訪問 Actor 中的共享數(shù)據(jù)時(shí),可以使用 async/await 關(guān)鍵字來標(biāo)記異步方法,并通過 await 關(guān)鍵字等待讀取和寫入操作的完成。這樣可以確保數(shù)據(jù)的訪問是線程安全的,并且能夠充分利用 Swift 提供的并發(fā)編程能力。
到此這篇關(guān)于Swift 中的 Actors 使用以及如何防止數(shù)據(jù)競(jìng)爭(zhēng)的文章就介紹到這了,更多相關(guān)Swift Actors 使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Ubuntu 16.04上安裝 Swift 3.0及問題解答
本文給大家分享的是在Ubuntu系統(tǒng)中安裝 Swift 3.0的方法和步驟,以及安裝過程中有可能遇到的問題的解答,這里推薦給小伙伴們,希望大家能夠喜歡2016-07-07switch實(shí)現(xiàn)一個(gè)兩數(shù)的運(yùn)算代碼示例
這篇文章主要介紹了switch實(shí)現(xiàn)一個(gè)兩數(shù)的運(yùn)算代碼示例,需要的朋友可以參考下2017-06-06Swift?中的?Actors?使用及如何防止數(shù)據(jù)競(jìng)爭(zhēng)問題(示例詳解)
Swift中的Actors旨在完全解決數(shù)據(jù)競(jìng)爭(zhēng)問題,但重要的是要明白,很可能還是會(huì)遇到數(shù)據(jù)競(jìng)爭(zhēng),本文將介紹Actors是如何工作的,以及你如何在你的項(xiàng)目中使用它們,感興趣的朋友跟隨小編一起看看吧2023-06-06Swift使用CoreData時(shí)遇到的一些填坑記錄
這篇文章主要給大家記錄了在Swift使用CoreData時(shí)遇到的一些坑,以及介紹了CoreData在Swift 3.0中的一點(diǎn)改變,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。2017-12-12Swift 3.1聊天界面鍵盤效果的實(shí)現(xiàn)詳解
這篇文章主要給大家介紹了Swift 3.1聊天界面鍵盤效果實(shí)現(xiàn)的相關(guān)資料,文中介紹的非常詳細(xì),相信對(duì)大家的學(xué)習(xí)或者工作具有一定的參考價(jià)值,需要的朋友們下面來一起看看吧。2017-04-04Swift實(shí)現(xiàn)倒計(jì)時(shí)5秒功能
這篇文章主要為大家詳細(xì)介紹了Swift實(shí)現(xiàn)倒計(jì)時(shí)5秒功能,在“登錄”和“注冊(cè)”頁(yè)面也有相似功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03