iOS-多線程

本文主要介紹了 iOS的多線程方案, 多線程安全方案, 多讀單寫方案.

篇幅稍長,還請(qǐng)耐心看完.

進(jìn)程

理論上,每個(gè)iOS App都是一個(gè)進(jìn)程, 有自己獨(dú)立的虛擬空間來存儲(chǔ)自己的運(yùn)行數(shù)據(jù).

線程

每個(gè)進(jìn)程中有多個(gè)線程,這些線程共享進(jìn)程的全局變量和堆數(shù)據(jù). 多條線程可以在一個(gè)進(jìn)程中并發(fā)執(zhí)行.達(dá)到同一時(shí)間完成多個(gè)任務(wù)的效果. 其實(shí)在單處理器中,所謂的并發(fā),是操作系統(tǒng)不斷的在線程間來回切換達(dá)到的一個(gè)偽并發(fā)效果.

多線程的作用

  • 避免線程堵塞,有些需要大量時(shí)間執(zhí)行的任務(wù),如果放在主線程同步執(zhí)行,則會(huì)造成卡頓
  • 將復(fù)雜任務(wù)拆分, 如UITableViewCell加載圖片時(shí),可以開辟新線程去處理圖片數(shù)據(jù),最終再交由主線程實(shí)現(xiàn)
  • 多任務(wù)并行

iOS中的多線程方案

  • pthread: C語言,使用難度大.需要由使用者管理生命周期. 基本沒人用
  • NSThread: OC 面向?qū)ο?需要依賴Runloop保活,需要由使用者管理生命周期,使用較少
  • GCD: C語言. 由系統(tǒng)本身管理生命周期 是目前較主流的多線程方案
  • NSOperation: OC語言 基于GCD的封裝,更加面向?qū)ο? 由系統(tǒng)本身管理聲明周期. 也比較多人使用

隊(duì)列

隊(duì)列中裝載著多個(gè)線程,根據(jù)隊(duì)列的不同屬性安排線程的任務(wù)調(diào)度,隊(duì)列可分為串行隊(duì)列和并發(fā)隊(duì)列

  • 串行隊(duì)列:隊(duì)列中的任務(wù)一個(gè)一個(gè)連成串執(zhí)行
  • 并發(fā)隊(duì)列:隊(duì)列可以同時(shí)執(zhí)行任務(wù)

同步

所有任務(wù)在同一個(gè)線程一個(gè)接一個(gè)執(zhí)行

B任務(wù)必須等待A任務(wù)執(zhí)行完成才可以進(jìn)行. 如果A任務(wù)耗時(shí)太長, 則B任務(wù)也會(huì)一直等到A任務(wù)進(jìn)行完成.

iOS場(chǎng)景: 加載網(wǎng)絡(luò)數(shù)據(jù)時(shí),使用加載指示器擋住當(dāng)前view, 等到加載完成后指示器消失,頁面展示數(shù)據(jù). 如果此時(shí)加載任務(wù)遲遲未完成.則界面一直卡在加載界面.

有可能會(huì)引起死鎖

異步

在新的線程中執(zhí)行任務(wù).

下面看看同步異步在不同線程的表現(xiàn)

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"main thread %@",[NSThread currentThread]);
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"main dispatch_async_thred:%@",[NSThread currentThread]);
    });
    
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"global dispatch_sync_thred:%@",[NSThread currentThread]);
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"global dispatch_async_thred:%@",[NSThread currentThread]);
    });
    
    dispatch_queue_t squeue = dispatch_queue_create("QUEUE1", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(squeue, ^{
        NSLog(@"squeue dispatch_sync_thred:%@",[NSThread currentThread]);
    });
    dispatch_async(squeue, ^{
        NSLog(@"squeue dispatch_async_thred:%@",[NSThread currentThread]);
    });
    
    dispatch_queue_t cqueue = dispatch_queue_create("QUEUE1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(cqueue, ^{
        NSLog(@"cqueue dispatch_sync_thred:%@",[NSThread currentThread]);
    });
    
    dispatch_async(squeue, ^{
        NSLog(@"cqueue dispatch_async_thred:%@",[NSThread currentThread]);
    });
}

