您好,登錄后才能下訂單哦!
這篇文章主要介紹了iOS開發多線程下全局變量賦值崩潰原理是什么的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇iOS開發多線程下全局變量賦值崩潰原理是什么文章都會有所收獲,下面我們一起來看看吧。
在多線程下同時給全局變量賦值時會發生崩潰:
static NSObject *_instance; - (void)foo { _instance = [[NSObject alloc] init]; }
如下為源碼的匯編代碼:
Demo-iOS`-[ViewController foo]: 0x104e4e088 <+0>: stp x29, x30, [sp, #-0x10]! 0x104e4e08c <+4>: mov x29, sp # newValue = [[NSObject alloc] init] 0x104e4e094 <+12>: ldr x0, #0x7454 ; (void *)0x00000001db209e08: NSObject 0x104e4e098 <+16>: bl 0x104e4e438 ; symbol stub for: objc_alloc_init # oldValue = _instance 0x104e4e09c <+20>: adrp x9, 7 0x104e4e0a0 <+24>: ldr x8, [x9, #0x788] # _instance = newValue 0x104e4e0a4 <+28>: str x0, [x9, #0x788] # objc_release(oldValue) 0x104e4e0a8 <+32>: mov x0, x8 0x104e4e0ac <+36>: ldp x29, x30, [sp], #0x10 0x104e4e0b0 <+40>: b 0x104e4e480 ; symbol stub for: objc_release
對匯編代碼進行反匯編,可以看出 ARC 下編譯器添加了讀取舊值 oldValue = _instance 和釋放舊值 objc_release(oldValue) 的操作:
- (void)foo { NSObject *newValue = [[NSObject alloc] init]; NSObject *oldValue = _instance; //讀取舊值 _instance = newValue; objc_release(oldValue); //釋放舊值 }
給全局變量賦值時會讀取舊值、釋放舊值,舊值是從全局變量讀取的,多個線程可以同時讀到同一個值,如果一個 線程 在訪問舊值時,舊值被其它線程銷毀,就會發生崩潰。
即使在代碼中添加了判空邏輯,也會有可能多個線程同時進入 if (!_instance) 里,發生錯誤:
static NSObject *_instance; - (void)foo { if (!_instance) { _instance = [[NSObject alloc] init]; } }
即使不崩潰,多個線程也會產生不同的實例,是不符合預期的
可以推斷出一種復現崩潰的辦法:
A B C 線程同時進入 - (NSObject *)foo
方法
A 線程先創建 NSObject 實例,賦值給 _instance (_instance = newValue),_instance 引用計數為 1
B、C 線程再開始執行,執行到 oldValue = _instance 時,會從 _instance 全局變量中讀到 A 線程創建的對象,賦值給各自的 oldValue,oldValue 引用計數為 1
B 線程在 objc_release(oldValue) 后會釋放 oldValue,oldValue 引用計數為 0,oldValue 被銷毀
C 線程在 objc_release(oldValue) 時訪問 oldValue,發生崩潰
lldb 的 thread continue 指令可以控制僅一個線程執行,其它線程保持掛起。
利用該指令,可以復現崩潰路徑,按下面步驟可以驗證:
準備三個線程執行 [self foo]
,并在 -foo
方法里面打上斷點:
可以多次測試讓 3 個 線程 同時進入斷點,進入斷點后可以看到 Thread 2、3、4 是創建的 3 個線程:
不加 asm("nop\n") 的話執行完 objc_release(oldValue) 后,foo 函數會直接結束,不太方便在 objc_release(oldValue) 之后打斷點進行調試,添加之后 objc_release 之后會有位置打斷點(第 4 5 步用到)
在 Thread 2 中給匯編代碼第 10 行打斷點,并執行 thread contine
,使 Thread 2 執行完 _instance = newValue :
可以看到 Thread 2 創建的實例為 0x0000000280df8020
使 Thread 3、4 線程 執行完 oldValue = _instance
步驟1:刪除斷點(每次切換線程都要刪掉斷點,不然 Xcode 可能會有 bug ...),切換到 Thread 3 ,給第 9 行打斷點,并執行 thread continue:
在 Xcode Debug Navitor 中選擇線程堆棧可以切換線程
或者使用 lldb,thread select 3
切換線程
步驟2:刪除斷點,切換到 Thread 4,給第 9 行打斷點,并執行 thread continue
:
可以發現 Thread 3、4 讀到的舊值都是 Thread 2 創建的 0x0000000280df8020
使Thread 3 執行完 objc_release(oldValue)
步驟:刪除斷點,切換到 Thread 3,給第 12 行打斷點,并執行 thread continue
:
此時 oldValue 引用計數為 0,被銷毀
使 Thread 4 執行 objc_release(oldValue), 訪問 oldValue
步驟:刪除斷點,切換到 Thread 4,給第 12 行打斷點,并執行 thread continue
:
在 Thread 3 執行 objc_release(oldValue) 后 oldValue 就已經被銷毀了,
Thread 4 再次訪問時會發生崩潰
對成員變量賦值時同樣有這個問題
@property (nonatomic, strong) NSObject *obj; - (NSObject *)getInstance { _obj = [[NSObject alloc] init]; return _obj; }
局部變量不會有這個問題
局部變量不涉及"將舊值釋放"這個操作。
關于“iOS開發多線程下全局變量賦值崩潰原理是什么”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“iOS開發多線程下全局變量賦值崩潰原理是什么”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。