了解鎖的機制會有助于項目開發(fā),從而避免項目中多個線程訪問同一塊資源引發(fā)數(shù)據(jù)混亂的問題。
一 概念 鎖的歸類
基本的鎖就包括了三類 自旋鎖 互斥鎖 讀寫鎖,其他的比如條件鎖,遞歸鎖,信號量都是上層的封裝和實現(xiàn)。
自旋鎖
自旋鎖:線程反復檢查鎖變量是否可用。由于線程在這一過程中保持執(zhí)行, 因此是一種忙等待。一旦獲取了自旋鎖,線程會一直保持該鎖,直至顯式釋 放自旋鎖。 自旋鎖避免了進程上下文的調(diào)度開銷,因此對于線程只會阻塞很 短時間的場合是有效的。
互斥鎖
互斥鎖:是一種用于多線程編程中,防止兩條線程同時對同一公共資源(比 如全局變量)進行讀寫的機制。該目的通過將代碼切片成一個一個的臨界區(qū) 而達成
這里屬于互斥鎖的有:
NSLock
pthread_mutex
@synchronized
條件鎖
條件鎖:就是條件變量,當進程的某些資源要求不滿足時就進入休眠,也就
是鎖住了。當資源被分配到了,條件鎖打開,進程繼續(xù)運行
NSCondition
NSConditionLock
遞歸鎖
遞歸鎖:就是同一個線程可以加鎖N次而不會引發(fā)死鎖
NSRecursiveLock
pthread_mutex(recursive)
信號量
信號量(semaphore):是一種更高級的同步機制,互斥鎖可以說是
semaphore在僅取值0/1時的特例。信號量可以有更多的取值空間,用來實
現(xiàn)更加復雜的同步,而不單單是線程間互斥。
dispatch_semaphore
讀寫鎖
讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。這種鎖相對于自旋鎖而言,能提高并發(fā)性,因為在多處理器系統(tǒng)中,它允許同時有多個讀者來訪問共享資源,最大可能的讀者數(shù)為實際的邏輯CPU 數(shù)。寫者是排他性的,一個讀寫鎖同時只能有一個寫者或多個讀者(與CPU數(shù)相關),但不能同時既有讀者又有寫者。在讀寫鎖保持期間也是搶占失效的。
如果讀寫鎖當前沒有讀者,也沒有寫者,那么寫者可以立刻獲得讀寫鎖,否則它必須自旋在那里,直到?jīng)]有任何寫者或讀者。如果讀寫鎖沒有寫者,那么讀者可以立即獲得該讀寫鎖,否則讀者必須
自旋在那里,直到寫者釋放該讀寫鎖。一次只有一個線程可以占有寫模式的讀寫鎖, 但是可以有多個線程同時占有讀模式的讀寫鎖. 正
是因為這個特性,當讀寫鎖是寫加鎖狀態(tài)時, 在這個鎖被解鎖之前, 所有試圖對這個鎖加鎖的線程都會被阻塞.當讀寫鎖在讀加鎖狀態(tài)時, 所有試圖以讀模式對它進行加鎖的線程都可以得到訪問權, 但是如果
線程希望以寫模式對此鎖進行加鎖, 它必須直到所有的線程釋放鎖.
通常, 當讀寫鎖處于讀模式鎖住狀態(tài)時, 如果有另外線程試圖以寫模式加鎖, 讀寫鎖通常會阻塞隨后的讀模式鎖請求, 這樣可以避免讀模式鎖?期占用, 而等待的寫模式鎖請求?期阻塞.讀寫鎖適合于對數(shù)據(jù)結構的讀次數(shù)比寫次數(shù)多得多的情況. 因為, 讀模式鎖定時可以共享, 以寫
模式鎖住時意味著獨占, 所以讀寫鎖又叫共享-獨占鎖.讀寫鎖適合于對數(shù)據(jù)結構的讀次數(shù)比寫次數(shù)多得多的情況. 因為, 讀模式鎖定時可以共享, 以寫模式鎖住時意味著獨占, 所以讀寫鎖又叫共享-獨占鎖.
鎖的性能數(shù)據(jù)