main thread <NSThread: 0x600003a0c200>{number = 1, name = main}
global dispatch_sync_thred:<NSThread: 0x600003a0c200>{number = 1, name = main}
global dispatch_async_thred:<NSThread: 0x600003a4d980>{number = 2, name = (null)}
squeue dispatch_sync_thred:<NSThread: 0x600003a0c200>{number = 1, name = main}
cqueue dispatch_sync_thred:<NSThread: 0x600003a0c200>{number = 1, name = main}
squeue dispatch_async_thred:<NSThread: 0x600003a4d980>{number = 2, name = (null)}
cqueue dispatch_async_thred:<NSThread: 0x600003a4d980>{number = 2, name = (null)}
main dispatch_async_thred:<NSThread: 0x600003a0c200>{number = 1, name = main}

從結(jié)果可以看到.

  • 同步:都沒有開啟新的線程
  • 異步:全局隊(duì)列,串行隊(duì)列,并發(fā)隊(duì)列中開辟了新線程. 主隊(duì)列不開辟新線程

主隊(duì)列異步執(zhí)行任務(wù)情況

NSLog(@"1%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"main async 2:%@",[NSThread currentThread]);
});
NSLog(@"3%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"main async 4:%@",[NSThread currentThread]);
});
NSLog(@"5%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"main async 6:%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"main async 7:%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"main async 8:%@",[NSThread currentThread]);
});
NSLog(@"9%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"main async 10:%@",[NSThread currentThread]);
});

1<NSThread: 0x600001564140>{number = 1, name = main}
3<NSThread: 0x600001564140>{number = 1, name = main}
5<NSThread: 0x600001564140>{number = 1, name = main}
9<NSThread: 0x600001564140>{number = 1, name = main}
main async 2:<NSThread: 0x600001564140>{number = 1, name = main}
main async 4:<NSThread: 0x600001564140>{number = 1, name = main}
main async 6:<NSThread: 0x600001564140>{number = 1, name = main}
main async 7:<NSThread: 0x600001564140>{number = 1, name = main}
main async 8:<NSThread: 0x600001564140>{number = 1, name = main}
main async 10:<NSThread: 0x600001564140>{number = 1, name = main}

從結(jié)果可以得出. 在主隊(duì)列中運(yùn)行異步任務(wù), 會(huì)等待viewdidload中的任務(wù)執(zhí)行完成后,再串行的執(zhí)行任務(wù).那么根據(jù)上述的結(jié)果.拋出一個(gè)問題.下屬代碼,在主隊(duì)列執(zhí)行同步任務(wù).那么會(huì)有什么后果.

NSLog(@"1%@",[NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"main sync 2:%@",[NSThread currentThread]);
});
NSLog(@"3%@",[NSThread currentThread]);

答案就是:死鎖. 因?yàn)橥饺蝿?wù)需要等待viewdidload執(zhí)行完畢,而viewdidload又要等待同步任務(wù)出隊(duì).大家一起等.大家都不出隊(duì).那就大家抱著一起卡死唄.

全局隊(duì)列異步執(zhí)行任務(wù)情況

image-20210603174841144.png
dispatch_queue_t gQueue = dispatch_get_global_queue(0, 0);
    NSLog(@"1%@",[NSThread currentThread]);
dispatch_async(gQueue, ^{
    NSLog(@"global async 2:%@",[NSThread currentThread]);
});
NSLog(@"3%@",[NSThread currentThread]);
dispatch_async(gQueue, ^{
    NSLog(@"global async 4:%@",[NSThread currentThread]);
});
NSLog(@"5%@",[NSThread currentThread]);
dispatch_async(gQueue, ^{
    NSLog(@"global async 6:%@",[NSThread currentThread]);
});
dispatch_async(gQueue, ^{
    NSLog(@"global async 7:%@",[NSThread currentThread]);
});
dispatch_async(gQueue, ^{
    NSLog(@"global async 8:%@",[NSThread currentThread]);
});
NSLog(@"9%@",[NSThread currentThread]);
dispatch_async(gQueue, ^{
    NSLog(@"global async 10:%@",[NSThread currentThread]);
});

