Swift全局變量的線程安全分析

一、示例代碼

import UIKit

let obj = TestObj()

class TestObj {
    init() {
        print("\(type(of: self)) init")
    }
}

class ViewController: UIViewController {
    
    func test() {
        let x = obj
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        print("ViewController start view didload")
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
            for _ in 0...100000 {
                DispatchQueue.global().async {
                    let x = obj
                    print(x)
                }
            }
        }
    }
}

 問題,TestObj 中的初始化方法何時調用?

二、經過測試

  方法在 viewDidLoad 的 log “ViewController start view didload”輸出5s之後纔會調用

  因爲swift中static修飾的全局變量或者類變量都是lazy load,也就是第一次使用的時候纔會初始化,並且這個初始化過程是原子化的 

 

 

 

 

三、多線程安全分析 

  結論: 

    對於只讀的全局變量,在多線程環境下面讀是安全的;

    對於可變的全局變量,多線程下面寫是不安全的,可能造成引用計數不正確,錯誤釋放。

    具體可參考:

    https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html

  爲什麼只讀的情況下面多線程訪問是安全的,可以看下面的方法

  

 

 

 這個方法的反彙編(DEBUG模式下面,編譯器優化之後可能有變化)

int _$s14GlobalVarTest214ViewControllerC4testyyF() {
    rax = GlobalVarTest2.obj.unsafeMutableAddressor : GlobalVarTest2.TestObj();
    rax = *rax;
    var_18 = rax;
    swift_retain(rax);
    rax = swift_release(var_18);
    return rax;
}

可以看到全局變量在這個作用域內是先進行了retain操作,最後進行了release。

對一個對象的retain和release操作,本身這個方法是原子操作的,所以不用擔心這兩個方法的線程安全問題。

如果是全局可寫的變量,當兩個線程對這個對象進行改變的時候

retain和release中間會出現穿插,導致業務上引用計數的變化不再安全。

因此,對於全局變量寫操作,必須進行加鎖,保證retain和release在正確的邏輯中。

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章