iOS-多線程02-多線程安全隱患及解決方案(常用鎖)

上一篇文章裹驰,我們大致了解了刷晋,多線程的基本知識(shí)和GCD的基本使用,這篇文章矢否,我們主要來看看多線程的隱患和解決方案仲闽。
一般在我們項(xiàng)目中,可能會(huì)有這種情況:一塊資源可能會(huì)被多個(gè)線程共享僵朗,也就是多個(gè)線程可能會(huì)訪問同一塊資源赖欣;或者說是多個(gè)線程訪問同一個(gè)對象、變量验庙、文件等等顶吮。這時(shí)候,如果不采取一定的措施粪薛,很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全的問題悴了。就像我們以前很早就聽說過的賣票的例子,如果在賣票過程中不去做相應(yīng)的限制违寿,這樣如果幾個(gè)窗口同一時(shí)間賣票湃交,這樣在原來的票數(shù)的基礎(chǔ)上扣減的票數(shù)就會(huì)出現(xiàn)問題,就好像下面這張圖:


image.png

所以這個(gè)時(shí)候藤巢,我們需要去解決這個(gè)問題搞莺。類似的例子還有像在銀行存錢取錢的操作。如果不做限制掂咒,同一時(shí)間存錢取錢才沧,也必然會(huì)出現(xiàn)問題,如下圖:


image.png

這個(gè)問題的關(guān)鍵點(diǎn)就是绍刮,同一時(shí)間温圆,做了不同的操作,每個(gè)操作對原來數(shù)據(jù)都產(chǎn)生了影響孩革,幾個(gè)影響沒有依次進(jìn)行岁歉,導(dǎo)致出現(xiàn)數(shù)據(jù)錯(cuò)亂。
舉個(gè)例子嫉戚,銀行賬目上原先有1000刨裆,在取錢500的同時(shí)澈圈,又存錢500彬檀,這時(shí)候大家第一時(shí)間就是覺得帆啃,還剩余1000,這只是正城系郏現(xiàn)象努潘,如果存取操作同時(shí)進(jìn)行的話,因?yàn)榛A(chǔ)都是1000坤学,所以疯坤,取完錢剩余500,存完錢剩余1500深浮,這兩個(gè)數(shù)據(jù)任何一個(gè)放回賬目上都是不正確的压怠。這就是多條線程同時(shí)操作一個(gè)對象所引發(fā)的問題。
這個(gè)飞苇,我們解決問題的方案就是:使用線程同步技術(shù)(同步菌瘫,就是協(xié)同步調(diào),按預(yù)定的先后次序進(jìn)行)布卡。而在iOS領(lǐng)域中雨让,常見的線程同步技術(shù)就是:加鎖。

先來看看我們iOS線程同步技術(shù)的一些方案忿等,也就是我們接下來要分析的方案:

OSSpinLock
os_unfair_lock
pthread_mutex
dispatch_semaphore
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSRecursiveLock
NSCondition
NSConditionLock
@synchronized

第一種OSSpinLock栖忠,如果了解這個(gè)鎖的同學(xué),應(yīng)該知道贸街,這個(gè)方案已經(jīng)被蘋果廢棄庵寞,當(dāng)然,現(xiàn)在仍然可用薛匪,只是蘋果不推薦使用皇帮。不過,我們還是要來看看這個(gè)鎖的使用:

首先蛋辈,創(chuàng)建一個(gè)base對象BLBaseDemo属拾,里面封裝了賣票和存取錢的操作,而且是多線程異步操作冷溶,基本代碼如下:
/**
 存錢渐白、取錢演示
 */
- (void)moneyTest
{
    self.money = 100;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self __saveMoney];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self __drawMoney];
        }
    });
}

/**
 存錢
 */
- (void)__saveMoney
{
    int oldMoney = self.money;
    sleep(.2);
    oldMoney += 50;
    self.money = oldMoney;
    
    NSLog(@"存50,還剩%d元 - %@", oldMoney, [NSThread currentThread]);
}