1<NSThread: 0x6000003f0080>{number = 1, name = main}
3<NSThread: 0x6000003f0080>{number = 1, name = main}
global async 2:<NSThread: 0x6000003b1840>{number = 6, name = (null)}
5<NSThread: 0x6000003f0080>{number = 1, name = main}
9<NSThread: 0x6000003f0080>{number = 1, name = main}
global async 4:<NSThread: 0x6000003b2180>{number = 7, name = (null)}
global async 6:<NSThread: 0x6000003b1840>{number = 6, name = (null)}
global async 7:<NSThread: 0x6000003f3380>{number = 5, name = (null)}
global async 8:<NSThread: 0x6000003b2180>{number = 7, name = (null)}
global async 10:<NSThread: 0x6000003ec9c0>{number = 4, name = (null)}

我們上面已經(jīng)得出結(jié)論.在全局隊(duì)列執(zhí)行異步任務(wù),是會(huì)開辟新的線程.所以很明顯.他并不需要等到viewdidload執(zhí)行完畢就可以執(zhí)行任務(wù).

自建串行隊(duì)列異步執(zhí)行任務(wù)情況

dispatch_queue_t squeue = dispatch_queue_create("QUEUE1", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1%@",[NSThread currentThread]);
dispatch_async(squeue, ^{
    NSLog(@"squeue async 2:%@",[NSThread currentThread]);
});
NSLog(@"3%@",[NSThread currentThread]);
dispatch_async(squeue, ^{
    NSLog(@"squeue async 4:%@",[NSThread currentThread]);
});
NSLog(@"5%@",[NSThread currentThread]);
dispatch_async(squeue, ^{
    NSLog(@"squeue async 6:%@",[NSThread currentThread]);
});
dispatch_async(squeue, ^{
    NSLog(@"squeue async 7:%@",[NSThread currentThread]);
});
dispatch_async(squeue, ^{
    NSLog(@"squeue async 8:%@",[NSThread currentThread]);
});
NSLog(@"9%@",[NSThread currentThread]);
dispatch_async(squeue, ^{
    NSLog(@"squeue async 10:%@",[NSThread currentThread]);
});

1<NSThread: 0x60000215c600>{number = 1, name = main}
3<NSThread: 0x60000215c600>{number = 1, name = main}
squeue async 2:<NSThread: 0x600002108140>{number = 6, name = (null)}
5<NSThread: 0x60000215c600>{number = 1, name = main}
9<NSThread: 0x60000215c600>{number = 1, name = main}
squeue async 4:<NSThread: 0x600002108140>{number = 6, name = (null)}
squeue async 6:<NSThread: 0x600002108140>{number = 6, name = (null)}
squeue async 7:<NSThread: 0x600002108140>{number = 6, name = (null)}
squeue async 8:<NSThread: 0x600002108140>{number = 6, name = (null)}
squeue async 10:<NSThread: 0x600002108140>{number = 6, name = (null)}

結(jié)果與全局隊(duì)列等同,但只創(chuàng)建了一條線程

自建并發(fā)隊(duì)列異步執(zhí)行任務(wù)情況

dispatch_queue_t cqueue = dispatch_queue_create("QUEUE1", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1%@",[NSThread currentThread]);
dispatch_async(cqueue, ^{
    NSLog(@"cqueue async 2:%@",[NSThread currentThread]);
});
NSLog(@"3%@",[NSThread currentThread]);
dispatch_async(cqueue, ^{
    NSLog(@"cqueue async 4:%@",[NSThread currentThread]);
});
NSLog(@"5%@",[NSThread currentThread]);
dispatch_async(cqueue, ^{
    NSLog(@"cqueue async 6:%@",[NSThread currentThread]);
});
dispatch_async(cqueue, ^{
    NSLog(@"cqueue async 7:%@",[NSThread currentThread]);
});
dispatch_async(cqueue, ^{
    NSLog(@"cqueue async 8:%@",[NSThread currentThread]);
});
NSLog(@"9%@",[NSThread currentThread]);
dispatch_async(cqueue, ^{
    NSLog(@"cqueue async 10:%@",[NSThread currentThread]);
});

1<NSThread: 0x6000028a03c0>{number = 1, name = main}
3<NSThread: 0x6000028a03c0>{number = 1, name = main}
cqueue async 2:<NSThread: 0x60000289d1c0>{number = 3, name = (null)}
5<NSThread: 0x6000028a03c0>{number = 1, name = main}
cqueue async 4:<NSThread: 0x6000028f8800>{number = 7, name = (null)}
cqueue async 6:<NSThread: 0x60000289d1c0>{number = 3, name = (null)}
cqueue async 7:<NSThread: 0x6000028f8800>{number = 7, name = (null)}
cqueue async 8:<NSThread: 0x6000028f8800>{number = 7, name = (null)}
9<NSThread: 0x6000028a03c0>{number = 1, name = main}
cqueue async 10:<NSThread: 0x6000028f8800>{number = 7, name = (null)}

結(jié)果與全局隊(duì)列相同,也創(chuàng)建了多條線程

global_queue

從上述結(jié)果對(duì)比,global_queue是一條并發(fā)隊(duì)列

main_queue

main_queue是一條串行隊(duì)列

死鎖

在往主隊(duì)列添加同步任務(wù)時(shí),會(huì)造成死鎖.其實(shí)不只是主隊(duì)列,在串行隊(duì)列同步任務(wù)中添加同步任務(wù),也會(huì)引起死鎖.一下代碼同樣會(huì)引起奔潰.而在并發(fā)隊(duì)列中則不會(huì)產(chǎn)生這種情況.

dispatch_queue_t cqueue = dispatch_queue_create("QUEUE1", DISPATCH_QUEUE_SERIAL);
dispatch_sync(cqueue, ^{
    NSLog(@"cqueue async 2:%@",[NSThread currentThread]);
    dispatch_sync(cqueue, ^{
        NSLog(@"cqueue sync 3:%@",[NSThread currentThread]);
    });
});

線程安全

多線程在帶來便利的同時(shí),也會(huì)帶來隱患.當(dāng)多個(gè)線程對(duì)同一個(gè)變量進(jìn)行寫操作時(shí),可能會(huì)造成結(jié)果不同.賣票問題就是很典型的多線程導(dǎo)致的隱患.

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i < 30; i++) {
             [self saleTickets];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 30; i++) {
             [self saleTickets];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 40; i++) {
             [self saleTickets];
        }
    });
}