二 @synchronized
@synchronized 是一種遞歸互斥鎖。
應用如下
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.ticketCount = 20;
[self lg_testSaleTicket];
}
- (void)lg_testSaleTicket{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 3; i++) {
[self saleTicket];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10; i++) {
[self saleTicket];
}
});
}
- (void)saleTicket{
// 枷鎖 - 線程安全
@synchronized (self) {
if (self.ticketCount > 0) {
self.ticketCount--;
sleep(0.1);
NSLog(@"當前余票還剩:%ld張",self.ticketCount);
}else{
NSLog(@"當前車票已售罄");
}
}
}
下面我們來分析下它的底層實現(xiàn),通過在@synchronized (self) 打斷點,顯示匯編,可以看到底層調(diào)用了objc_sync_enter 和 objc_sync_exit方法,打符號斷點看到方法是在libobjc.dylib庫里面,也就是objc的源碼,我們打開源碼繼續(xù)分析。

搜索objc_sync_enter方法,具體實現(xiàn)如下:
// Begin synchronizing on 'obj'.
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
assert(data);
data->mutex.lock();
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
return result;
}
這里可以發(fā)現(xiàn)如果對象不存在就會走方法objc_sync_nil(),這里底層沒有實現(xiàn),也就是什么事情都不做,所以有時候會出現(xiàn)加鎖了但是沒用,因為對象被釋放了,這里需要特別注意。
BREAKPOINT_FUNCTION(
void objc_sync_nil(void)
);
這里我們看下SyncData的結構,父類里面有子類,是一個指針結點,發(fā)現(xiàn)是一個鏈表結構
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex;
} SyncData;
遞歸鎖與objc_sync_nil搭配的使用,可以防止死鎖現(xiàn)象發(fā)生。
繼續(xù)往下看方法id2data
static SyncData* id2data(id object, enum usage why)
{
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
SyncData **listp = &LIST_FOR_OBJ(object);
SyncData* result = NULL;
// @syn - vc model view 任何地方都可以直接使用 - 全局
// data 存儲節(jié)點
// map - obj - 表
#if SUPPORT_DIRECT_THREAD_KEYS
// Check per-thread single-entry fast cache for matching object
// 檢查每線程單項快速緩存中是否有匹配的對象
bool fastCacheOccupied = NO;
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
fastCacheOccupied = YES;
if (data->object == object) {
// Found a match in fast cache.
uintptr_t lockCount;
result = data;
lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
if (result->threadCount <= 0 || lockCount <= 0) {
_objc_fatal("id2data fastcache is buggy");
}
switch(why) {
case ACQUIRE: {
lockCount++;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
break;
}
case RELEASE:
lockCount--;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
if (lockCount == 0) {
// remove from fast cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
#endif
// Check per-thread cache of already-owned locks for matching object
// 檢查已擁有鎖的每個線程高速緩存中是否有匹配的對象
SyncCache *cache = fetch_cache(NO);
if (cache) {
unsigned int I;
for (i = 0; i < cache->used; i++) {
SyncCacheItem *item = &cache->list[I];
if (item->data->object != object) continue;
// Found a match.
result = item->data;
if (result->threadCount <= 0 || item->lockCount <= 0) {
_objc_fatal("id2data cache is buggy");
}
switch(why) {
case ACQUIRE:
item->lockCount++;
break;
case RELEASE:
item->lockCount--;
if (item->lockCount == 0) {
// remove from per-thread cache
cache->list[i] = cache->list[--cache->used];
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
// Thread cache didn't find anything.
// Walk in-use list looking for matching object
// Spinlock prevents multiple threads from creating multiple
// locks for the same new object.
// We could keep the nodes in some hash table if we find that there are
// more than 20 or so distinct locks active, but we don't do that now.
lockp->lock();
{
SyncData* p;
SyncData* firstUnused = NULL;
for (p = *listp; p != NULL; p = p->nextData) {
if ( p->object == object ) {
result = p;
// atomic because may collide with concurrent RELEASE
OSAtomicIncrement32Barrier(&result->threadCount);
goto done;
}
if ( (firstUnused == NULL) && (p->threadCount == 0) )
firstUnused = p;
}
// no SyncData currently associated with object
if ( (why == RELEASE) || (why == CHECK) )
goto done;
// an unused one was found, use it
if ( firstUnused != NULL ) {
result = firstUnused;
result->object = (objc_object *)object;
result->threadCount = 1;
goto done;
}
}
// Allocate a new SyncData and add to list.
// XXX allocating memory with a global lock held is bad practice,
// might be worth releasing the lock, allocating, and searching again.
// But since we never free these guys we won't be stuck in allocation very often.
posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
result->object = (objc_object *)object;
result->threadCount = 1;
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
result->nextData = *listp;
*listp = result;
done:
lockp->unlock();
if (result) {
// Only new ACQUIRE should get here.
// All RELEASE and CHECK and recursive ACQUIRE are
// handled by the per-thread caches above.
if (why == RELEASE) {
// Probably some thread is incorrectly exiting
// while the object is held by another thread.
return nil;
}
if (why != ACQUIRE) _objc_fatal("id2data is buggy");
if (result->object != object) _objc_fatal("id2data is buggy");
#if SUPPORT_DIRECT_THREAD_KEYS
if (!fastCacheOccupied) {
// Save in fast thread cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
} else
#endif
{
// Save in thread cache
if (!cache) cache = fetch_cache(YES);
cache->list[cache->used].data = result;
cache->list[cache->used].lockCount = 1;
cache->used++;
}
}
return result;
}
看第一句代碼里LOCK_FOR_OBJ,是一個宏,sDataLists的真實類型是SyncList,所以我們猜測StripedMap應該有個全局的哈希表來通過obj得到一個index,然后存儲SyncList,而SyncList存儲的都是一個個的節(jié)點SyncData。
#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
#define LIST_FOR_OBJ(obj) sDataLists[obj].data
static StripedMap<SyncList> sDataLists;
struct SyncList {
SyncData *data;
spinlock_t lock;
constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
};

總結:@synchronized使用簡單,但是性能不高,因為底層是對哈希表進行增刪改查,有漏洞。
@synchronized的注意點
我們先看一段代碼
for (int i = 0; i < 200000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
_testArray = [NSMutableArray array];
});
}
這里不斷創(chuàng)建線程創(chuàng)建訪問對象,會造成可變數(shù)組的不安全性,運行代碼,crash掉,如下所示:

創(chuàng)建的過程是產(chǎn)生一個new value,old value release掉的過程,會有一瞬間_testArray為nil,我們可以通過加鎖解決,這里如果使用@synchronized的話,不會產(chǎn)生作用,因為當對象為空時,鎖不起作用。
for (int i = 0; i < 200000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@synchronized (_testArray) {
_testArray = [NSMutableArray array];
}
});
}
這時候可以使用NSLock來解決。
三 NSLock
NSLock是「非」遞歸互斥鎖,具體使用如下:
NSLock *lock = [[NSLock alloc] init];
for (int i = 0; i < 200000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[lock lock];
// old value ->
_testArray = [NSMutableArray array];
[lock unlock];
});
}
點進去發(fā)現(xiàn)NSLock在Foundation庫里面

NSLock 是一種互斥鎖,也是非遞歸的互斥鎖,所以在遞歸時,會造成線程的堵塞。
這里我們看一個例子,這里遞歸調(diào)用,使用互斥鎖NSLock,運行發(fā)現(xiàn)結果只打印10,產(chǎn)生了堵塞,所以遞歸調(diào)用這里不能使用互斥鎖,應該使用遞歸鎖NSRecursiveLock
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
[lock lock];
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
[lock unlock];
};
testMethod(10);
});
運行結果
2020-11-02 15:34:40.854430+0800 002-@synchronized注意點[3938:109845] current value = 10
四 NSRecursiveLock
修改上面例子,代碼如下:
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
[lock lock];
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
[lock unlock];
};
testMethod(10);
});
運行結果正常

