Swift中內(nèi)存安全之獨占性原則

概念:

所謂獨占性原則,是指:

如果一個存儲引用表達式的求值結(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還未支持)

參考文獻

OwnershipManifesto.
onevcat
Swift教程
Swift中文教程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末茉稠,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子把夸,更是在濱河造成了極大的恐慌而线,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恋日,死亡現(xiàn)場離奇詭異膀篮,居然都是意外死亡,警方通過查閱死者的電腦和手機岂膳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門誓竿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人谈截,你說我怎么就攤上這事筷屡〗担” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵毙死,是天一觀的道長燎潮。 經(jīng)常有香客問我,道長扼倘,這世上最難降的妖魔是什么确封? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮再菊,結(jié)果婚禮上爪喘,老公的妹妹穿的比我還像新娘。我一直安慰自己纠拔,他們只是感情好腥放,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绿语,像睡著了一般秃症。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吕粹,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天种柑,我揣著相機與錄音,去河邊找鬼匹耕。 笑死聚请,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的稳其。 我是一名探鬼主播驶赏,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼既鞠!你這毒婦竟也來了煤傍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤嘱蛋,失蹤者是張志新(化名)和其女友劉穎蚯姆,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體洒敏,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡龄恋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了凶伙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片郭毕。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖函荣,靈堂內(nèi)的尸體忽然破棺而出显押,到底是詐尸還是另有隱情链韭,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布煮落,位于F島的核電站敞峭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蝉仇。R本人自食惡果不足惜旋讹,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望轿衔。 院中可真熱鬧沉迹,春花似錦、人聲如沸害驹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宛官。三九已至葫松,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間底洗,已是汗流浹背腋么。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留亥揖,地道東北人珊擂。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像费变,于是被迫代替她去往敵國和親摧扇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345