- (void)saleTickets {
    tickets = tickets - 1;
    NSLog(@"賣出1張票,還剩%d張票",tickets);
}

從最終結(jié)果可以看出.最終的余票并不為0.

說明有多條線程在同時(shí)操作一塊內(nèi)存

例如:多個(gè)線程訪問tickets時(shí)值為98,進(jìn)行減1. 所以造成了結(jié)果的不同.

解決方法1.加鎖

iOS中的鎖
  • OSSpinLock(自旋鎖)
  • os_unfairLock
  • pthread_mutex
  • dispatch_semaphor
  • NSLock
  • NSRecursiveLock
  • NSCondition
  • NSConditonLock
  • @synchronized
自旋鎖

當(dāng)線程檢測(cè)到自旋鎖上鎖時(shí),會(huì)進(jìn)行忙等,直到鎖被釋放,才會(huì)繼續(xù)執(zhí)行任務(wù).類似于while(lock){}

互斥鎖

當(dāng)線程檢測(cè)到上鎖時(shí),該線程會(huì)進(jìn)行休眠,等到其他線程解鎖,才會(huì)喚起該線程.

OSSpinLock
#import <libkern/OSAtomic.h>
- (void)viewDidLoad {
    [super viewDidLoad];
    self.lock = OS_SPINLOCK_INIT;
    [self beginSaleTicket];
}

- (void)saleTickets {
    OSSpinLockLock(&_lock);
    tickets = tickets - 1;
    NSLog(@"賣出1張票,還剩%d張票",tickets);
    OSSpinLockUnlock(&_lock);
}

- (void)beginSaleTicket {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i < 30; i++) {
             [self saleTickets];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 30; i++) {
             [self saleTickets];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 40; i++) {
             [self saleTickets];
        }
    });
}

最終執(zhí)行結(jié)果,發(fā)現(xiàn)最終余票為0. 也就是說,線程安全了

OSSpinLock為什么被棄用
原因1:死鎖
#import <os/lock.h>
- (void)viewDidLoad {
    [super viewDidLoad];
    self.lock = OS_SPINLOCK_INIT;
    [self deadLock];
    NSLog(@"test");
}

