在多線程一:GCD中我們?cè)敿?xì)了解了GCD
,如果多個(gè)線程同時(shí)占用一塊資源,很可能會(huì)發(fā)生數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問題.所以我們今天了解一下線程同步概念.
-
1:
OSSpinLock
導(dǎo)入#import <libkern/OSAtomic.h>
OSSpinLock
叫做自旋鎖
,等待鎖的線程會(huì)處于忙等 (busy-wait)
狀態(tài),一直占用著CPU資源,相當(dāng)于while(1)
循環(huán);用法如下:
@interface OSSPinTest ()
@property (nonatomic,assign)OSSpinLock moneyLock;
@end
@implementation OSSPinTest
- (instancetype)init{
if (self = [super init]) {
self.moneyLock = OS_SPINLOCK_INIT;
}
return self;
}
//存錢
- (void)saveMoney
//嘗試加鎖,如果需要等待就不加鎖,直接返回false;如果不需要等待就直接加鎖,返回true.
// OSSpinLockTry(&_moneyLock);
//加鎖
OSSpinLockLock(&_moneyLock);
[super saveMoney];
解鎖
OSSpinLockUnlock(&_moneyLock);
}
//取錢
- (void)drawMoney{
//嘗試加鎖,如果需要等待就不加鎖,直接返回false;如果不需要等待就直接加鎖,返回true.
// OSSpinLockTry(&_moneyLock);
//加鎖
[super drawMoney];
//解鎖
OSSpinLockUnlock(&_moneyLock);
}
需要注意的是,這種鎖在iOS10.0后已經(jīng)被棄用了,因?yàn)檫@種鎖可能會(huì)出現(xiàn)優(yōu)先級(jí)反轉(zhuǎn)的問題,如果優(yōu)先級(jí)低的線程搶到了這把鎖,給這把鎖加鎖后,優(yōu)先級(jí)高的線程就會(huì)一直處于等待狀態(tài),會(huì)一直占用CPU資源,優(yōu)先級(jí)低的線程就無法釋放
.
-
2:
os_unfair_lock
導(dǎo)入#import <os/lock.h>
os_unfair_lock
用于取代不安全的OSSpinLock
,從iOS10.0才開始支持.但是和等待OSSpinLock
鎖的線程會(huì)處于忙等狀態(tài)不同的是,等待os_unfair_lock
鎖的線程處于休眠狀態(tài).并非忙等.
@interface OSUnfairLock ()
@property (nonatomic,assign)os_unfair_lock moneyLock;
@property (nonatomic,assign)os_unfair_lock ticketLock;
@end
@implementation OSUnfairLock
- (instancetype)init{
if (self = [super init]) {
self.moneyLock = OS_UNFAIR_LOCK_INIT;
self.ticketLock = OS_UNFAIR_LOCK_INIT;
}
return self;
}
- (void)saveMoney{
os_unfair_lock_lock(&_moneyLock);
[super saveMoney];
os_unfair_lock_unlock(&_moneyLock);
}
- (void)drawMoney{
//嘗試加鎖,如果需要等待就不加鎖,直接返回false;如果不需要等待就直接加鎖,返回true.
// os_unfair_lock_trylock(&_moneyLock);
os_unfair_lock_lock(&_moneyLock);
[super drawMoney];
os_unfair_lock_unlock(&_moneyLock);
}
- (void)ticket{
//嘗試加鎖,如果需要等待就不加鎖,直接返回false;如果不需要等待就直接加鎖,返回true.
// os_unfair_lock_trylock(&_ticketLock);
os_unfair_lock_lock(&_ticketLock);
[super ticket];
os_unfair_lock_unlock(&_ticketLock);
}
- 3:
Mutex
:互斥鎖,等待鎖的線程會(huì)處于休眠狀態(tài).mutex 是跨平臺(tái)的
@interface MutexLock ()
@property (nonatomic,assign)pthread_mutex_t moneyLock;
@property (nonatomic,assign)pthread_mutex_t ticketLock;
@end
@implementation MutexLock
- (instancetype)init{
if (self = [super init]) {
//創(chuàng)建鎖
[self __initMutex:&_moneyLock];
[self __initMutex:&_ticketLock];
}
return self;
}
//創(chuàng)建鎖的屬性 attr
- (void)__initMutex:(pthread_mutex_t *)mux{
//創(chuàng)建屬性
pthread_mutexattr_t muteattr;
pthread_mutexattr_init(&muteattr);
//設(shè)置屬性
pthread_mutexattr_settype(&muteattr, PTHREAD_MUTEX_DEFAULT);
//屬性值有以下幾種
// #define PTHREAD_MUTEX_NORMAL 0 //default
// #define PTHREAD_MUTEX_ERRORCHECK 1 //檢查錯(cuò)誤
// #define PTHREAD_MUTEX_RECURSIVE 2 //遞歸鎖
// #define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
//創(chuàng)建鎖
pthread_mutex_init(mux, &muteattr);
//銷毀屬性
pthread_mutexattr_destroy(&muteattr);
}
- (void)saveMoney{
//嘗試加鎖
// pthread_mutex_trylock(&_moneyLock);
pthread_mutex_lock(&_moneyLock);
[super saveMoney];
pthread_mutex_unlock(&_moneyLock);
}
- (void)drawMoney{
pthread_mutex_lock(&_moneyLock);
[super drawMoney];
pthread_mutex_unlock(&_moneyLock);
}
- (void)ticket{
pthread_mutex_lock(&_moneyLock);
[super ticket];
pthread_mutex_unlock(&_moneyLock);
}
如果是遞歸調(diào)用需要加鎖,可以把屬性設(shè)置為pthread_mutexattr_settype(&muteattr, PTHREAD_MUTEX_RECURSIVE);
遞歸鎖允許同一個(gè)線程對(duì)一把鎖進(jìn)行重復(fù)加鎖.
互斥鎖有一個(gè)更高級(jí)的功能:比如說現(xiàn)在有這么一個(gè)需求,一個(gè)取錢操作和一個(gè)存錢操作,取錢操作必須要等到卡里的余額大于0的時(shí)候才可以進(jìn)行.這種時(shí)候就需要用到Mutex
中的pthread_cond_t
了,先看代碼:
@interface MutexConditionLock ()
@property (nonatomic,assign)int money;//余額
@property (nonatomic,assign)pthread_mutex_t mutex;//互斥鎖
@property (nonatomic,assign)pthread_mutexattr_t attr;//互斥鎖屬性
@property (nonatomic,assign)pthread_cond_t condition;//條件
@end
@implementation MutexConditionLock
- (instancetype)init{
if (self == [super init]) {
//創(chuàng)建鎖所需的屬性
pthread_mutexattr_init(&_attr);
pthread_mutexattr_settype(&_attr, PTHREAD_MUTEX_DEFAULT);
//創(chuàng)建互斥鎖
pthread_mutex_init(&_mutex, &_attr);
//創(chuàng)建條件所需的屬性
pthread_condattr_t condattr;
pthread_condattr_init(&condattr);
//創(chuàng)建等待條件,第二個(gè)參數(shù)可以直接傳 NULL
pthread_cond_init(&_conditaion, &condattr);
}
return self;
}
- (void)otherTest{
[[[NSThread alloc]initWithTarget:self selector:@selector(__drawMoney) object:nil]start];
[[[NSThread alloc]initWithTarget:self selector:@selector(__saveMoney) object:nil]start];
}
//取錢
- (void)__drawMoney{
pthread_mutex_lock(&_mutex);
//如果 余額 為 0,就把鎖解開,并進(jìn)入休眠狀態(tài)等待,等待其他線程喚醒,一旦喚醒后再次加鎖
if (self.money == 0) {
pthread_cond_wait(&_conditaion, &_mutex);
}
self.money -= 50;
NSLog(@"取錢后余額 %d",self.money);
pthread_mutex_unlock(&_mutex);
}
//存錢
- (void)__saveMoney{
pthread_mutex_lock(&_mutex);
sleep(2);
self.money += 100;
NSLog(@"存錢余額 %d",self.money);
//通知喚醒正在休眠等待中的線程
pthread_cond_signal(&_conditaion);
//如果多個(gè)線程都在等待信號(hào)喚醒就需要用到廣播了
// pthread_cond_broadcast(&_conditaion);
pthread_mutex_unlock(&_mutex);
}
- (void)dealloc{
pthread_mutexattr_destroy(&_attr);
pthread_cond_destroy(&_conditaion);
}
運(yùn)行結(jié)果:
2019-12-12 18:08:54.470700+0800 各種lockTest[3159:1068307] 存錢余額 100
2019-12-12 18:08:54.471072+0800 各種lockTest[3159:1068306] 取錢后余額 50
關(guān)鍵代碼就在pthread_cond_wait(pthread_cond_t , pthread_mutex_t )
和pthread_cond_signal(pthread_cond_t *)
這兩句:
-
pthread_cond_wait(pthread_cond_t , pthread_mutex_t )
傳入兩個(gè)參數(shù),第一個(gè)參數(shù)就是等待喚醒的條件
;第二個(gè)參數(shù)是互斥鎖
.
以實(shí)例代碼為例,如果首選進(jìn)入__drawMoney
方法,然后對(duì)_mutex
加鎖,如果self.money == 0
符合條件,執(zhí)行pthread_cond_wait(&_conditaion, &_mutex);
, -
pthread_cond_signal(pthread_cond_t *)
傳入一個(gè)條件,發(fā)送信號(hào)喚醒正在等待這個(gè)條件的線程.
剛才我們說OSSpinLock
線程會(huì)處于忙等狀態(tài)
,我們從匯編代碼看看是不是這樣:
si
(step instruction):是讓匯編代碼一行一行執(zhí)行.
s
(step):是讓OC 代碼一行一行執(zhí)行.
next I
:也是一樣一行往下走,只不過遇到函數(shù)調(diào)用的時(shí)候不會(huì)進(jìn)入函數(shù)內(nèi)部,而si
會(huì)進(jìn)入函數(shù)內(nèi)部.所以要想查看函數(shù)內(nèi)部實(shí)現(xiàn)就要用si
.
從
OSSpinLock
匯編語言看到,底部就是一個(gè)while
循環(huán),一直處于忙等狀態(tài).
再來看看Mutex
互斥鎖的底層匯編:
os_unfair_lock
的底層匯編:
從匯編可以看到
os_unfair_lock
和Mutex
一樣都是讓等待的線程進(jìn)入休眠狀態(tài).另外蘋果官方也說
os_unfair_lock
是一個(gè)Low-level lock
( 低級(jí)鎖 ).低級(jí)鎖的特點(diǎn)就是睡覺.
-
4:
NSLock
:是對(duì)Mutex
普通鎖封裝.只是用起來更加面向?qū)ο?更加方便,主要有4個(gè)方法:
NSLocking
協(xié)議下的lock()
和unlock()
方法以及自身的tryLock()
和lockBeforeDate:
方法.我們只說一下lockBeforeDate:
方法因?yàn)槠渌?個(gè)方法和mutex
功能一樣.
lockBeforeDate :(NSDate*)date
:傳入一個(gè)時(shí)間,表示在這個(gè)時(shí)間之前線程會(huì)一直等待,如果等到別的線程放開這把鎖就對(duì)這把鎖加鎖,并返回yes
;如果在規(guī)定的時(shí)間還是沒有等到這把鎖,就加鎖失敗,返回NO
代碼繼續(xù)往下走.會(huì)阻塞線程. - 5:
NSRecursiveLock
是對(duì)Mutex
遞歸鎖的封裝.API和NSLock
一致. -
6:
NSCodition
是對(duì)mutex
和cond
的封裝.主要有以下API:
- (void)wait;
等待條件喚醒
- (BOOL)waitUntilDate:(NSDate *)limit;
傳入一個(gè)時(shí)間,在這個(gè)時(shí)間之前線程一直休眠等待.時(shí)間到了之后自動(dòng)喚醒.
- (void)signal;
信號(hào)
- (void)broadcast;
廣播
@interface NSConditionTest ()
@property (nonatomic,strong)NSCondition *moneyLock;
@property (nonatomic,assign)int money;//余額
@end
@implementation NSConditionTest
- (instancetype)init{
if (self = [super init]) {
//初始化鎖
self.moneyLock = [[NSCondition alloc]init];
}
return self;
}
- (void)otherTest{
[[[NSThread alloc]initWithTarget:self selector:@selector(__drawMoney) object:nil]start];
[[[NSThread alloc]initWithTarget:self selector:@selector(__saveMoney) object:nil]start];
}
//取錢
- (void)__drawMoney{
[self.moneyLock lock];
//如果 余額 為 0,就把鎖解開,并進(jìn)入休眠狀態(tài)等待,等待其他線程喚醒,一旦喚醒后再次加鎖
if (self.money == 0) {
[self.moneyLock wait];
}
self.money -= 50;
NSLog(@"取錢后余額 %d",self.money);
[self.moneyLock unlock];
}
//存錢
- (void)__saveMoney{
[self.moneyLock lock];
sleep(2);
self.money += 100;
NSLog(@"存錢余額 %d",self.money);
//通知喚醒正在休眠等待中的線程
[self.moneyLock signal];
//如果多個(gè)線程都在等待信號(hào)喚醒就需要用到廣播了
// pthread_cond_broadcast(&_conditaion);
[self.moneyLock unlock];
}
@end
運(yùn)行結(jié)果
2019-12-13 10:25:50.905652+0800 各種lockTest[3621:1439700] 存錢余額 100
2019-12-13 10:25:50.905983+0800 各種lockTest[3621:1439699] 取錢后余額 50
-
7:
NSConditionLock
:是對(duì)NSCondition
的進(jìn)一步封裝,可以設(shè)置具體的條件值.可以控制線程間的執(zhí)行順序.主要API如下:
@property (readonly) NSInteger condition;
條件值
- (void)lockWhenCondition:(NSInteger)condition;
一直等到符合條件后加鎖
- (BOOL)tryLock;
嘗試加鎖,如果鎖已被其他線程加鎖立馬返回NO;如果未被加鎖就加鎖后返回YES.
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
嘗試加鎖,如果鎖被其他線程占用立馬返回NO,否則返回YES.
- (void)unlockWithCondition:(NSInteger)condition;
釋放鎖,并設(shè)置條件值
- (BOOL)lockBeforeDate:(NSDate *)limit;
是在指定Date之前嘗試加鎖庶橱,如果在指定時(shí)間之前都不能加鎖表谊,則返回NO
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
一直等待符合條件值后加鎖.
@interface NSConditionLockDemo ()
@property (nonatomic,strong)NSConditionLock *lock;
@end
@implementation NSConditionLockDemo
- (instancetype)init{
if (self = [super init]) {
//初始化鎖,條件值設(shè)為1
self.lock = [[NSConditionLock alloc]initWithCondition:1];
}
return self;
}
- (void)otherTest{
[[[NSThread alloc]initWithTarget:self selector:@selector(__test1) object:nil]start];
[[[NSThread alloc]initWithTarget:self selector:@selector(__test2) object:nil]start];
}
- (void)__test1{
//如果條件值為1,就加鎖
[self.lock lockWhenCondition:1];
NSLog(@"1");
sleep(2);
//解鎖,并把條件值設(shè)為2
[self.lock unlockWithCondition:2];
}
- (void)__test2{
//如果條件值為2,就加鎖
[self.lock lockWhenCondition:2];
NSLog(@"2");
[self.lock unlock];
}
@end
運(yùn)行結(jié)果
2019-12-13 21:00:55.954512+0800 各種lockTest[4907:2111963] 1
2019-12-13 21:00:57.960116+0800 各種lockTest[4907:2111964] 2
注意如果initWithCondition
創(chuàng)建的時(shí)候條件值沒有設(shè)置或設(shè)置的nil
,condition
默認(rèn)是0;
- 8:
dispatch_semaphore
:可以用來控制線程并發(fā)訪問的最大數(shù)量.
@interface SemaphoreDemo ()
@property (nonatomic,strong)dispatch_semaphore_t semaphore;
@property (nonatomic,strong)dispatch_semaphore_t money_semaphore;
@property (nonatomic,strong)dispatch_semaphore_t ticket_semaphore;
@end
@implementation SemaphoreDemo
- (instancetype)init{
if (self = [super init]) {
//初始化鎖,條件值設(shè)為1
self.semaphore = dispatch_semaphore_create(5);
self.money_semaphore = dispatch_semaphore_create(1);
self.ticket_semaphore = dispatch_semaphore_create(1);
}
return self;
}
- (void)saveMoney{
dispatch_semaphore_wait(self.money_semaphore, DISPATCH_TIME_FOREVER);
[super saveMoney];
dispatch_semaphore_signal(self.money_semaphore);
}
- (void)drawMoney{
dispatch_semaphore_wait(self.money_semaphore, DISPATCH_TIME_FOREVER);
[super drawMoney];
dispatch_semaphore_signal(self.money_semaphore);
}
- (void)ticket{
dispatch_semaphore_wait(self.ticket_semaphore, DISPATCH_TIME_FOREVER);
[super ticket];
dispatch_semaphore_signal(self.ticket_semaphore);
}
- (void)otherTest{
for (int i = 0; i < 30; i ++) {
[[[NSThread alloc]initWithTarget:self selector:@selector(__test1) object:nil]start];
}
}
- (void)__test1{
//信號(hào)量的值 -1 繼續(xù)往下執(zhí)行代碼
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_NOW);
sleep(2);
NSLog(@"1111");
//信號(hào)量的值 +1
dispatch_semaphore_signal(_semaphore);
}
@end
如果設(shè)置semaphore
的初始值為5,就代表線程并發(fā)訪問的最大值是5.他的實(shí)現(xiàn)原理是:如果信號(hào)量的初始值 <= 0,當(dāng)前線程就會(huì)進(jìn)入休眠狀態(tài)等待,直到信號(hào)量的值 > 0;如果信號(hào)量的值 > 0,就先減1,然后往下執(zhí)行代碼.
dispatch_semaphore_signal 會(huì)讓信號(hào)量的值加1
.
所以如果,設(shè)置信號(hào)量的值為1,控制線程的最大并發(fā)數(shù)為1,就可以實(shí)現(xiàn)線程同步
- 9:
dispatch_queue
使用GCD的串行隊(duì)列實(shí)現(xiàn)線程同步.把需要控制的操作都放到一個(gè)串行隊(duì)列中:
@interface SerialQueueDemo ()
@property (nonatomic,strong)dispatch_queue_t serialQueue_money;
@property (nonatomic,strong)dispatch_queue_t serialQueue_ticket;
@end
@implementation SerialQueueDemo
- (instancetype)init{
if (self == [super init]) {
self.serialQueue_money = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
self.serialQueue_ticket = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)saveMoney{
dispatch_sync(self.serialQueue_money, ^{
[super saveMoney];
});
}
- (void)drawMoney{
dispatch_sync(self.serialQueue_money, ^{
[super drawMoney];
});
}
- (void)ticket{
dispatch_sync(self.serialQueue_ticket, ^{
[super ticket];
});
}
@end
- 10:
synchronized
:是對(duì)mutex
遞歸鎖的封裝.這是最簡(jiǎn)潔的方式,但是性能比較差,蘋果不推薦使用.
@implementation SynchronizedDemo
- (void)saveMoney{
//傳入的對(duì)象要保證是同一個(gè)對(duì)象
@synchronized (self) { // objc_sync_enter 相當(dāng)于加鎖
[super saveMoney];
}// objc_sync_exit 相當(dāng)于解鎖
}
- (void)drawMoney{
@synchronized (self) {//加鎖
[super drawMoney];
}//解鎖
}
- (void)ticket{
static NSObject *lock;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
lock = [[NSObject alloc]init];
});
@synchronized (lock) {
[super ticket];
}
}
@end
跟進(jìn)synchronized
的匯編代碼,會(huì)發(fā)現(xiàn)兩個(gè)重要的函數(shù):
在objc-sync.mm
中找到這兩個(gè)函數(shù):
到目前為止我們已經(jīng)講了10中線程同步的方法,那么我們?cè)陧?xiàng)目中應(yīng)該使用哪一種呢:
使用小技巧:
如果有好幾個(gè)方法都需要加不同的鎖,我們可以這樣寫:
- (void)test{
static dispatch_semaphore_t semaphore;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
semaphore = dispatch_semaphore_create(1);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//..
//加鎖操作
//...
dispatch_semaphore_signal(semaphore);
}
也可以把加鎖的代碼寫成宏定義,這樣更方便:
- (void)test{
DispatchSemaphoreBegin
//..
//加鎖操作
//...
DispatchSemaphoreEnd
}
自旋鎖和互斥鎖的比較:
什么情況下使用自旋鎖比較劃算?
- 預(yù)計(jì)線程等待的時(shí)間很短.
如果線程等待的時(shí)間很短,就沒必要讓線程休眠等待,因?yàn)樾菝吆笤賳拘岩矔?huì)消耗資源,降低性能. - 加鎖的代碼(臨界區(qū))經(jīng)常被調(diào)用,但競(jìng)爭(zhēng)情況很少發(fā)生.
臨界區(qū):lock
和unlock
之間的代碼我們稱之為臨界區(qū). - CPU資源不緊張
什么情況下使用互斥鎖比較劃算? - 預(yù)計(jì)線程等待鎖的時(shí)間比較長(zhǎng),比如說2,3s.
- 單核處理器.
- 臨界區(qū)有IO操作.
IO操作的時(shí)間一般比較長(zhǎng),需要更多的CPU資源,而自旋鎖會(huì)一直占用CPU資源,我們應(yīng)該把CPU資源讓出來給IO操作,所以IO操作用互斥鎖比較合適. - 臨界區(qū)代碼比較復(fù)雜或者循環(huán)量大.
- 臨界區(qū)的競(jìng)爭(zhēng)非常激烈.
iOS中實(shí)現(xiàn)多讀單寫
比如說現(xiàn)在有這種需求:
1:同一時(shí)間只能有一個(gè)線程進(jìn)行寫入文件的操作.
2:同一時(shí)間允許多個(gè)線程進(jìn)行讀取文件的操作.
3:同一時(shí)間,不允許讀,寫操作同時(shí)進(jìn)行.
上面的需求就是多讀單寫
操作,iOS中有兩種方式實(shí)現(xiàn)多讀單寫操作:
-
pthread_rwlock
:讀寫鎖
@interface PthreadRWlLockDemo ()
@property (nonatomic,assign)pthread_rwlock_t rwLock;
@end
@implementation PthreadRWlLockDemo
- (instancetype)init{
if (self == [super init]) {
//初始化讀寫鎖
pthread_rwlock_init(&_rwLock, NULL);
}
return self;
}
- (void)otherTest{
for (int i = 0; i < 10; i ++) {
[[[NSThread alloc]initWithTarget:self selector:@selector(read) object:nil]start];
[[[NSThread alloc]initWithTarget:self selector:@selector(write) object:nil]start];
}
}
- (void)read{
//讀操作加鎖
pthread_rwlock_rdlock(&_rwLock);
sleep(1);
NSLog(@"read");
//解鎖
pthread_rwlock_unlock(&_rwLock);
}
- (void)write{
//寫操作加鎖
pthread_rwlock_wrlock(&_rwLock);
sleep(1);
NSLog(@"write");
解鎖
pthread_rwlock_unlock(&_rwLock);
}
@end
-
dispatch_barrier_async
:異步柵欄調(diào)用
@interface BarrierLockDemo ()
@property (nonatomic,strong)dispatch_queue_t queue;
@end
@implementation BarrierLockDemo
- (instancetype)init{
if (self == [super init]) {
//初始化讀寫鎖
self.queue = dispatch_queue_create("readWirteQueue", DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
- (void)otherTest{
for (int i = 0; i < 10; i ++) {
[[[NSThread alloc]initWithTarget:self selector:@selector(read) object:nil]start];
[[[NSThread alloc]initWithTarget:self selector:@selector(write) object:nil]start];
}
}
- (void)read{
dispatch_async(self.queue, ^{
sleep(1);
NSLog(@"read");
});
}
- (void)write{
dispatch_barrier_async(self.queue, ^{
sleep(1);
NSLog(@"write");
});
}
@end
異步柵欄的原理是:把寫入文件
的任務(wù)放到隊(duì)列的時(shí)候,會(huì)給這個(gè)線程建立一個(gè)柵欄,圍欄
,不允許其他的任務(wù)進(jìn)來.如圖:
使用異步柵欄的時(shí)候需要注意:傳入這個(gè)函數(shù)的隊(duì)列( queue )必須是通過
dispatch_queue_create
創(chuàng)建的,如果傳入的是一個(gè)串行或者全局并發(fā)隊(duì)列,那異步柵欄函數(shù)的功能就相當(dāng)于dispatch_asyn
的效果.
-
atomic
:最后說一下atomic
關(guān)鍵字.
atomic
是線程安全的,如果使用這個(gè)關(guān)鍵字修飾屬性,系統(tǒng)會(huì)在屬性的setter
,getter
方法內(nèi)部加上加鎖
和解鎖
的代碼,我們看一下源代碼:
get 方法底層實(shí)現(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;// 如果是 nonatomic 直接返回值
// Atomic retain release world
//如果是 atomic
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);
}
set 方法底層實(shí)現(xiàn)
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) {//如果是 nonatomic
oldValue = *slot; //*slot 屬性的內(nèi)存地址
*slot = newValue;
} else {// atomic
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();//加鎖
oldValue = *slot;
*slot = newValue;
slotlock.unlock();//解鎖
}
objc_release(oldValue);
}
雖然atomic
是線程安全的,但是我們?cè)陧?xiàng)目中還是不會(huì)使用,因?yàn)槲覀儠?huì)非常頻繁的訪問屬性,如果屬性用atomic
修飾,那會(huì)極大的消耗性能.所以我們項(xiàng)目中一般都是用nonatomic
,如果有的屬性的確需要線程同步操作,完全可以哪里需要哪里加鎖.