/**
 取錢
 */
- (void)__drawMoney
{
    int oldMoney = self.money;
    sleep(.2);
    oldMoney -= 20;
    self.money = oldMoney;
    
    NSLog(@"取20逞频,還剩%d元 - %@", oldMoney, [NSThread currentThread]);
}

/**
 賣票演示
 */
- (void)ticketTest
{
    self.ticketsCount = 15;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self __saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self __saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self __saleTicket];
        }
    });
}

/**
 賣1張票
 */
- (void)__saleTicket
{
    int oldTicketsCount = self.ticketsCount;
    sleep(.2);
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    NSLog(@"還剩%d張票 - %@", oldTicketsCount, [NSThread currentThread]);
}
如果直接調(diào)用實(shí)例對象的方法纯衍,如下:
BLBaseDemo *demo = [[BLBaseDemo alloc] init];
[demo moneyTest];
[demo ticketTest];
在正常流程下,最后的結(jié)果是錢剩余400苗胀,票剩余0
但是實(shí)際打印最后的結(jié)果:
2019-09-05 10:56:59.051802+0800 002-多線程加鎖方案[60823:35399754] 存50襟诸,還剩350元 - <NSThread: 0x6000018dc0c0>{number = 3, name = (null)}
2019-09-05 11:12:48.618159+0800 002-多線程加鎖方案[61082:35418439] 還剩2張票 - <NSThread: 0x600000ae4d40>{number = 3, name = (null)}
可以看到瓦堵,錢和票的結(jié)果都是錯(cuò)了,這就是多線程同事操作同一個(gè)對象或者方法造成的隱患歌亲。

接下來菇用,我們用OSSpinLock加鎖方法來解決問題:

首先,我們創(chuàng)建一個(gè)子類BLOSSpinLockDemo陷揪,繼承BLBaseDemo,然后定義兩個(gè)鎖惋鸥,票鎖和錢鎖,初始化對象的同時(shí)對它們進(jìn)行初始化悍缠。然后重寫賣票和存取錢的方法卦绣,在方法中加鎖解鎖:

@interface OSSpinLockDemo()
@property (assign, nonatomic) OSSpinLock moneyLock;
@property (assign, nonatomic) OSSpinLock ticketLock;
@end
- (instancetype)init
{
    if (self = [super init]) {
        self.moneyLock = OS_SPINLOCK_INIT;
        self.ticketLock = OS_SPINLOCK_INIT;
    }
    return self;
}

- (void)__drawMoney
{
    OSSpinLockLock(&_moneyLock);
    
    [super __drawMoney];
    
    OSSpinLockUnlock(&_moneyLock);
}

- (void)__saveMoney
{
    OSSpinLockLock(&_moneyLock);
    
    [super __saveMoney];
    
    OSSpinLockUnlock(&_moneyLock);
}

- (void)__saleTicket
{
    OSSpinLockLock(&_ticketLock);
    
    [super __saleTicket];
    
    OSSpinLockUnlock(&_ticketLock);
}
這樣我們調(diào)用實(shí)例對象的方法
BLBaseDemo *demo = [[BLOSSpinLockDemo alloc] init];
[demo moneyTest];
[demo ticketTest];
所打印結(jié)果如下:
2019-09-05 11:17:10.600189+0800 002-多線程加鎖方案[61172:35424465] 取20,還剩400元 - <NSThread: 0x600002b37280>{number = 7, name = (null)}
2019-09-05 11:17:10.597382+0800 002-多線程加鎖方案[61172:35424478] 還剩0張票 - <NSThread: 0x600002b32680>{number = 6, name = (null)}
這里票和錢的結(jié)果都是我們想要的飞蚓,并且是正確的結(jié)果滤港。