- (void)deadLock {
    OSSpinLockLock(&_lock);
    [self lockAgain];
    NSLog(@"等待lockAgain執(zhí)行完成");
    OSSpinLockUnlock(&_lock);
}

- (void)lockAgain {
    OSSpinLockLock(&_lock);
    NSLog(@"加鎖");
    OSSpinLockUnlock(&_lock);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"touch");
}

上述代碼的結(jié)果就是, 什么都不輸出, touchesBegan也無法響應(yīng).為什么呢?分析一下

自旋鎖中, deadLock方法拿到鎖,上鎖后調(diào)用lockAgain,此時(shí)檢測(cè)到_lock上鎖,lockAgain會(huì)進(jìn)入忙等. deadLock被堵住,無法拿到鎖進(jìn)行解鎖.所以線程卡死. 當(dāng)然, 互斥鎖也會(huì)有這種問題發(fā)生.

原因2:優(yōu)先級(jí)反轉(zhuǎn)

線程運(yùn)行時(shí)搶占式的, 當(dāng)高優(yōu)先級(jí)的線程輪轉(zhuǎn)時(shí),會(huì)搶占低優(yōu)先級(jí)任務(wù)的CPU控制器 .

假設(shè)有 A,B兩條線程, 優(yōu)先級(jí) : A > B

B先于A持有鎖并上鎖, 此時(shí)A輪轉(zhuǎn), 搶占B的CPU控制權(quán), B未對(duì)鎖就行解鎖操作, A任務(wù)檢測(cè)到上了鎖, 忙等, 最終掛起線程, 繼續(xù)回到B操作. 只有兩條線程可能問題不大. 但是如果有多個(gè)線程不斷輪轉(zhuǎn), 就會(huì)出現(xiàn)優(yōu)先級(jí)最高的任務(wù)A遲遲無法完成, 最終影響了程序性能

os_unfair_lock

iOS10之后, OC推出的用來替代 OSSpinLock 的鎖,

- (void)viewDidLoad {
    [super viewDidLoad];
    self.lock = OS_UNFAIR_LOCK_INIT;
    [self beginSaleTicket];
}

- (void)saleTickets {
    os_unfair_lock_lock(&_lock);
    tickets = tickets - 1;
    NSLog(@"賣出1張票,還剩%d張票",tickets);
    os_unfair_lock_unlock(&_lock);
}

使用方法也很簡單, iOS內(nèi)部沒有標(biāo)明os_unfair_lock是自旋鎖還是互斥鎖, 但是通過從匯編看到的結(jié)果, 檢測(cè)到加鎖的線程走到某一步之后, 是直接休眠的. 根據(jù)定義看, os_unfair_lock是一個(gè)互斥鎖.

pthread_mutex_t

pthread_mutex_t是一個(gè)比較強(qiáng)大的鎖, 里面封裝了多個(gè)類型的鎖. 普通鎖, 遞歸鎖. 也可以為鎖添加條件

- (void)viewDidLoad {
    [super viewDidLoad];
    pthread_mutex_init(&(_lock), NULL);
    [self beginSaleTicket];
}

- (void)saleTickets {
    pthread_mutex_lock(&_lock);
    tickets = tickets - 1;
    NSLog(@"賣出1張票,還剩%d張票",tickets);
    pthread_mutex_unlock(&_lock);
}

眼尖的朋友應(yīng)該看出來了.pthread_mutex_init可以傳入兩個(gè)參數(shù), 第二個(gè)參數(shù)指定了鎖的類型. 還記得上面提到的同一線程中不同任務(wù)多次加鎖導(dǎo)致的死鎖問題嗎? pthread_mutex_init提供了解決方案. 那就是 遞歸鎖

遞歸鎖
- (void)viewDidLoad {
    [super viewDidLoad];
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&(_lock), &attr);
    pthread_mutexattr_destroy(&attr);
    [self deadLock];
    NSLog(@"test");
}

- (void)deadLock {
    pthread_mutex_lock(&_lock);
    [self lockAgain];
    NSLog(@"等待lockAgain執(zhí)行完成");
    pthread_mutex_unlock(&_lock);
}

