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