總結(jié):OSSpinLock是一個(gè)自旋鎖(后面再做介紹),通過加鎖解鎖趴拧,可以有效解決多線程隱患問題溅漾。
OSSpinLock是第一個(gè)同步技術(shù)方案,接下來就是os_unfair_lock八堡,在分析這個(gè)方案之前樟凄,我們先來了解下自旋鎖和互斥鎖。所謂的自旋鎖兄渺,即等待鎖的線程會(huì)處于忙等(busy-wait)狀態(tài)缝龄,一直占用著CPU資源,直到鎖被打開后挂谍,退出忙等叔壤,并且進(jìn)入加鎖狀態(tài)。
而互斥鎖口叙,就是等待鎖的過程是處理休眠的過程炼绘,在這個(gè)過程中不會(huì)占用CPU資源,一旦鎖被打開后妄田,重新喚醒等待的線程俺亮,然后進(jìn)行加鎖。
從使用的情況來看疟呐,兩個(gè)鎖的主要區(qū)別在于脚曾,是否會(huì)占用CPU資源,當(dāng)時(shí)不是說互斥鎖不占用CPU資源启具,就一定比自旋鎖好本讥,因?yàn)樵趩拘堰^程中也是會(huì)消耗內(nèi)存的。
只是說OSSpinLock這種自旋鎖會(huì)出現(xiàn)一種狀況,當(dāng)優(yōu)先級低的線程加鎖之后拷沸,優(yōu)先級高的線程在等待的過程中色查,可能出現(xiàn)優(yōu)先級高的線程會(huì)一直占著CPU資源,導(dǎo)致優(yōu)先級低的線程沒法釋放鎖撞芍,出現(xiàn)線程死鎖狀態(tài)秧了。而互斥鎖雖然在喚醒線程的時(shí)候會(huì)消耗CPU,但是不會(huì)出現(xiàn)死鎖狀態(tài)勤庐,相對比較安全示惊,所以目前蘋果從iOS10開始就推薦大家用os_unfair_lock來替換OSSpinLock好港。
接下來愉镰,我們來看看os_unfair_lock的使用

@interface BLOSUnfairLockDemo ()

@property (nonatomic, assign) os_unfair_lock moneyLock;
@property (nonatomic, assign) os_unfair_lock ticketLock;

@end

@implementation BLOSUnfairLockDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.moneyLock = OS_UNFAIR_LOCK_INIT;
        self.ticketLock = OS_UNFAIR_LOCK_INIT;
    }
    return self;
}

- (void)__drawMoney {
    
    os_unfair_lock_lock(&_moneyLock);
    
    [super __drawMoney];
    
    os_unfair_lock_unlock(&_moneyLock);
}

- (void)__saveMoney {
    
    os_unfair_lock_lock(&_moneyLock);
    
    [super __saveMoney];
    
    os_unfair_lock_unlock(&_moneyLock);
}

- (void)__saleTicket {
    
    os_unfair_lock_lock(&_ticketLock);
    
    [super __saleTicket];
    
    os_unfair_lock_unlock(&_ticketLock);
}

@end
這樣我們調(diào)用實(shí)例對象的方法
BLBaseDemo *demo = [[BLOSUnfairLockDemo alloc] init];
[demo moneyTest];
[demo ticketTest];
所打印結(jié)果如下:
2019-09-05 15:36:21.394991+0800 002-多線程加鎖方案[65256:35655513] 取20,還剩400元 - <NSThread: 0x600001de4a00>{number = 7, name = (null)}
2019-09-05 15:36:21.392122+0800 002-多線程加鎖方案[65256:35655524] 還剩0張票 - <NSThread: 0x600001dc8f80>{number = 6, name = (null)}
所以os_unfair_lock可以提供線程同步技術(shù)钧汹,而且目前來看丈探,蘋果更希望我們使用它來替換OSSpinLock

接下來,我們來看一個(gè)用C語言編寫的鎖拔莱,就是pthread_mutex碗降,mutex也是一種互斥鎖。先看看基本的使用

@interface BLMutexDemo ()

@property (nonatomic, assign) pthread_mutex_t ticketMutex;
@property (nonatomic, assign) pthread_mutex_t moneyMutex;