- (void)lockAgain {
    pthread_mutex_lock(&_lock);
    NSLog(@"加鎖");
    pthread_mutex_unlock(&_lock);
}

可以看到, 當(dāng)設(shè)置pthread_mutex_lock為遞歸鎖時(shí), 任務(wù)可以順利執(zhí)行. 遞歸鎖允許同一線程中的不同任務(wù)對(duì)同一把鎖多次上鎖.

條件

給鎖添加條件時(shí), 線程對(duì)當(dāng)前持有的鎖進(jìn)行釋放, 然后進(jìn)行休眠, 等待其他線程釋放條件信號(hào),就會(huì)喚醒該線程, 重新加鎖并繼續(xù)執(zhí)行.

假設(shè)我們有一個(gè)場(chǎng)景: 搬磚糊墻. 當(dāng)有磚的時(shí)候, 才可以開始糊墻.假設(shè)糊墻跟搬磚的是兩個(gè)人, 當(dāng)糊墻的把磚用完的時(shí)候, 就需要等搬磚的人把磚搬過來才可以繼續(xù)進(jìn)行.

- (void)viewDidLoad {
    [super viewDidLoad];
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&_lock, &attr);
    pthread_mutexattr_destroy(&attr);
    pthread_cond_init(&_cond, NULL);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self build];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self getBrick];
    });
}

- (void)build {
    pthread_mutex_lock(&_lock);
    NSLog(@"開始糊墻");
    if (brick == 0) {
        // 等待
        NSLog(@"沒磚了,等磚來");
        pthread_cond_wait(&_cond, &_lock);
    }
    NSLog(@"糊墻");
    pthread_mutex_unlock(&_lock);
}

- (void)getBrick {
    pthread_mutex_lock(&_lock);
    sleep(1);
    brick += 1;
    NSLog(@"磚來了");
    pthread_cond_signal(&_cond);
    pthread_mutex_unlock(&_lock);
}

值得注意的一點(diǎn)是. 條件信號(hào)釋放后, 添加條件的一方并不會(huì)馬上喚醒并加鎖執(zhí)行后續(xù)任務(wù). 而是會(huì)等待信號(hào)發(fā)出方解開當(dāng)前鎖的時(shí)候才會(huì)喚醒. 所以如果有特別耗時(shí)的任務(wù). 可以放在unlock 后面做.

NSLock
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.lock = [NSLock new];
    [self beginSaleTicket];
}

- (void)saleTickets {
    [self.lock lock];
    tickets = tickets - 1;
    NSLog(@"賣出1張票,還剩%d張票",tickets);
    [self.lock unlock];
}

NSLock是對(duì)mutex 普通鎖的封裝, 原理相同.此處就不贅述了

NSRecursiveLock

對(duì)mutex遞歸鎖的封裝,API也與NSLock基本一致.此處就不貼代碼占篇幅了

NSCondition

對(duì)mutex和cond的封裝

- (void)viewDidLoad {
    [super viewDidLoad];
    self.condition = [NSCondition new];
    [self buildWall];
}

- (void)buildWall {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self build];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self getBrick];
    });
}

- (void)build {
    [self.condition lock];
    NSLog(@"開始糊墻");
    if (brick == 0) {
        // 等待
        NSLog(@"沒磚了,搬磚去");
        [self.condition wait];
    }
    NSLog(@"糊墻");
    [self.condition unlock];
}

- (void)getBrick {
    [self.condition lock];
    sleep(1);
    brick += 1;
    NSLog(@"開始搬磚");
    [self.condition signal];
    [self.condition unlock];
}
NSConditionLock

對(duì)NSCondition進(jìn)行封裝,可以在同一把鎖上添加不同的條件

- (void)viewDidLoad {
    [super viewDidLoad];
    self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self stepThree];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self stepOne];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self stepTwo];
    });
}

- (void)stepOne
{
    [self.conditionLock lock];
    NSLog(@"%s",__func__);
    sleep(1);
    [self.conditionLock unlockWithCondition:2];
}

- (void)stepTwo
{
    [self.conditionLock lockWhenCondition:2];
    NSLog(@"%s",__func__);
    sleep(1);
    [self.conditionLock unlockWithCondition:3];
}