如果我們給上面的代碼加一個for循環(huán),就會造成一個死鎖現(xiàn)象
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
for (int i= 0; i<100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
[lock lock];
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
[lock unlock];
};
testMethod(10);
});
}

這里改為@synchronized就能解決死鎖,因為 @synchronized 鎖的是同一個對象,當下次再來的時候只會從 cache 里面取,而不會像 NSRecursiveLock 當線程 1 鎖了一次之后,線程 2 來了還會再鎖一次,所以造成了線程 1 等線程 2 解鎖,線程 2 等線程 1 解鎖的死鎖狀況。
for (int i= 0; i<100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
@synchronized (self) {
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
}
};
testMethod(10);
});
}
總結
普通線程安全使用NSLock
遞歸調(diào)用線程安全使用NSRecursiveLock
產(chǎn)生循環(huán),受外界線程影響的時候要特別注意死鎖,就算損失一點點性能,也要保證安全
四 NSCondition
NSCondition 的對象實際上作為一個鎖和一個線程檢查器:鎖主要 為了當檢測條件時保護數(shù)據(jù)源,執(zhí)行條件引發(fā)的任務;線程檢查器 主要是根據(jù)條件決定是否繼續(xù)運行線程,即線程是否被阻塞。
1:[condition lock];//一般用于多線程同時訪問、修改同一個數(shù)據(jù)源,保證在同一 時間內(nèi)數(shù)據(jù)源只被訪問、修改一次,其他線程的命令需要在lock 外等待,只到 unlock ,才可訪問
2:[condition unlock];//與lock 同時使用
3:[condition wait];//讓當前線程處于等待狀態(tài)
4:[condition signal];//CPU發(fā)信號告訴線程不用在等待,可以繼續(xù)執(zhí)行
NSCondition 使用場景更適用于生產(chǎn)消費者模式,當你生產(chǎn)大于 0 的時候,我可以進行消費,如果一直等于 0 的時候,我就一直等你生產(chǎn),例子如下:
@interface ViewController ()
@property (nonatomic, assign) NSUInteger ticketCount;
@property (nonatomic, strong) NSCondition *testCondition;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.ticketCount = 0;
[self lg_testConditonLock];
}
#pragma mark -- NSCondition
- (void)lg_testConditon{
_testCondition = [[NSCondition alloc] init];
//創(chuàng)建生產(chǎn)-消費者
for (int i = 0; i < 50; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_producer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_producer];
});
}
}
- (void)lg_producer{
[_testCondition lock];
self.ticketCount = self.ticketCount + 1;
NSLog(@"生產(chǎn)一個 現(xiàn)有 count %zd",self.ticketCount);
[_testCondition signal];
[_testCondition unlock];
}
- (void)lg_consumer{
// 線程安全
[_testCondition lock];
while (self.ticketCount == 0) {
NSLog(@"等待 count %zd",self.ticketCount);
// 保證正常流程
[_testCondition wait];
}
//注意消費行為,要在等待條件判斷之后
self.ticketCount -= 1;
NSLog(@"消費一個 還剩 count %zd ",self.ticketCount);
[_testCondition unlock];
}
五 NSConditionLock
NSConditionLock 是對 NSCondition 的進一步封裝,可以設置具體的條件值對象。NSConditionLock 可以確保只有在滿足特定條件時,線程才能獲得鎖。
1.1 NSConditionLock 是鎖,一旦一個線程獲得鎖,其他線程一定等待
1.2 [xxxx lock]; 表示 xxx 期待獲得鎖,如果沒有其他線程獲得鎖(不需要判斷內(nèi)部的 condition) 那它能執(zhí)行此行以下代碼,如果已經(jīng)有其他線程獲得鎖(可能是條件鎖,或者無條件 鎖),則等待,直至其他線程解鎖
1.3 [xxx lockWhenCondition:A條件]; 表示如果沒有其他線程獲得該鎖,但是該鎖內(nèi)部的 condition不等于A條件,它依然不能獲得鎖,仍然等待。如果內(nèi)部的condition等于A條件,并 且沒有其他線程獲得該鎖,則進入代碼區(qū),同時設置它獲得該鎖,其他任何線程都將等待它代碼 的完成,直至它解鎖。
1.4 [xxx unlockWithCondition:A條件]; 表示釋放鎖,同時把內(nèi)部的condition設置為A條件
1.5 return = [xxx lockWhenCondition:A條件 beforeDate:A時間]; 表示如果被鎖定(沒獲得 鎖),并超過該時間則不再阻塞線程。但是注意:返回的值是NO,它沒有改變鎖的狀態(tài),這個函 數(shù)的目的在于可以實現(xiàn)兩種狀態(tài)下的處理
1.6 所謂的condition就是整數(shù),內(nèi)部通過整數(shù)比較條件
具體使用如下:
#pragma mark -- NSConditionLock
- (void)lg_testConditonLock{
// 信號量
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[conditionLock lockWhenCondition:1];
NSLog(@"線程 1");
[conditionLock unlockWithCondition:0];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[conditionLock lockWhenCondition:2];
NSLog(@"線程 2");
[conditionLock unlockWithCondition:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock];
NSLog(@"線程 3");
[conditionLock unlock];
});
}
打印結果是321。
線程 1 調(diào)用[NSConditionLock lockWhenCondition:],此時此刻因為不滿足當前條件,所
以會進入 waiting 狀態(tài),當前進入到 waiting 時,會釋放當前的互斥鎖。
此時當前的線程 3 調(diào)用[NSConditionLock lock:],本質(zhì)上是調(diào)用 [NSConditionLock
lockBeforeDate:],這里不需要比對條件值,所以線程 3 會打印
接下來線程 2 執(zhí)行[NSConditionLock lockWhenCondition:],因為滿足條件值,所以線程
2 會打印,打印完成后會調(diào)用[NSConditionLock unlockWithCondition:],這個時候講
value 設置為 1,并發(fā)送 boradcast, 此時線程 1 接收到當前的信號,喚醒執(zhí)行并打印。
自此當前打印為 線程 3->線程 2 -> 線程 1。
[NSConditionLock lockWhenCondition:]:這里會根據(jù)傳入的 condition 值和 Value 值進
行對比,如果不相等,這里就會阻塞,進入線程池,否則的話就繼續(xù)代碼執(zhí)行
[NSConditionLock unlockWithCondition:]: 這里會先更改當前的 value 值,然后進行廣
播,喚醒當前的線程。
六 atomic
atomic原理
所有為屬性賦值的方法在底層調(diào)用的是reallySetProperty方法,在源碼里全局搜索,可以看到原子性和非原子性調(diào)用方法的區(qū)別
void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}
void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}
主要區(qū)別是第五個參數(shù)
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);
}
取值的時候在底層會調(diào)用objc_getProperty方法,全局搜索:
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);
}
所以atomic的本質(zhì)是對setter和getter(reallySetProperty,objc_getProperty)方法分別加了一把鎖,生成了原子性的get,set方法。
注意點
atomic修飾屬性加的鎖,僅僅是在setter和getter內(nèi)部加的鎖,保證取值/賦值時的線程安全性,但是如果像下面這樣使用并不能保證線程安全。
例子一
@property (atomic, strong) NSArray *array;
- (void)lg_test_atomic2{
//Thread A
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 100000; i ++) {
if (i % 2 == 0) {
self.array = @[@"Hank", @"CC", @"Cooci"];
}
else {
self.array = @[@"Kody"];
}
NSLog(@"Thread A: %@\n", self.array);
}
});
//Thread B
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 100000; i ++) {
if (self.array.count >= 2) {
NSString* str = [self.array objectAtIndex:1];
}
NSLog(@"Thread B: %@\n",self.array);
}
});
}
運行代碼崩潰
看第二個例子:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10000; i ++) {
self.num = self.num + 1;
NSLog(@"Thread A: %@--%ld", NSThread.currentThread,(long)self.num);
}
});
//Thread B
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10000; i ++) {
self.num = self.num + 1;
NSLog(@"Thread A: %@--%ld", NSThread.currentThread,(long)self.num);
}
});
理想結果應該是20000,運行代碼,結果為19833,證明在某個時刻兩個線程訪問了一個數(shù)據(jù),重復賦值,self.num的setter方法和getter方法是線程安全的,但是self.num + 1這句代碼不是線程安全的,不受保護。
總而言之,atomic只能保證代碼進入getter或者setter函數(shù)內(nèi)部時是安全的,一旦出了getter和setter,多線程安全只能靠程序員自己保障了。所以atomic屬性和使用property的多線程安全并沒什么直接的聯(lián)系。
平時不建議使用atomic,會消耗更多的資源,性能會很低,要比nonatomic慢20倍。
七 自定義讀寫鎖
我們可以創(chuàng)建一個并發(fā)隊列,對于讀操作,我們可以用同步函數(shù)進行處理,對于寫操作我們可以用異步柵欄函數(shù)這操作,這樣就簡單實現(xiàn)了一個讀寫鎖
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGRWLock : NSObject
// 讀數(shù)據(jù)
- (id)lg_objectForKey:(NSString *)key;
// 寫數(shù)據(jù)
- (void)lg_setObject:(id)obj forKey:(NSString *)key;
@end
@interface LGRWLock ()
// 定義一個并發(fā)隊列:
@property (nonatomic, strong) dispatch_queue_t concurrent_queue;
// 用戶數(shù)據(jù)中心, 可能多個線程需要數(shù)據(jù)訪問:
@property (nonatomic, strong) NSMutableDictionary *dataCenterDic;
@end
@implementation LGRWLock
- (id)init{
self = [super init];
if (self){
// 創(chuàng)建一個并發(fā)隊列:
self.concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
// 創(chuàng)建數(shù)據(jù)字典:
self.dataCenterDic = [NSMutableDictionary dictionary];
}
return self;
}
#pragma mark - 讀數(shù)據(jù)
- (id)lg_objectForKey:(NSString *)key{
__block id obj;
// 同步讀取指定數(shù)據(jù):
dispatch_sync(self.concurrent_queue, ^{
obj = [self.dataCenterDic objectForKey:key];
});
return obj;
}
#pragma mark - 寫數(shù)據(jù)
- (void)lg_setObject:(id)obj forKey:(NSString *)key{
// 異步柵欄調(diào)用設置數(shù)據(jù):
dispatch_barrier_async(self.concurrent_queue, ^{
[self.dataCenterDic setObject:obj forKey:key];
});
}
@end