@end

@implementation BLMutexDemo

- (void)initMutex:(pthread_mutex_t *)mutex {
    // 初始化屬性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
    
    // 初始化mutex,參數(shù)一是pthread_mutex_t(鎖) 參數(shù)二是pthread_mutexattr_t(屬性)
    pthread_mutex_init(mutex, &attr);
    //使用完屬性之后 要銷毀
    pthread_mutexattr_destroy(&attr);
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        [self initMutex:&_ticketMutex];
        [self initMutex:&_moneyMutex];
    }
    return self;
}

- (void)__drawMoney {
    
    pthread_mutex_lock(&_moneyMutex);
    
    [super __drawMoney];
    
    pthread_mutex_unlock(&_moneyMutex);
}

- (void)__saveMoney {
    
    pthread_mutex_lock(&_moneyMutex);
    
    [super __saveMoney];
    
    pthread_mutex_unlock(&_moneyMutex);
}

- (void)__saleTicket {
    
    pthread_mutex_lock(&_ticketMutex);
    
    [super __saleTicket];
    
    pthread_mutex_unlock(&_ticketMutex);
}

-(void)dealloc {
    
    pthread_mutex_destroy(&_ticketMutex);
    pthread_mutex_destroy(&_moneyMutex);
}

@end
這樣我們調(diào)用實(shí)例對象的方法
BLBaseDemo *demo = [[BLMutexDemo alloc] init];
[demo moneyTest];
[demo ticketTest];
所打印結(jié)果如下:
2019-09-05 16:24:30.859567+0800 002-多線程加鎖方案[66031:35707487] 存50塘秦,還剩400元 - <NSThread: 0x600001522180>{number = 3, name = (null)}
2019-09-05 16:24:30.857287+0800 002-多線程加鎖方案[66031:35707486] 還剩0張票 - <NSThread: 0x600001524ec0>{number = 4, name = (null)}

這是就pthread_mutex_t的基本使用讼渊,當(dāng)然在定義pthread_mutex_t時(shí),我們還可以用這種方式:

// 屬性參數(shù)傳NULL尊剔,默認(rèn)為PTHREAD_MUTEX_DEFAULT模式
pthread_mutex_init(mutex, NULL);

這里爪幻,還需要講一個(gè)知識(shí)點(diǎn)就是,屬性設(shè)置须误,pthread_mutexattr_t有幾種模式:

#define PTHREAD_MUTEX_NORMAL        0
#define PTHREAD_MUTEX_ERRORCHECK    1
#define PTHREAD_MUTEX_RECURSIVE     2
#define PTHREAD_MUTEX_DEFAULT       PTHREAD_MUTEX_NORMAL

這里我們再來看看PTHREAD_MUTEX_RECURSIVE這種模式挨稿,用這種模式定義的鎖,叫做遞歸鎖京痢。顧名思義奶甘,這是在遞歸函數(shù)中使用的鎖。如下代碼:

@interface BLMutexDemo ()

@property (nonatomic, assign) pthread_mutex_t recursiveMutex;

@property (nonatomic, assign) int count;

@end

@implementation BLMutexDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.count = 10;
        
        // 初始化屬性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        
        // 初始化mutex,參數(shù)一是pthread_mutex_t(鎖) 參數(shù)二是pthread_mutexattr_t(屬性)
        pthread_mutex_init(&_recursiveMutex, &attr);
        //使用完屬性之后 要銷毀
        pthread_mutexattr_destroy(&attr);
        
    }
    return self;
}

- (void)otherTest {
    
    pthread_mutex_lock(&_recursiveMutex);
    
    
    if (self.count > 0) {
        self.count--;
        NSLog(@"count的值 -- %d",self.count);
        [self otherTest];
    }
    pthread_mutex_unlock(&_recursiveMutex);
}

-(void)dealloc {
    
    pthread_mutex_destroy(&_recursiveMutex);
}