- (void)stepThree
{
    [self.conditionLock lockWhenCondition:3];
    NSLog(@"%s",__func__);
    [self.conditionLock unlock];
}
@synchronized

是OC的語法糖,本質(zhì)上也是對(duì)mutex的封裝

- (void)viewDidLoad {
    [super viewDidLoad];
    [self beginSaleTicket];
}
- (void)saleTickets {
    @synchronized (self) {
        tickets = tickets - 1;
        NSLog(@"賣出1張票,還剩%d張票",tickets);
    }
}

使用@synchronized代碼相當(dāng)簡潔. 通過測(cè)試, @synchronized內(nèi)部實(shí)現(xiàn)的是遞歸鎖.

說完了iOS中提供的鎖,再來說說他們的性能

普通鎖 > 條件鎖 > 遞歸鎖

os_unfair_lock > OSSPinLock > pthread_mutext_t > NSLock > NSCondition > pthread_mutex(recursive) > NSRecursiveLock > NSConditionLock > @synchronized

解決方法2:GCD串行隊(duì)列

上面我們提到,在串行隊(duì)列中,異步任務(wù)是串行執(zhí)行的.所以我們可以新建一個(gè)GCD的串行隊(duì)列來進(jìn)行賣票操作.

- (void)viewDidLoad {
    [super viewDidLoad];
    self.ticketQueue = dispatch_queue_create("ticketQueue", DISPATCH_QUEUE_SERIAL);
    [self beginSaleTicket];
}


- (void)saleTickets {
    tickets = tickets - 1;
    NSLog(@"賣出1張票,還剩%d張票",tickets);
}

- (void)beginSaleTicket {
    dispatch_async(self.ticketQueue, ^{
        for (int i = 0; i < 30; i++) {
             [self saleTickets];
        }
    });
    dispatch_async(self.ticketQueue, ^{
        for (int i = 0; i < 30; i++) {
             [self saleTickets];
        }
    });
    dispatch_async(self.ticketQueue, ^{
        for (int i = 0; i < 40; i++) {
             [self saleTickets];
        }
    });
}

解決方法3:信號(hào)量dispatch_semaphore_t

此處涉及PV操作的概念.

執(zhí)行P操作時(shí), 若 P > 0, 信號(hào)量-1, 執(zhí)行任務(wù), 若 P <= 0, 休眠等待

執(zhí)行V操作, 信號(hào)量+1

我們可以通過設(shè)置信號(hào)量為1, 來設(shè)置并發(fā)線程的最大并發(fā)量為1

- (void)viewDidLoad {
    [super viewDidLoad];
    self.ticketQueue = dispatch_get_global_queue(0, 0);
    self.semaphore = dispatch_semaphore_create(1);
    [self beginSaleTicket];
}


- (void)saleTickets {
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    tickets = tickets - 1;
    NSLog(@"賣出1張票,還剩%d張票",tickets);
    dispatch_semaphore_signal(self.semaphore);
}

讀寫安全(多讀單寫)

當(dāng)一個(gè)文件, 我們可以對(duì)它進(jìn)行讀寫的時(shí)候, 就會(huì)出現(xiàn)不安全的狀況, 多條線程同時(shí)寫, 或者一邊讀一邊寫都是安全隱患.

所以我們希望對(duì)文件操作是可以做到

  • 可以多條線程同時(shí)讀取文件
  • 進(jìn)行寫操作時(shí),不可以讀取文件, 也不允許多條線程進(jìn)行寫入操作

解決方案:dispatch_barrier_async

- (void)viewDidLoad {
    [super viewDidLoad];
    self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(self.queue, ^{
            [self read];
        });
        dispatch_async(self.queue, ^{
            [self read];
        });
        dispatch_async(self.queue, ^{
            [self read];
        });
        dispatch_barrier_async(self.queue, ^{
            [self write];
        });
    }
}

- (void)read {
    sleep(1);
    NSLog(@"read");
}

- (void)write
{
    sleep(1);
    NSLog(@"write");
}

從輸出結(jié)果看到, 會(huì)同時(shí)打印多個(gè)read,但一次只會(huì)打印出一個(gè)write,而且在讀操作時(shí),也不會(huì)進(jìn)行其他操作.

