概念:
所謂獨占性原則,是指:
如果一個存儲引用表達式的求值結(jié)果是一個由變量所實現(xiàn)的存儲引用闸天,那么對這個引用的訪問的持續(xù)時間段,不應該與其他任何對這個相同變量的訪問持續(xù)時間段產(chǎn)生重合斜做,除非這兩個訪問都是讀取訪問苞氮。(不應該出現(xiàn)重入訪問,Swift只能基本上禁止重(再)入式的訪問)而且獨占性原則會保證在訪問期間不會有其他訪問能對原來的變量進行修改瓤逼。
這里有一個地方故意說得比較模糊:這條原則只指出了訪問“不應該”產(chǎn)生重合笼吟,但是它沒有指出如何強制做到這一點。這是因為我們將對不同類型的存儲使用不同的機制來強制檢測獨占性原則霸旗。
獨占性原則的強制適用機制: 想要讓獨占性原則適用贷帮,我們有三種可行機制:靜態(tài)強制,動態(tài)強制诱告,以及未定義強制撵枢。所要選擇的機制必須能由存儲聲明簡單地決定,因為存儲的定義和對它的所有直接的訪問方法都必須滿足聲明的要求精居。一般來說锄禽,我們通過存儲被聲明為的類型,它的容器 (如果存在的話靴姿,值類型沃但、引用類型、static變量)佛吓,以及任何存在的聲明標記 (比如 var 或者 inout 之類) 來決定適用哪種機制宵晚。
如果一個訪問不可能在其訪問期間被其它代碼訪問,那么就是一個瞬時訪問维雇。正常來說坝疼,兩個瞬時訪問是不可能同時發(fā)生的。大多數(shù)內(nèi)存訪問都是瞬時的谆沃。像+等運算符函數(shù)是長期訪問钝凶,不是瞬時訪問。
重疊訪問(獨占性原則唁影,獨占訪問)主要是出現(xiàn)在inout函數(shù)以及方法或者值類型的mutating方法耕陷、或者對值類型(的屬性)重疊訪問。此類情況我們稱它為長期訪問据沈,或者內(nèi)存的重疊訪問哟沫、獨占訪問。而引用類型的重疊訪問锌介,
重入錯誤: 【長期訪問時禁止變量或值(的屬性)重入嗜诀,出現(xiàn)同時訪問同一個內(nèi)存地址的讀寫沖突猾警。】隆敢,其實inout和mutating都是處理重入問題的手段和規(guī)則发皿,只有代碼寫的出格了才會出現(xiàn)重入錯誤。對于引用類型和靜態(tài)變量的訪問拂蝎,必須遵守動態(tài)強制的原則穴墅,檢測出那些必然會違反獨占性原則的情況。
如何保證獨占性: 動態(tài)的修改温自,對于性能的損失是不可忽視的玄货,在盡可能的情況下使用靜態(tài)的方法來保證獨占性。只有在確實無法靜態(tài)決定的情況下悼泌,再使用動態(tài)方式松捉。
技術(shù)總結(jié): 限制結(jié)構(gòu)體屬性的重疊訪問對于保證內(nèi)存安全不是必要的。保證內(nèi)存安全是必要的馆里,但因為訪問獨占權(quán)的要求比內(nèi)存安全還要更嚴格——意味著即使有些代碼違反了訪問獨占權(quán)的原則隘世,也是內(nèi)存安全的,所以如果編譯器可以保證這種非專屬的訪問是安全的也拜,那 Swift 就會允許這種行為的代碼運行以舒。特別是當你遵循下面的原則時趾痘,它可以保證結(jié)構(gòu)體屬性的重疊訪問是安全的:
你訪問的是實例的存儲屬性慢哈,而不是計算屬性或類的屬性
結(jié)構(gòu)體是本地變量的值,而非全局變量
結(jié)構(gòu)體要么沒有被閉包捕獲永票,要么只被非逃逸閉包捕獲了卵贱。不管什么時候,程序員應盡量避免對值類型的訪問出現(xiàn)重入侣集。
總的原則: 而在某個特定 (但是很重要) 的特殊情況以外(mutating時可以修改self或者結(jié)構(gòu)體是本地變量并且訪問的是實例的存儲屬性)键俱,總是將值類型的屬性當作是非獨立的來進行處理(獨占原則,不能將對不同的屬性的訪問和對整體值的訪問獨立開來)世分。
引用類型屬性和 static 屬性看作各自獨立的屬性
如果編譯器無法保證訪問的安全性编振,它就不會允許那次訪問。
想要維持變量的不變幾乎是不可能的臭埋,所以Swift基本上禁止重(再)入式的訪問踪央,但也不是完全可能的,只能盡可能地幫助規(guī)范程序員們的代碼書寫(提出inout瓢阴、mutating等規(guī)則保證內(nèi)存安全并盡可能提高性能)畅蹂。而且我們也要在運行時做一些額外的工作,來確保這樣的代碼不會導致未定義的行為荣恐,或者是讓整個進程發(fā)生錯誤液斜。
注意
如果你寫過并發(fā)和多線程的代碼累贤,內(nèi)存訪問沖突也許是同樣的問題。然而少漆,這里訪問沖突的討論是在單線程的情境下討論的臼膏,并沒有使用并發(fā)或者多線程。
如果你曾經(jīng)在單線程代碼里有訪問沖突检疫,Swift 可以保證你在編譯或者運行時會得到錯誤讶请。對于多線程的代碼,可以使用 Thread Sanitizer 去幫助檢測多線程的沖突屎媳。
代碼示例:
?長期訪問中出現(xiàn)了變量的重入夺溢,stepSize變量進入了2次。inout規(guī)則
??關于變量重入和捕獲變量的討論: 烛谊》缦欤【inout可以捕獲并可選的修改變量,也可以不使用inout直接通過閉包去捕獲變量】,暫時未支持在函數(shù)外用inout進行捕獲【類似 inout root = &tree.root】丹禀,只能使用閉包去捕獲變量状勤,但很麻煩。
var stepSize = 1
func increment(_ number: inout Int) {
number += stepSize
}
increment(&stepSize)
// Error: conflicting accesses to stepSize
?長期訪問中(輸入輸出形式參數(shù)函數(shù)中)多次重入相同的輸入輸出形式參數(shù)双泪。
func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
var playerOneScore = 42
var playerTwoScore = 30
balance(&playerOneScore, &playerTwoScore) // OK
balance(&playerOneScore, &playerOneScore)
// Error: conflicting accesses to playerOneScore
?mutating方法重入self
struct Player {
var name: String
var health: Int
var energy: Int
static let maxHealth = 10
mutating func restoreHealth() {
health = Player.maxHealth
}
}
extension Player {
mutating func shareHealth(with teammate: inout Player) {
balance(&teammate.health, &health)
}
}
var oscar = Player(name: "Oscar", health: 10, energy: 10)
var maria = Player(name: "Maria", health: 5, energy: 10)
oscar.shareHealth(with: &maria) // OK
oscar.shareHealth(with: &oscar)
// Error: conflicting accesses to oscar
?元組(的屬性)適用獨占性原則持搜,不能獨立訪問。(元組也是值類型)
var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// Error: conflicting access to properties of playerInformation
?在某個特定 (但是很重要) 的特殊情況以外(mutating時可以修改self或者結(jié)構(gòu)體是本地變量并且訪問的是實例的存儲屬性)焙矛,總是將值類型(的屬性)當作是非獨立的來進行處理(獨占原則)葫盼。
var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy) // Error
?特殊情況下,編譯器可以保證對結(jié)構(gòu)體屬性的重疊訪問是安全的村斟,那 Swift 就會允許這種行為的代碼運行贫导。我們通過3個特別的原則進行判斷重疊訪問是否是內(nèi)存安全的。
- 你訪問的是實例的存儲屬性蟆盹,而不是計算屬性或類的屬性
- 結(jié)構(gòu)體是本地變量的值孩灯,而非全局變量
- 結(jié)構(gòu)體要么沒有被閉包捕獲,要么只被非逃逸閉包捕獲了
func someFunction() {
var oscar = Player(name: "Oscar", health: 10, energy: 10)
balance(&oscar.health, &oscar.energy) // OK
}
? 通過下標訪問值類型的一個部分和訪問整個值是同等對待的(再入式訪問)逾滥,包括同一個數(shù)組內(nèi)兩個不同的數(shù)組元素進行同時(重疊)訪問都不是內(nèi)存安全的峰档。
這會妨礙到某些操作數(shù)組時的通常做法,不過有些 (比如并行地改變一個數(shù)組的不同切片這類) 事情在 Swift 中本來就充滿了問題寨昙。我們認為讥巡,通過有目的的對集合的 API 進行改進,可以將緩和所帶來的主要影響毅待。對于數(shù)組的操作可能是并行編程中比較常見的多線程問題尚卫。在很大程度上,下標操作和實例屬性的訪問類似尸红,我們可以通過加鎖或者 GCD 做 barrier 的方式來確保數(shù)組線程安全吱涉。(Swift中數(shù)組不是線程安全的)
?總是將引用類型屬性和 static 屬性看作各自獨立的屬性刹泄,不會有重疊訪問的問題,但swift暫時不支持原子操作怎爵,所以多線程中處理這類屬性時需要加鎖特石。
? 閉包中的嵌套式訪問,如何處理這種同一個變量再入式的訪問。想要維持變量的不變幾乎是不可能的鳖链,所以Swift基本上禁止重(再)入式的訪問姆蘸,但也不是完全可能的,只能盡可能地幫助規(guī)范程序員們的代碼書寫(提出inout芙委、mutating等規(guī)則保證內(nèi)存安全并盡可能提高性能)逞敷。而且我們也要在運行時做一些額外的工作,來確保這樣的代碼不會導致未定義的行為灌侣,或者是讓整個進程發(fā)生錯誤推捐。
如果閉包 C 不會逃逸出函數(shù),閉包predicate的調(diào)用會被靜態(tài)強制認為是試圖對 某個變量V(Self) 進行寫操作的調(diào)用侧啼,同時也會被視作對 某個變量V(Self) 的讀取操作牛柒。那么這樣的再入訪問就有可能發(fā)生修改,需要程序員有意識的假設會出現(xiàn)修改和預防修改痊乾,可以使用值語義進行復制皮壁,或者深拷貝。如果閉包 C 有可能逃逸哪审,必須遵守動態(tài)強制的原則蛾魄,除非所有的訪問都是讀取訪問。
extension Array {
mutating func organize(_ predicate: (Element) -> Bool) {
let first = self[0]
if !predicate(first) { return }
...
// something here uses first
}
}
不過协饲,如果我們能夠支持唯一引用的 class 類型的話畏腕,獨占性原則就可以靜態(tài)地適用它們的屬性了缴川。(暫時Swift還未支持)