@end
這樣我們調(diào)用實(shí)例對象的方法
BLBaseDemo *demo = [[BLMutexDemo alloc] init];
[demo otherTest];
所打印結(jié)果如下:
2019-09-05 16:38:41.741313+0800 002-多線程加鎖方案[66338:35728207] count的值 -- 9
2019-09-05 16:38:41.741421+0800 002-多線程加鎖方案[66338:35728207] count的值 -- 8
2019-09-05 16:38:41.741504+0800 002-多線程加鎖方案[66338:35728207] count的值 -- 7
2019-09-05 16:38:41.741587+0800 002-多線程加鎖方案[66338:35728207] count的值 -- 6
2019-09-05 16:38:41.741657+0800 002-多線程加鎖方案[66338:35728207] count的值 -- 5
2019-09-05 16:38:41.741724+0800 002-多線程加鎖方案[66338:35728207] count的值 -- 4
2019-09-05 16:38:41.741809+0800 002-多線程加鎖方案[66338:35728207] count的值 -- 3
2019-09-05 16:38:41.741891+0800 002-多線程加鎖方案[66338:35728207] count的值 -- 2
2019-09-05 16:38:41.741961+0800 002-多線程加鎖方案[66338:35728207] count的值 -- 1
2019-09-05 16:38:41.742033+0800 002-多線程加鎖方案[66338:35728207] count的值 -- 0

這就是pthread_mutex_t在PTHREAD_MUTEX_RECURSIVE模式下的使用祭椰,不過使用遞歸模式臭家,要注意需要再同一個(gè)線程下使用,才能達(dá)到遞歸加鎖解鎖的效果方淤。
除了PTHREAD_MUTEX_RECURSIVE遞歸模式钉赁,這里我們還需要知道另外一個(gè)知識(shí)點(diǎn),那就是條件(pthread_cond_t)臣淤。到這里大家可能覺得知識(shí)點(diǎn)有點(diǎn)多橄霉,因?yàn)楫吘惯€有好幾個(gè)鎖還沒有講到,其實(shí)后面的那些鎖,很多都是基于pthread_mutex_t這個(gè)鎖的基礎(chǔ)上來封裝的姓蜂,了解了pthread_mutex_t按厘,也就意味著知道后面的鎖的原理。那我們來看看什么是pthread_cond_t钱慢,很簡單逮京,條件條件,就是到達(dá)一種條件的情況下進(jìn)行操作束莫,來看看代碼

@interface BLMutexDemo1 ()

@property (assign, nonatomic) pthread_mutex_t mutex;
@property (assign, nonatomic) pthread_cond_t cond;
@property (strong, nonatomic) NSMutableArray *data;

@end

@implementation BLMutexDemo1

- (instancetype)init
{
    if (self = [super init]) {
        // 初始化屬性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
        // 初始化鎖
        pthread_mutex_init(&_mutex, &attr);
        // 銷毀屬性
        pthread_mutexattr_destroy(&attr);
        
        // 初始化條件
        pthread_cond_init(&_cond, NULL);
        
        self.data = [NSMutableArray array];
    }
    return self;
}

- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

// 生產(chǎn)者-消費(fèi)者模式

// 線程1
// 刪除數(shù)組中的元素
- (void)__remove
{
    pthread_mutex_lock(&_mutex);
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        // 等待
        pthread_cond_wait(&_cond, &_mutex);
    }
    
    [self.data removeLastObject];
    NSLog(@"刪除了元素");
    
    pthread_mutex_unlock(&_mutex);
}

// 線程2
// 往數(shù)組中添加元素
- (void)__add
{
    pthread_mutex_lock(&_mutex);
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    
    // 信號
    pthread_cond_signal(&_cond);
    // 廣播
    //    pthread_cond_broadcast(&_cond);
    
    pthread_mutex_unlock(&_mutex);
}

- (void)dealloc
{
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
}

