多線程二:線程同步,OC中的各種鎖

多線程一: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 匯編

OSSpinLock匯編語言看到,底部就是一個(gè)while循環(huán),一直處于忙等狀態(tài).

再來看看Mutex互斥鎖的底層匯編:

Mutex 匯編

os_unfair_lock的底層匯編:

os_unfair_lock 匯編

從匯編可以看到os_unfair_lockMutex一樣都是讓等待的線程進(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ì)mutexcond的封裝.主要有以下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ù):

synchronized 底層

objc-sync.mm中找到這兩個(gè)函數(shù):

enter exit 函數(shù)

到目前為止我們已經(jīng)講了10中線程同步的方法,那么我們?cè)陧?xiàng)目中應(yīng)該使用哪一種呢:
效率 僅供產(chǎn)考

使用小技巧:
如果有好幾個(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
}

自旋鎖和互斥鎖的比較:
什么情況下使用自旋鎖比較劃算?

  1. 預(yù)計(jì)線程等待的時(shí)間很短.
    如果線程等待的時(shí)間很短,就沒必要讓線程休眠等待,因?yàn)樾菝吆笤賳拘岩矔?huì)消耗資源,降低性能.
  2. 加鎖的代碼(臨界區(qū))經(jīng)常被調(diào)用,但競(jìng)爭(zhēng)情況很少發(fā)生.
    臨界區(qū):lockunlock之間的代碼我們稱之為臨界區(qū).
  3. CPU資源不緊張
    什么情況下使用互斥鎖比較劃算?
  4. 預(yù)計(jì)線程等待鎖的時(shí)間比較長(zhǎng),比如說2,3s.
  5. 單核處理器.
  6. 臨界區(qū)有IO操作.
    IO操作的時(shí)間一般比較長(zhǎng),需要更多的CPU資源,而自旋鎖會(huì)一直占用CPU資源,我們應(yīng)該把CPU資源讓出來給IO操作,所以IO操作用互斥鎖比較合適.
  7. 臨界區(qū)代碼比較復(fù)雜或者循環(huán)量大.
  8. 臨界區(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)多讀單寫操作:

  1. 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
  1. 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,如果有的屬性的確需要線程同步操作,完全可以哪里需要哪里加鎖.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末地啰,一起剝皮案震驚了整個(gè)濱河市腺毫,隨后出現(xiàn)的幾起案子铺根,更是在濱河造成了極大的恐慌,老刑警劉巖推姻,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件踩寇,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡晋修,警方通過查閱死者的電腦和手機(jī)吧碾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來墓卦,“玉大人倦春,你說我怎么就攤上這事÷浼簦” “怎么了睁本?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)忠怖。 經(jīng)常有香客問我添履,道長(zhǎng),這世上最難降的妖魔是什么脑又? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮锐借,結(jié)果婚禮上问麸,老公的妹妹穿的比我還像新娘。我一直安慰自己钞翔,他們只是感情好严卖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著布轿,像睡著了一般哮笆。 火紅的嫁衣襯著肌膚如雪来颤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天稠肘,我揣著相機(jī)與錄音福铅,去河邊找鬼。 笑死项阴,一個(gè)胖子當(dāng)著我的面吹牛滑黔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播环揽,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼略荡,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了歉胶?” 一聲冷哼從身側(cè)響起汛兜,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎通今,沒想到半個(gè)月后粥谬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡衡创,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年帝嗡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片璃氢。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哟玷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出一也,到底是詐尸還是另有隱情巢寡,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布椰苟,位于F島的核電站抑月,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏舆蝴。R本人自食惡果不足惜谦絮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望洁仗。 院中可真熱鬧层皱,春花似錦、人聲如沸赠潦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽她奥。三九已至瓮增,卻和暖如春怎棱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背绷跑。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工拳恋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人你踩。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓诅岩,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親带膜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吩谦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Q:為什么出現(xiàn)多線程? A:為了實(shí)現(xiàn)同時(shí)干多件事的需求(并發(fā))膝藕,同時(shí)進(jìn)行著下載和頁面UI刷新式廷。對(duì)于處理器,為每個(gè)線...
    幸福相依閱讀 1,578評(píng)論 0 2
  • 前提簡(jiǎn)述: 常用的線程方案有Pthread芭挽,NSThread, GCD,NSOperation滑废。以下是比較:pth...
    小小小蚍蜉閱讀 2,731評(píng)論 0 7
  • 目錄:1.為什么要線程安全2.多線程安全隱患分析3.多線程安全隱患的解決方案4.鎖的分類-13種鎖4.1.1OSS...
    二斤寂寞閱讀 1,184評(píng)論 0 3
  • 線程安全是怎么產(chǎn)生的 常見比如線程內(nèi)操作了一個(gè)線程外的非線程安全變量,這個(gè)時(shí)候一定要考慮線程安全和同步袜爪。 - (v...
    幽城88閱讀 663評(píng)論 0 0
  • 還有12天,身體出現(xiàn)了不適昙篙±白矗可能是前段時(shí)間強(qiáng)度太大,現(xiàn)在身體特別疲憊苔可,有要感冒的跡象缴挖。頭一直暈暈的,心里厭跑情緒蔓...
    南九條7號(hào)閱讀 147評(píng)論 0 0