上一篇文章裹驰,我們大致了解了刷晋,多線程的基本知識(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)問題,就好像下面這張圖:
所以這個(gè)時(shí)候藤巢,我們需要去解決這個(gè)問題搞莺。類似的例子還有像在銀行存錢取錢的操作。如果不做限制掂咒,同一時(shí)間存錢取錢才沧,也必然會(huì)出現(xiàn)問題,如下圖:
這個(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)行一定的排序,如下圖:
其實(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ǔ)充,希望對大家有所幫助徙垫!