@end
代碼大概的主要功能就是懒棉,一個(gè)數(shù)組的添加和刪除元素,只是在刪除元素的時(shí)候览绿,我們需要保證數(shù)組先有元素策严,如果沒有元素的話,先去添加元素饿敲,這時(shí)就可以用pthread_cond_t妻导,先在判斷數(shù)組元素個(gè)數(shù)為0的時(shí)候,使用pthread_cond_wait()怀各,暫時(shí)放開鎖倔韭,等待通知,然后再添加元素之后瓢对,使用pthread_cond_singal()來通知pthread_cond_wait()函數(shù)中的等待寿酌,這時(shí)候pthread_cond_wait()會(huì)重新加鎖,繼續(xù)原先刪除元素的操作硕蛹,得到結(jié)果醇疼。
打印如下:
2019-09-05 16:56:09.054302+0800 002-多線程加鎖方案[66701:35751970] __remove - begin
2019-09-05 16:56:10.059322+0800 002-多線程加鎖方案[66701:35751971] 添加了元素
2019-09-05 16:56:10.059662+0800 002-多線程加鎖方案[66701:35751970] 刪除了元素

接來下就是GCD中的信號量dispatch_semaphore,這也是同步技術(shù)方案中妓美,比較方便的一直方案僵腺。這里就簡單舉個(gè)例子

@interface BLSemaphoreDemo ()

@property (nonatomic, strong) dispatch_semaphore_t semaphore;

@end

@implementation BLSemaphoreDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.semaphore = dispatch_semaphore_create(3);
    }
    return self;
}

-(void)otherTest {
    
    for (int i = 0; i <= 9; i ++) {
        [[[NSThread alloc]initWithTarget:self selector:@selector(test) object:nil] start];
    }
}

- (void)test {
    
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    
    sleep(3);
    
    NSLog(@"test --- %@", [NSThread currentThread]);
    
    dispatch_semaphore_signal(self.semaphore);
}

@end
這樣我們調(diào)用實(shí)例對象的方法
BLBaseDemo *demo = [[BLSemaphoreDemo alloc] init];
[demo otherTest];
所打印結(jié)果如下:
2019-09-05 17:07:58.970138+0800 002-多線程加鎖方案[67225:35770816] test --- <NSThread: 0x60000007b600>{number = 3, name = (null)}
2019-09-05 17:07:58.970183+0800 002-多線程加鎖方案[67225:35770825] test --- <NSThread: 0x60000007bc40>{number = 12, name = (null)}
2019-09-05 17:07:58.970157+0800 002-多線程加鎖方案[67225:35770824] test --- <NSThread: 0x60000007bac0>{number = 11, name = (null)}
2019-09-05 17:08:01.974692+0800 002-多線程加鎖方案[67225:35770821] test --- <NSThread: 0x60000007bf00>{number = 8, name = (null)}
2019-09-05 17:08:01.974707+0800 002-多線程加鎖方案[67225:35770819] test --- <NSThread: 0x60000007b6c0>{number = 6, name = (null)}
2019-09-05 17:08:01.974693+0800 002-多線程加鎖方案[67225:35770820] test --- <NSThread: 0x60000007b540>{number = 7, name = (null)}
2019-09-05 17:08:04.978897+0800 002-多線程加鎖方案[67225:35770823] test --- <NSThread: 0x60000007b940>{number = 10, name = (null)}
2019-09-05 17:08:04.978897+0800 002-多線程加鎖方案[67225:35770822] test --- <NSThread: 0x60000007b640>{number = 9, name = (null)}
2019-09-05 17:08:04.978897+0800 002-多線程加鎖方案[67225:35770817] test --- <NSThread: 0x60000007bcc0>{number = 4, name = (null)}
2019-09-05 17:08:07.980190+0800 002-多線程加鎖方案[67225:35770818] test --- <NSThread: 0x60000007be40>{number = 5, name = (null)}