dispatch_barrier_async 通過為并發(fā)隊(duì)列設(shè)置柵欄的方式, 當(dāng)柵欄建立時(shí),其他異步任務(wù)都不允許執(zhí)行.

注意

思考一下,把上面的并發(fā)隊(duì)列換成全局隊(duì)列,會(huì)怎么樣?

self.queue = dispatch_get_global_queue(0, 0);

2021-06-04 11:53:23.544718+0800 MultiThread[98352:14881477] read
2021-06-04 11:53:23.544718+0800 MultiThread[98352:14881479] write
2021-06-04 11:53:23.544733+0800 MultiThread[98352:14881480] read
2021-06-04 11:53:23.544743+0800 MultiThread[98352:14881485] read
2021-06-04 11:53:23.544718+0800 MultiThread[98352:14881476] read
2021-06-04 11:53:23.544718+0800 MultiThread[98352:14881482] read
2021-06-04 11:53:23.544766+0800 MultiThread[98352:14881483] read
2021-06-04 11:53:23.544780+0800 MultiThread[98352:14881490] read
2021-06-04 11:53:23.544780+0800 MultiThread[98352:14881489] write
2021-06-04 11:53:23.544830+0800 MultiThread[98352:14881491] read
2021-06-04 11:53:23.544841+0800 MultiThread[98352:14881492] read
2021-06-04 11:53:23.544882+0800 MultiThread[98352:14881493] write
2021-06-04 11:53:23.544905+0800 MultiThread[98352:14881494] read
2021-06-04 11:53:23.545049+0800 MultiThread[98352:14881496] read
2021-06-04 11:53:23.545061+0800 MultiThread[98352:14881495] read
2021-06-04 11:53:23.545064+0800 MultiThread[98352:14881498] read
2021-06-04 11:53:23.545077+0800 MultiThread[98352:14881499] read
2021-06-04 11:53:23.545146+0800 MultiThread[98352:14881501] write
2021-06-04 11:53:23.545083+0800 MultiThread[98352:14881497] write

以上是部分的結(jié)果.可以看出,就算加了GCD柵欄,還是等于異步并發(fā).

值得注意的是, GCD的柵欄只能擋住我們自己創(chuàng)建的并發(fā)隊(duì)列.

并不能擋住全局隊(duì)列. 這是為了安全起見. 如果全局隊(duì)列被一個(gè)及其耗時(shí)的操作block住, 那會(huì)引發(fā)很多其他的問題.

文章很長,感謝觀看. 如果文章有問題,還請(qǐng)不吝賜教.如果對(duì)你有一些幫助, 麻煩點(diǎn)個(gè)贊

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荤牍,一起剝皮案震驚了整個(gè)濱河市岛马,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌数冬,老刑警劉巖竞川,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鲁沥,死亡現(xiàn)場(chǎng)離奇詭異瘫筐,居然都是意外死亡堤魁,警方通過查閱死者的電腦和手機(jī)喂链,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來妥泉,“玉大人椭微,你說我怎么就攤上這事∶ち矗” “怎么了赏表?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長匈仗。 經(jīng)常有香客問我瓢剿,道長,這世上最難降的妖魔是什么悠轩? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任间狂,我火速辦了婚禮,結(jié)果婚禮上火架,老公的妹妹穿的比我還像新娘鉴象。我一直安慰自己忙菠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布纺弊。 她就那樣靜靜地躺著牛欢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪淆游。 梳的紋絲不亂的頭發(fā)上傍睹,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音犹菱,去河邊找鬼拾稳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛腊脱,可吹牛的內(nèi)容都是我干的访得。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼陕凹,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼悍抑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起杜耙,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤传趾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后泥技,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浆兰,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年珊豹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了簸呈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡店茶,死狀恐怖蜕便,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情贩幻,我是刑警寧澤轿腺,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站丛楚,受9級(jí)特大地震影響族壳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜趣些,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一仿荆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦拢操、人聲如沸锦亦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杠园。三九已至,卻和暖如春舔庶,著一層夾襖步出監(jiān)牢的瞬間抛蚁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國打工栖茉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人孵延。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓吕漂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親尘应。 傳聞我的和親對(duì)象是個(gè)殘疾皇子惶凝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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