本文首發於 我的博客ios
多線程中的鎖一般分爲互斥鎖和自旋鎖,這篇文章主要向你們介紹一些自旋鎖的原理以及atomic的底層實現。git
⚛維基百科上對自旋鎖的解釋:程序員
自旋鎖 是計算機科學用於多線程同步的一種鎖,線程反覆檢查鎖變量是否可用。因爲線程在這一過程當中保持執行,所以是一種
忙等
(忙碌等待)。一旦獲取了自旋鎖,線程會一直持有該鎖,直至顯式釋放自旋鎖。github獲取、釋放自旋鎖,其實是讀寫自旋鎖的存儲內存或寄存器。所以這種讀寫操做必須是原子的
(atomic)
。一般用text-and-set
原子操做來實現。安全
自旋鎖
的核心就是忙等,嘗試自定義一個自旋鎖以下:markdown
struct spinlock {
int flag;
};
@implementation TPSpinLock {
spinlock _lock;
}
- (instancetype)init {
self = [super init];
if (self) {
_lock = spinlock{0};
}
return self;
}
- (void)lock {
while (test_and_set(&_lock.flag, 1)) {
// wait
}
}
- (void)unlock {
_lock.flag = 0;
}
int test_and_set(int *old_ptr, int _new) {
int old = *old_ptr;
*old_ptr = _new;
return old;
}
@end
複製代碼
如上述代碼,咱們自定義了test_and_set
方法,當線程1
進行lock
操做的時候會傳入flag = 0
,test_and_set
方法返回0
的同時並將flag = 1
,這個時候線程2
執行lock
的時候一直返回1
,那麼就一直執行while(1)
處於等待狀態,直到線程1
執行unlock
將flag = 0
這個時候就打破while
循環,線程2
就能繼續執行並加鎖。多線程
提及自旋鎖,無不聯想到屬性的原子操做,即 atomic
異步
atomic
底層是如何實現的?atomic
絕對安全嗎?帶着這些問題咱們對 atomic
進行探討,咱們來到 objc源碼 處進行查看,atomic
既然是修飾property
的,那麼必然會跟property
的set
和get
方法相關,咱們找到了相關方法的實現:async
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);
}
複製代碼
set
方法atomic
那塊加了判斷,若是是原子性就會進行加鎖和解鎖操做。oop
再看 get
方法:
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);
}
複製代碼
很明顯,也是對原子操做進行加鎖處理。
咱們注意到全部的源碼中針對加鎖的地方都是定義爲spinlock
,也就是自旋鎖,因此一般被人問到咱們atomic
底層是什麼的時候,咱們都回答 自旋鎖
,結合YY大神的再也不安全的OSSpinLock 一文,能夠看出Apple
已經棄用OSSpinLock
了,內部確以下述代碼那樣是用os_unfair_lock
來實現的,探其底層執行lock
和unlock
的實際上是mutex_t
,也就是互斥鎖
。
// property的set方法
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
// atomic中用到的鎖
using spinlock_t = mutex_tt<LOCKDEBUG>;
// mutex_tt 的結構
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
(&mLock, OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION);
}
void unlock() {
lockdebug_mutex_unlock(this);
os_unfair_lock_unlock_inline(&mLock);
}
// 上述lock方法的實現
void
lockdebug_mutex_lock(mutex_t *lock)
{
auto& locks = ownedLocks();
if (hasLock(locks, lock, MUTEX)) {
_objc_fatal("deadlock: relocking mutex");
}
setLock(locks, lock, MUTEX);
}
複製代碼
因此說 atomic
的本質並非自旋鎖,至少當前不是,我查詢了 objc
以前的源碼發現老版本的 atomic
的實現,確實不同:
typedef uintptr_t spin_lock_t;
OBJC_EXTERN void _spin_lock(spin_lock_t *lockp);
OBJC_EXTERN int _spin_lock_try(spin_lock_t *lockp);
OBJC_EXTERN void _spin_unlock(spin_lock_t *lockp);
複製代碼
由此可知:
atomic
原子操做只是對setter
和getter
方法進行加鎖
那麼第二個問題來了:atomic
絕對安全嗎?咱們接着分析,首先看下面的代碼,最終的 number
會是多少?20000
?
@property (atomic, assign) NSInteger number;
- (void)atomicTest {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10000; i ++) {
self.number = self.number + 1;
NSLog(@"A-self.number is %ld",self.number);
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10000; i ++) {
self.number = self.number + 1;
NSLog(@"B-self.number is %ld",self.number);
}
});
}
複製代碼
打印結果:
NO
並非 20000
,這是爲啥呢?咱們的 number
是 atomic
進行加鎖了啊,爲何還會出現線程安全問題。其實答案上文已經有了,只是須要咱們仔細去品,atomic
只是針對 setter
和 getter
方法進行加鎖,上述代碼有兩個異步線程同時執行,若是某個時間 A線程
執行到getter
方法,以後 cpu
當即切換到 線程B
去執行他的get方法那麼這個時候他們進行 +1
的處理並執行setter
方法,那麼兩個線程的 number
就會是同樣的結果,這樣咱們的 +1
就會出現線程安全問題,就會致使咱們的數字出現誤差,那麼咱們找一找打印數字裏是否有重複的:
功夫不負有心人,咱們果真找到了重複的,那麼基於咱們 20000
的循環次數少個百八十的太正常不過了。
自旋鎖
不一樣於互斥鎖
若是訪問的資源被佔用,它會處於 忙等
狀態。自旋鎖因爲一直處於忙等狀態因此他在線程鎖被釋放的時候會當即獲取而不用喚醒,因此其執行效率是很高的,尤爲是在多核的cpu上運行效率很高,可是其忙等的狀態會消耗cpu
的性能,因此其性能比互斥鎖要低不少。atomic
的底層實現,老版本是自旋鎖
,新版本是互斥鎖
。atomic
並非絕對線程安全,它能保證代碼進入 getter
和 setter
方法的時候是安全的,可是並不能保證多線程的訪問狀況下是安全的,一旦出了 getter
和 setter
方法,其線程安全就要由程序員本身來把握,因此 atomic
屬性和線程安全並無必然聯繫。