總結(jié):在打印的結(jié)果中,我們可以看到壶栋,每三秒辰如,打印一次,總共9條線程贵试,這就是信號量的作用琉兜。他可以控制同時(shí)并發(fā)的最大線程數(shù)量,每一條線程到達(dá)dispatch_semaphore_wait()這里時(shí)毙玻,會(huì)去判斷semaphore剩余可以使用的線程數(shù)豌蟋,因?yàn)槲覀円婚_始初始化的時(shí)候定義是3條:self.semaphore = dispatch_semaphore_create(3),所以這里最大并發(fā)數(shù)就是3桑滩,這就是為什么每次打印是三條的原因梧疲,dispatch_semaphore_signal()這個(gè)函數(shù)是在當(dāng)前線程走完時(shí),恢復(fù)一條線程的使用,因?yàn)槲覀僼est函數(shù)中幌氮,用了sleep(3)缭受,所以每條線程都會(huì)卡3秒,這只是為了演示效果该互。
接下來就是NSLock米者、NSCondition和NSConditionLock的使用,NSLock其實(shí)就是針對pthread_mutex_t在默認(rèn)模式下的封裝使用宇智,大致就是:

- (void)__saleTicket
{
    [self.ticketLock lock];
    
    [super __saleTicket];
    
    [self.ticketLock unlock];
}

而NSCondition蔓搞,其實(shí)就是對pthread_cond_t加pthread_mutex_t的封裝使用,如下代碼:

@interface BLNSConditionDemo ()

@property (nonatomic, strong) NSCondition *con;
@property (strong, nonatomic) NSMutableArray *data;

@end

@implementation BLNSConditionDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.con = [[NSCondition alloc] init];
        self.data = [NSMutableArray array];
    }
    return self;
}


- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

- (void)__remove
{
    
    [self.con lock];
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        [self.con wait];
    }
    
    [self.data removeLastObject];
    NSLog(@"刪除了元素");
    
    [self.con unlock];
}

- (void)__add
{
    
    [self.con lock];
    
    sleep(1);
    
    [self.data addObject:@"test"];
    NSLog(@"添加了元素");
    
    [self.con unlock];
    
    [self.con signal];
}

@end

而NSConditionLock,則是對NSCondition的進(jìn)一步封裝随橘,如下

@interface BLNSConditionLockDemo ()

@property (nonatomic, strong) NSConditionLock *con;
@property (strong, nonatomic) NSMutableArray *data;

@end

@implementation BLNSConditionLockDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.con = [[NSConditionLock alloc] initWithCondition:1];
        self.data = [NSMutableArray array];
    }
    return self;
}


- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
}

- (void)__one
{
    [self.con lockWhenCondition:1];
    
    NSLog(@"__one");
    sleep(1);
    
    [self.con unlockWithCondition:2];
}

- (void)__two
{
    [self.con lockWhenCondition:2];
    
    NSLog(@"__two");
    sleep(1);
    
    [self.con unlockWithCondition:3];
}

- (void)__three
{
    [self.con lockWhenCondition:3];
    
    NSLog(@"__three");
    
    [self.con unlock];
}

@end

NSConditionLock主要作用就在于喂分,它可以在滿足一個(gè)條件的情況下,進(jìn)行加鎖和解鎖太防。
最后還有兩種同步加鎖的方案妻顶,一種是dispatch_queue(DISPATCH_QUEUE_SERIAL)酸员,還有一種是@synchronized蜒车。
對于dispatch_queue(DISPATCH_QUEUE_SERIAL)大家都知道,因?yàn)榇嘘?duì)列幔嗦,不管你是同步還是異步酿愧,它都是依次執(zhí)行任務(wù)的,所以可以達(dá)到加鎖效果邀泉。
而對于@synchronized嬉挡,可能大家在日常的項(xiàng)目中已經(jīng)有所用到,大概就是@synchronized(self)汇恤,其實(shí)庞钢,在()中,只要是同一個(gè)對象因谎,就可以達(dá)到加鎖的效果基括,這里我就不做過多說明了,但是這個(gè)加鎖方式性能上不是很好财岔,因?yàn)檫@簡單一句話风皿,封裝了很多內(nèi)容。這里匠璧,既然講到性能桐款,我們也可以進(jìn)行一定的排序,如下圖:


