atomic不是絕對的線程安全。atomic的本意是指屬性的存取方法是線程安全的,并不保證整個對象是線程安全的。
網(wǎng)上常見的關(guān)于atomic非線程安全的舉例:如果線程 A 調(diào)了 getter,與此同時線程 B 、線程 C 都調(diào)了 setter——那最后線程 A get 到的值,有3種可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值。同時,最終這個屬性的值,可能是 B set 的值,也有可能是 C set 的值。所以atomic可并不能保證對象的線程安全。
類似的這個例子相信很多人都見過,看起來也非常合理,沒什么錯;但細(xì)琢磨,這個例子本身沒問題,但并不能證明atomic的非線程安全這個觀點,它只說明了不確定哪個線程先調(diào)用了set。所以面試的時候如果舉這個例子~~說明你沒明白atomic的非線程安全性!
首先你得知道什么是線程不安全?線程的不安全是由于多線程訪問和修改共享資源而引起的不可預(yù)測的結(jié)果(有可能crash)。可以簡單理解為我們拿到的值是錯的。這個例子中,如果線程A getter到的值是個錯誤的值才能說是線程不安全的,可是這個例子就算線程A可能取到好幾種值,你能說取值不對嗎;不能。所以這個例子是個錯誤的例子!*誤導(dǎo)了我好久;下文中我會舉兩個正確的例子。
atomic的原子性和nonatomic的非原子性
- atomic :系統(tǒng)自動生成的getter/setter方法會進(jìn)行加鎖操作;可以理解過讀寫鎖,可以保證讀寫安全;較耗時。
- nonatomic:系統(tǒng)自動生成的getter/setter方法不會進(jìn)行加鎖操作;但速度會更快。
源代碼分析atomic為什么不是線程安全
為什么要把atomic和線程安全聯(lián)系在一起去探究?這個問題本身其實就很奇怪,atomic只是對屬性的getter/setter方法進(jìn)行了加鎖操作,這種安全僅僅是get/set的讀寫安全,僅此而已,但是線程安全還有除了讀寫的其他操作,比如:當(dāng)一個線程正在get/set時,另一個線程同時進(jìn)行release操作,可能會直接crash。很明顯atomic的讀寫鎖不能保證線程安全。 下面兩個例子寫的就挺好,挺簡單:
例子一:如果定義屬性NSInteger i是原子的,對i進(jìn)行i = i + 1操作就是不安全的; 因為原子性只能保證讀寫安全,而該表達(dá)式需要三步操作:
1、讀取i的值存入寄存器;
2、將i加1;
3、修改i的值;
如果在第一步完成的時候,i被其他線程修改了,那么表達(dá)式執(zhí)行的結(jié)果就與預(yù)期的不一樣,也就是不安全的。
例子二:
self.slice = 0; dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ for (int i=0; i<10000; i++) { self.slice = self.slice + 1; } }); dispatch_async(queue, ^{ for (int i=0; i<10000; i++) { self.slice = self.slice + 1; } });
結(jié)果可能是[10000,20000]之間的某個值,而我們想要的結(jié)果是20000;很明顯這個例子就會引起線程隱患,而atomic并不能防止這個問題;所以我們說atomic不是線程安全。
所以要想真正理解atomic的非線程安全性,必須要去官網(wǎng)查找解釋并通過源碼分析才行;在runtime時property的atomic是一個booleau值,是采用spinlock_t鎖去實現(xiàn)的;
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
很明顯atomic屬性的setter/getter方法都被加了spinlock自旋鎖,需要注意的是spinlock已經(jīng)由于存在優(yōu)先級反轉(zhuǎn)問題被棄用并用os_unfair_lock替代。既然被棄用了,這里為什么還在用;原因是進(jìn)入spinlock去看會發(fā)現(xiàn),底層已經(jīng)被os_unfair_lick替換:
using spinlock_t = mutex_tt<LOCKDEBUG>;
class mutex_tt : nocopy_t {
os_unfair_lock mLock;
public:
constexpr mutex_tt() : mLock(OS_UNFAIR_LOCK_INIT) {
lockdebug_remember_mutex(this);
}
constexpr mutex_tt(const fork_unsafe_lock_t unsafe) : mLock(OS_UNFAIR_LOCK_INIT) { }
void lock() {
lockdebug_mutex_lock(this);
os_unfair_lock_lock_with_options_inline
.
.
.