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在正确的逻辑中。

  

posted @ 2021-07-23 15:45  兜兜有糖的博客  阅读(567)  评论(0编辑  收藏  举报