image.png

其實(shí)也蠻容易理解的夷恍,首先是蘋果在iOS中主推的os_unfair_lock,其次就是被它替代的OSSpinLock魔眨,然后是GCD的信號量,接著是C語言的pthread_mutex,然后是串行隊(duì)列遏暴,后面NS打頭的Foundation框架的對象侨艾,都是基于pthread_mutex_t進(jìn)行封裝的,自然往后排拓挥。最后就是@synchronized唠梨。
這里,推薦大家平常使用的侥啤,有信號量和pthread_mutex当叭,因?yàn)閛s_unfair_lock是iOS10之后才出來的,而OSSpinLock又不推薦使用盖灸,所以性能又高蚁鳖,又比較兼容的就是信號量和和pthread_mutex。
iOS的多線程隱患和解決方案的幾大鎖赁炎,就先分享到這里醉箕,后續(xù)可能會(huì)有一些補(bǔ)充,希望對大家有所幫助徙垫!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末讥裤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子姻报,更是在濱河造成了極大的恐慌己英,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吴旋,死亡現(xiàn)場離奇詭異损肛,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)荣瑟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進(jìn)店門治拿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人笆焰,你說我怎么就攤上這事劫谅。” “怎么了仙辟?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵同波,是天一觀的道長。 經(jīng)常有香客問我叠国,道長未檩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任粟焊,我火速辦了婚禮冤狡,結(jié)果婚禮上孙蒙,老公的妹妹穿的比我還像新娘。我一直安慰自己悲雳,他們只是感情好挎峦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著合瓢,像睡著了一般坦胶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上晴楔,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天顿苇,我揣著相機(jī)與錄音,去河邊找鬼税弃。 笑死纪岁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的则果。 我是一名探鬼主播幔翰,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼西壮!你這毒婦竟也來了遗增?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤茸时,失蹤者是張志新(化名)和其女友劉穎贡定,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體可都,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年蚓耽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了渠牲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,146評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡步悠,死狀恐怖签杈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鼎兽,我是刑警寧澤答姥,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站谚咬,受9級特大地震影響鹦付,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜择卦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一敲长、第九天 我趴在偏房一處隱蔽的房頂上張望郎嫁。 院中可真熱鬧,春花似錦祈噪、人聲如沸泽铛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盔腔。三九已至,卻和暖如春月褥,著一層夾襖步出監(jiān)牢的瞬間铲觉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工吓坚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撵幽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓礁击,卻偏偏與公主長得像盐杂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子哆窿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評論 2 356

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

  • 鎖是一種同步機(jī)制链烈,用于多線程環(huán)境中對資源訪問的限制iOS中常見鎖的性能對比圖(摘自:ibireme): iOS鎖的...
    LiLS閱讀 1,518評論 0 6
  • 一. 多線程 1.常見多線程方案 pthread : 純粹 C 語言的API,跨平臺(tái), 線程生命周期程序員管理...
    天明天閱讀 4,157評論 0 14
  • 一挚躯、簡介:多線程在之前進(jìn)行過一篇詳細(xì)的基礎(chǔ)博客 iOS多線程 二强衡、多線程的基礎(chǔ)知識(shí)回顧 1.1、iOS中的常見多線...
    IIronMan閱讀 895評論 0 4
  • 前言 一塊資源可能會(huì)被多個(gè)線程共享码荔,也就是多個(gè)線程可能會(huì)訪問同一塊資源漩勤,比如多個(gè)線程訪問同一個(gè)對象、同一個(gè)變量缩搅、同...
    WQ_UESTC閱讀 867評論 0 5
  • 一越败、前言 前段時(shí)間看了幾個(gè)開源項(xiàng)目,發(fā)現(xiàn)他們保持線程同步的方式各不相同硼瓣,有@synchronized究飞、NSLock...
    稻春閱讀 464評論 0 0