文章目錄
1. iOS中多線程方案
最底層API pthread
: 是一套通用的跨平臺的多線程API,是基於c語言,線程的生命週期需要手動管理;
NSThread:是對pthread用OC語言的封裝,使得操作線程可以像操作對象一樣操作;
使用最多的GCD
:替代NSThread的多線程API,可以充分利用多核技術;執行的子線程任務都在塊裏(block);其生命週期自行維護;
NSOperation:是基於GCD的面向對象的封裝,比起gcd使用更加簡單,同時也增加了一些實用功能;
2. GCD 同步,異步,串行,並行
2.1 從一到面試題入手認識下GCD:
下面代碼會發生什麼?
NSLog(@"步驟1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"步驟2");
});
NSLog(@"步驟3");
答案是:輸出步驟1 後程序會崩潰;看一下實際運行後的截圖:
dispatch_get_main_queue()獲取的是主隊列,主隊列等同於串行隊列;先來看下iOS中多線程的四個名詞:
2.2 多線程易混淆的名詞
同步sync,異步async,並行隊列Concurrent Dispatch Queue,串行隊列Serial Dispatch Queue;
- 並行隊列Concurrent Dispatch Queue:可以讓多個任務同時併發執行,並同時開啓新的線程,併發隊列只有在異步任務下才有效;
- 串行隊列Serial Dispatch Queue:一個任務執行完成後才能執行下一個任務;
併發隊列 | 串行隊列 | 主隊列 | |
---|---|---|---|
sync | 沒有開啓新線程,串行執行任務 | 沒有開啓新線程,串行執行任務 | 沒有開啓新線程,串行執行任務 |
async | 有開啓新線程,併發執行任務 | 有開啓新線程,串行執行任務 | 沒有開啓新線程,串行執行任務 |
通過代碼對上表進行驗證下:
dispatch_queue_t serialQue = dispatch_queue_create("com.lym.serial", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrentQue = dispatch_queue_create("com.lym.concurrent", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"步驟0");
dispatch_async(serialQue, ^{
NSLog(@"串行隊列 異步任務 1");
});
dispatch_async(serialQue, ^{
NSLog(@"串行隊列 異步任務 2");
});
dispatch_async(serialQue, ^{
NSLog(@"串行隊列 異步任務 3");
});
NSLog(@"------------------------");
dispatch_async(concurrentQue, ^{
NSLog(@"併發隊列 異步任務 1");
});
dispatch_async(concurrentQue, ^{
NSLog(@"併發隊列 異步任務 2");
});
dispatch_async(concurrentQue, ^{
NSLog(@"併發隊列 異步任務 3");
});
NSLog(@"------------------------");
dispatch_sync(concurrentQue, ^{
NSLog(@"併發隊列 同步任務 1");
});
dispatch_sync(concurrentQue, ^{
NSLog(@"併發隊列 同步任務 2");
});
dispatch_sync(concurrentQue, ^{
NSLog(@"併發隊列 同步任務 3");
});
NSLog(@"------------------------");
dispatch_sync(serialQue, ^{
NSLog(@"串行隊列 同步任務 1");
});
dispatch_sync(serialQue, ^{
NSLog(@"串行隊列 同步任務 2");
});
dispatch_sync(serialQue, ^{
NSLog(@"串行隊列 同步任務 3");
});
NSLog(@"步驟2");
輸出結果:
2.3 面試題解析
下面再來分析下鎖住的原因:iOS中函數默認都是運行在主線程中,而主線程所在的主隊列是串行隊列;那麼上述函數中在主隊列中有一個任務(viewDidLoad)開始執行,當執行到dispatch_sync
的時候串行隊列中有多了個同步任務,那麼上一個任務(viewDidLoad)等待該任務執行完才能接着執行,但是因爲是同步線程,dispatch_sync的任務需要等到上一個任務執行完成才能執行;這樣就造成兩個任務相互等待對方執行完成,從而造成死鎖;解決方式有以下兩種:
a,主隊列中異步執行:
NSLog(@"步驟1");
dispatch_queue_t mianQue = dispatch_get_main_queue();
dispatch_async(mianQue, ^{
NSLog(@"步驟2");
});
NSLog(@"步驟3");
//2019-09-28 10:16:07.384373+0800 testThread[20853:156963] 步驟1
//2019-09-28 10:16:07.384633+0800 testThread[20853:156963] 步驟2
//2019-09-28 10:16:07.384782+0800 testThread[20853:156963] 步驟3
b, 新隊列中執行:
NSLog(@"步驟1");
dispatch_queue_t que = dispatch_queue_create("kkk", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(que, ^{//異步這裏就不再測試
NSLog(@"步驟2");
});
NSLog(@"步驟3");
//2019-09-28 10:16:07.384373+0800 testThread[20853:156963] 步驟1
//2019-09-28 10:16:07.384633+0800 testThread[20853:156963] 步驟2
//2019-09-28 10:16:07.384782+0800 testThread[20853:156963] 步驟3
**
注意:使用sync在當前串行隊列中添加任務,會是當前隊列產生死鎖;
**
3 多人線程與runloop
3.1 面試題1
NSLog(@"步驟1");
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"步驟2");
});
NSLog(@"步驟3");
- (void)test{
NSLog(@"%s",__func__);
}
- 執行結果是:
2019-09-28 10:56:17.360642+0800 testThread[27068:210230] 步驟1
2019-09-28 10:56:17.360920+0800 testThread[27068:210230] 步驟3
2019-09-28 10:56:17.361117+0800 testThread[27068:210325] 步驟2
從上面打印可以看出並沒有執行test()方法;
- 分析:
如果沒有afterDelay:那麼該方法可以在objc源碼裏找到實現,其底層最終調用了msgSend;而有afterDelay:後在Runloop.h中,其底層實現沒有開源,但是可以通過相似的開源項目GNUSetup,知道其底層是添加了一個定時器;而子線程中的定時器是依靠runloop,如果沒有獲取過,子線程是沒有runloop;所以不會去執行test方法。 - 解決方法
NSLog(@"步驟1");
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
NSLog(@"步驟2");
});
NSLog(@"步驟3");
- (void)test{
NSLog(@"%s",__func__);
CFRunLoopStop(CFRunLoopGetCurrent());
}
2019-09-28 11:03:08.085945+0800 testThread[28275:221117] 步驟1
2019-09-28 11:03:08.086135+0800 testThread[28275:221117] 步驟3
2019-09-28 11:03:08.086765+0800 testThread[28275:221198] -[ViewController test]
2019-09-28 11:03:08.087350+0800 testThread[28275:221198] 步驟2
3.2 面試題2
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"步驟0");
NSThread *curentThres = [[NSThread alloc]initWithBlock:^{
NSLog(@"步驟1");
}];
[curentThres start];
NSLog(@"步驟2");
[self performSelector:@selector(test) onThread:curentThres withObject:nil waitUntilDone:YES];
}
運行結果如下:
2019-09-28 11:16:04.951807+0800 testThread[30020:237237] 步驟0
2019-09-28 11:16:04.952187+0800 testThread[30020:237237] 步驟2
2019-09-28 11:16:04.954974+0800 testThread[30020:237393] 步驟1
2019-09-28 11:16:04.980856+0800 testThread[30020:237237] *** Terminating app due to uncaught exception 'NSDestinationInvalidException', reason: '*** -[ViewController performSelector:onThread:withObject:waitUntilDone:modes:]: target thread exited while waiting for the perform'
原因是在執行performSelector的時候curentThres在執行完一個任務後已經銷燬了,解決方法也是runloop:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"步驟0");
NSThread *curentThres = [[NSThread alloc]initWithBlock:^{
NSLog(@"步驟1");
//不添加下面這一句會崩潰
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}];
[curentThres start];
NSLog(@"步驟2");
[self performSelector:@selector(test) onThread:curentThres withObject:nil waitUntilDone:YES];
}
4 隊列組
多個併發的任務執行完成後再執行另一個任務,最經典的就是多線程下載的應用;使用很簡單;我們直接看源碼實例:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t concurrentQue = dispatch_queue_create("com.lym.concurrentGroup", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"步驟0");
dispatch_group_async(group,concurrentQue, ^{
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"併發隊列組 異步任務 1");
});
dispatch_group_async(group,concurrentQue, ^{
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"併發隊列組 異步任務 2");
});
dispatch_group_async(group,concurrentQue, ^{
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"併發隊列組 異步任務 3");
});
dispatch_group_notify(group, concurrentQue, ^{
NSLog(@"全部執行完成");
});
NSLog(@"步驟2");
2019-09-28 11:31:12.132972+0800 testThread[32679:259275] 步驟0
2019-09-28 11:31:12.133151+0800 testThread[32679:259275] 步驟2
2019-09-28 11:31:14.135128+0800 testThread[32679:259330] 併發隊列組 異步任務 2
2019-09-28 11:31:14.135159+0800 testThread[32679:259331] 併發隊列組 異步任務 3
2019-09-28 11:31:14.135147+0800 testThread[32679:259329] 併發隊列組 異步任務 1
2019-09-28 11:31:14.135595+0800 testThread[32679:259331] 全部執行完成
5 線程安全問題
5.1 異常例子
#import "LYMBaseThreadTast.h"
@interface LYMBaseThreadTast ()
@property(nonatomic,assign)NSInteger cont;
@end
@implementation LYMBaseThreadTast
/*****售票問題******/
-(void)scaleTicket{
int count = (int)_cont;
count = count - 1;;
sleep(.3);
self.cont = count;
NSLog(@"還剩%ld currenthread:%@",_cont,[NSThread currentThread]);
}
-(void)scaleTicketsTask{
self.cont = 20;
dispatch_queue_t scaleTicketQue = dispatch_queue_create("com.lym.scaleTicket", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(scaleTicketQue, ^{
for (int i = 10; i > 0; i--) {
[self scaleTicket];
}
});
dispatch_async(scaleTicketQue, ^{
for (int i = 5; i > 0; i--) {
[self scaleTicket];
}
});
dispatch_async(scaleTicketQue, ^{
for (int i = 5; i > 0; i--) {
[self scaleTicket];
}
});
}
/*****end****/
@end
輸出:
2019-09-28 11:58:44.783825+0800 testThread[36523:296048] 還剩19 currenthread:<NSThread: 0x60000065e400>{number = 3, name = (null)}
2019-09-28 11:58:44.783831+0800 testThread[36523:296045] 還剩19 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.783932+0800 testThread[36523:296046] 還剩19 currenthread:<NSThread: 0x6000006691c0>{number = 4, name = (null)}
2019-09-28 11:58:44.784113+0800 testThread[36523:296046] 還剩18 currenthread:<NSThread: 0x6000006691c0>{number = 4, name = (null)}
2019-09-28 11:58:44.784113+0800 testThread[36523:296048] 還剩18 currenthread:<NSThread: 0x60000065e400>{number = 3, name = (null)}
2019-09-28 11:58:44.784116+0800 testThread[36523:296045] 還剩18 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.784242+0800 testThread[36523:296046] 還剩17 currenthread:<NSThread: 0x6000006691c0>{number = 4, name = (null)}
2019-09-28 11:58:44.784246+0800 testThread[36523:296048] 還剩17 currenthread:<NSThread: 0x60000065e400>{number = 3, name = (null)}
2019-09-28 11:58:44.784254+0800 testThread[36523:296045] 還剩16 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.784345+0800 testThread[36523:296046] 還剩15 currenthread:<NSThread: 0x6000006691c0>{number = 4, name = (null)}
2019-09-28 11:58:44.785462+0800 testThread[36523:296048] 還剩14 currenthread:<NSThread: 0x60000065e400>{number = 3, name = (null)}
2019-09-28 11:58:44.785538+0800 testThread[36523:296045] 還剩13 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.785745+0800 testThread[36523:296046] 還剩12 currenthread:<NSThread: 0x6000006691c0>{number = 4, name = (null)}
2019-09-28 11:58:44.785979+0800 testThread[36523:296048] 還剩11 currenthread:<NSThread: 0x60000065e400>{number = 3, name = (null)}
2019-09-28 11:58:44.786492+0800 testThread[36523:296045] 還剩10 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.786959+0800 testThread[36523:296045] 還剩9 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.787282+0800 testThread[36523:296045] 還剩8 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.791496+0800 testThread[36523:296045] 還剩7 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.791644+0800 testThread[36523:296045] 還剩6 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.791770+0800 testThread[36523:296045] 還剩5 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
從上面的例子裏可以看到,當多個線程同時讀寫一個變量的時候就會出現異常;
5.2 異常例子 解決方法:
5.2.1 自旋鎖 OSSpinLock
將上述方法修改如下:
#import "LYMOSSpinLockThreadTast.h"
#import <libkern/OSAtomic.h>
@interface LYMOSSpinLockThreadTast ()
@property(nonatomic,assign)OSSpinLock lock;
@end
@implementation LYMOSSpinLockThreadTast
-(instancetype)init{
if (self=[super init]) {
self.lock = OS_SPINLOCK_INIT;
}
return self;
}
/*****售票問題 自旋鎖解決方式******/
-(void)scaleTicket{
OSSpinLockLock(&_lock);//自旋鎖加鎖
[super scaleTicket];
OSSpinLockUnlock(&_lock);//自旋鎖解鎖
}
/*****end****/
@end
/*****end****/
輸出:
2019-12-01 13:00:45.190729+0800 還剩19 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.191271+0800 還剩18 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.191511+0800 還剩17 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.193041+0800 還剩16 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.193231+0800 還剩15 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.193416+0800 還剩14 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.193586+0800 還剩13 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.193772+0800 還剩12 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.194042+0800 還剩11 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.198458+0800 還剩10 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.199288+0800 還剩9 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a2af40>{number = 4, name = (null)}
2019-12-01 13:00:45.199431+0800 還剩8 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a2af40>{number = 4, name = (null)}
2019-12-01 13:00:45.199560+0800 還剩7 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a2af40>{number = 4, name = (null)}
2019-12-01 13:00:45.199700+0800 還剩6 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a2af40>{number = 4, name = (null)}
2019-12-01 13:00:45.199816+0800 還剩5 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a2af40>{number = 4, name = (null)}
2019-12-01 13:00:45.203833+0800 還剩4 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a10080>{number = 5, name = (null)}
2019-12-01 13:00:45.204001+0800 還剩3 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a10080>{number = 5, name = (null)}
2019-12-01 13:00:45.204128+0800 還剩2 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a10080>{number = 5, name = (null)}
2019-12-01 13:00:45.204258+0800 還剩1 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a10080>{number = 5, name = (null)}
2019-12-01 13:00:45.204381+0800 還剩0 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a10080>{number = 5, name = (null)}
自旋鎖實現大致是一個循環不斷去判斷是不是完成任務,就是說線程會一直佔用cpu資源即:忙等;假如這個等待的線程是級別比較高的線程,那麼線程調度時候就會導致其他線程無法執行;及線程等級反轉。
所以蘋果已經不推薦使用自旋鎖;因此推薦使用os_unfair_lock:
#import "LYMOSUnfairLockThreadTast.h"
#import <os/lock.h>
@interface LYMOSUnfairLockThreadTast ()
@property (assign, nonatomic) os_unfair_lock ticketLock;
@end
@implementation LYMOSUnfairLockThreadTast
/*****售票問題 os_unfair_lock #import <os/lock.h>******/
-(instancetype)init{
if (self=[super init]) {
self.ticketLock = OS_UNFAIR_LOCK_INIT;
}
return self;
}
-(void)scaleTicket{
os_unfair_lock_lock(&_ticketLock);
[super scaleTicket];
os_unfair_lock_unlock(&_ticketLock);//解鎖
}
/*****end****/
@end
5.2.2 pthread_mutex_t 互斥鎖
#import "LYMOSPThreadTast.h"
#import <pthread.h>
@interface LYMOSPThreadTast ()
@property(nonatomic,assign)pthread_mutex_t mutex_lock;//互斥鎖
@end
@implementation LYMOSPThreadTast
/*****售票問題 os_unfair_lock #import <os/lock.h>******/
-(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_lock), &attr);//第二個參數默認可以爲NULL
}
return self;
}
-(void)scaleTicket{
pthread_mutex_lock(&(_mutex_lock));
[super scaleTicket];
pthread_mutex_unlock(&(_mutex_lock));//解鎖
}
-(void)dealloc{
pthread_mutex_destroy(&(_mutex_lock));
}
/*****end****/
@end
上述代碼中加鎖的代碼中如果有調用了使用同一把鎖加鎖的代碼,就會造成死鎖,這時候應該使用遞歸鎖pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);//遞歸鎖
;
在加鎖與解鎖成對的線程中,遞歸鎖允許對同一把鎖重複加鎖
5.2.2 NSCondition(條件鎖),NSConditionLock(條件鎖的封裝)
//
// LYMNSCordition.m
// testThread
//
// Created by ymluo on 2019/12/6.
// Copyright © 2019 ymluo. All rights reserved.
//
#import "LYMNSCordition.h"
@interface LYMNSCordition()
@property (strong, nonatomic)NSCondition *condition;
@property (strong, nonatomic) NSMutableArray *data;
@end
@implementation LYMNSCordition
- (instancetype)init
{
if (self = [super init]) {
//當內部的d條件值等於1的時候,才能加鎖
self.condition = [[NSCondition alloc] init];
self.data = [NSMutableArray array];
}
return self;
}
- (void)otherTest
{
// [[[NSThread alloc] initWithTarget:self selector:@selector(_one) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
}
- (void)__two
{
[_condition lock];
NSLog(@"__fnc:%s 2",__func__);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"__fnc:%s 2 sleep",__func__);
sleep(2);
[self.condition signal];
NSLog(@"__fnc:%s 2 sleep untile",__func__);
});
[_condition wait];
[_condition unlock];
NSLog(@"__fnc:%s 2 end function",__func__);
}
- (void)__one
{
[_condition lock];
NSLog(@"__fnc:%s 1",__func__);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"__fnc:%s 1 sleep",__func__);
sleep(2);
[self.condition signal];
NSLog(@"__fnc:%s 1 sleep untile",__func__);
});
[_condition wait];
[_condition unlock];
[self __two];
}
@end
// 輸出
2019-12-06 09:01:21.794134+0800 __fnc:-[LYMNSCordition __one] 1
2019-12-06 09:01:21.806155+0800 __fnc:-[LYMNSCordition __one]_block_invoke 1 sleep
2019-12-06 09:01:23.806536+0800 __fnc:-[LYMNSCordition __one]_block_invoke 1 sleep untile
2019-12-06 09:01:23.806533+0800 __fnc:-[LYMNSCordition __two] 2
2019-12-06 09:01:23.808100+0800 __fnc:-[LYMNSCordition __two]_block_invoke 2 sleep
2019-12-06 09:01:25.809708+0800 __fnc:-[LYMNSCordition __two]_block_invoke 2 sleep untile
2019-12-06 09:01:25.809708+0800 __fnc:-[LYMNSCordition __two] 2 end function
//
// LYMNsconditionLock.m
// testThread
//
// Created by ymluo on 2019/12/6.
// Copyright © 2019 ymluo. All rights reserved.
//
#import "LYMNsconditionLock.h"
@interface LYMNsconditionLock()
@property (strong, nonatomic)NSConditionLock *conditionLock;
@property (strong, nonatomic) NSMutableArray *data;
@end
@implementation LYMNsconditionLock
- (instancetype)init
{
if (self = [super init]) {
//當內部的d條件值等於1的時候,才能加鎖
self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
self.data = [NSMutableArray array];
}
return self;
}
- (void)otherTest
{
[[[NSThread alloc] initWithTarget:self selector:@selector(__third) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(_one) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
}
/**
因爲加鎖的內部條件是condition等於1 所以該線程的方法會等待線程2的__two執行完畢
*/
- (void)_one
{
[self.conditionLock lockWhenCondition:2];
NSLog(@"__fnc:%s 2",__func__);
sleep(1);
[self.conditionLock unlockWithCondition:3];
}
/**
因爲加鎖的內部條件是condition等於1 所以該線程的方法會最先執行
*/
- (void)__two
{
[self.conditionLock lockWhenCondition:1];
NSLog(@"__fnc:%s 1",__func__);
dispatch_async(dispatch_get_main_queue(), ^{
});
sleep(1);
[self.conditionLock unlockWithCondition:2];
}
/**
因爲加鎖的內部條件是condition等於1 所以該線程的方法會等到 1和2都執行完畢且condition的值等於開始執行下一個 方法
*/
- (void)__third{
[self.conditionLock lockWhenCondition:3];
NSLog(@"__fnc:%s 3",__func__);
sleep(1);
[self.conditionLock unlockWithCondition:3];
}
@end
2019-12-06 09:07:16.887674+0800 __fnc:-[LYMNsconditionLock __two] 1
2019-12-06 09:07:17.893224+0800 __fnc:-[LYMNsconditionLock _one] 2
2019-12-06 09:07:18.898025+0800 __fnc:-[LYMNsconditionLock __third] 3
5.3 dispatch_semaphore 信號量
信號量可以控制同時最大併發數,也可當做線程鎖使用;
- 1: 線程數控制:
#pragma mark - 控制同時執行線程數(控制最大併發數)
- (void)otherTestMaxThred{
for (NSInteger i = 30; i > 0; i--) {
NSThread *thread = [[NSThread currentThread]initWithTarget:self selector:@selector(__thestThred:) object:@(i)];
[thread start];
}
}
- (void)__thestThred:(id)obj{
// 如果信號量的值 > 0,那麼就讓信號量的值減一,且繼續向下執行;
// 如果 <= o,則等待(線程休眠等待),直到dispatch_semaphore_signal()讓信號量的值 +1,就讓信號量的值 -1,然後繼續執行代碼。
dispatch_semaphore_wait(_dispatchSemapthore, DISPATCH_TIME_FOREVER);
sleep(2);
NSLog(@"thread:%@ i:%@",[NSThread currentThread],obj);
// 讓信號量的值 +1;
dispatch_semaphore_signal(_dispatchSemapthore);
}
- 2 同步鎖
#import "LYMSemphoreTest.h"
@interface LYMSemphoreTest ()
@property (nonatomic, strong) dispatch_semaphore_t dispatchSemapthore; //!<信號量
@end
@implementation LYMSemphoreTest
-(instancetype)init{
if (self = [super init]) {
self.dispatchSemapthore = dispatch_semaphore_create(1);
}
return self;
}
-(void)scaleTicket{
// 如果信號量的值 > 0,那麼就讓信號量的值減一,且繼續向下執行;
// 如果 <= o,則等待(線程休眠等待),直到dispatch_semaphore_signal()讓信號量的值 +1,就讓信號量的值 -1,然後繼續執行代碼。
dispatch_semaphore_wait(_dispatchSemapthore, DISPATCH_TIME_FOREVER);
[super scaleTicket];
// 讓信號量的值 +1;
dispatch_semaphore_signal(_dispatchSemapthore);
}
@end
2019-12-08 11:43:20.307289+0800 還剩19 currenthread:<NSThread: 0x60000201cd40>{number = 3, name = (null)}
2019-12-08 11:43:20.307705+0800 還剩18 currenthread:<NSThread: 0x60000202cec0>{number = 4, name = (null)}
。。。。。
2019-12-08 11:43:20.320493+0800 還剩1 currenthread:<NSThread: 0x60000201cd40>{number = 3, name = (null)}
2019-12-08 11:43:20.320789+0800 還剩0 currenthread:<NSThread: 0x60000201cd40>{number = 3, name = (null)}
- 3 子線程多任務同步
/**
線程中等待子線程執行返回後繼續執行任務
*/
- (void)otherTestThred{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self __thestThred1:@(0)];
});
}
- (void)__thestThred1:(id)obj{
NSLog(@"===begin thread:%@ i:%@",[NSThread currentThread],obj);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"thread 1:%@ i:%@",[NSThread currentThread],obj);
sleep(2);
NSLog(@"thread 2:%@ i:%@",[NSThread currentThread],obj);
// 讓信號量的值 +1;
dispatch_semaphore_signal(self.dispatchSemapthore);
});
dispatch_semaphore_wait(_dispatchSemapthore, DISPATCH_TIME_FOREVER);
NSLog(@"===end thread:%@ i:%@",[NSThread currentThread],obj);
輸出:
2019-12-08 11:48:44.393387+0800 ===begin thread:<NSThread: 0x6000021eee40>{number = 3, name = (null)} i:0
2019-12-08 11:48:44.393922+0800 thread 1:<NSThread: 0x6000021d5c00>{number = 4, name = (null)} i:0
2019-12-08 11:48:46.396430+0800 thread 2:<NSThread: 0x6000021d5c00>{number = 4, name = (null)} i:0
2019-12-08 11:48:46.396631+0800 ===end thread:<NSThread: 0x6000021eee40>{number = 3, name = (null)} i:0
5.4 @synchronized
是對mutex遞歸鎖的封裝,參數self,在底層中將其地址作爲key去取出對應的鎖;
@synchronized (self) {
// Task
}
6 鎖性能問題
性能由高到低依次如下:
os_unfair_lock:性能最高,
OSSpinLock:預計線程等待時間很短的時候;cpu資源足夠;很少發生競爭;
dispatch_semaphore:
pthread_mutex:
dispatch_queue(DISPATCH_QUEUE_SERIAL);//同步隊列
NSLock
NSCondition
pthread_mutex():recursive//遞歸鎖
NSRecuriseiveLock
NSConditionLock
@synchronized
7 IO 操作安全問題
多讀單寫:pthread_rwlock 等待的鎖會進入 休眠
@interface LYMIOSafeTest ()
//@property (nonatomic, strong) dispatch_semaphore_t dispatchSemapthore; //!<信號量
//@property (nonatomic, copy) NSString *filePath; //!<<#註釋#>
@property (nonatomic, assign) pthread_rwlock_t rwLock; //!<信號量
@property (nonatomic, copy) NSString *filePath; //!<<#註釋#>
@end
@implementation LYMIOSafeTest
-(instancetype)init{
if (self = [super init]) {
pthread_rwlock_init(&(_rwLock), NULL);
NSString *documetPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
self.filePath = [documetPath stringByAppendingPathComponent:@"testFile.txt"];
}
return self;
}
-(void)otherTest{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (NSInteger i = 0; i < 5; i++) {
dispatch_async(queue, ^{
[self __writeileThred:@(i)];
});
dispatch_async(queue, ^{
[self __readFileThred:@(i)];
});
}
}
- (void)__readFileThred:(id)obj{
pthread_rwlock_rdlock(&_rwLock);
if ([[NSFileManager defaultManager] fileExistsAtPath:_filePath]) {
NSString *resultStr = [NSString stringWithContentsOfFile:_filePath encoding:NSUTF8StringEncoding error:nil];
NSLog(@"__readFileThred=====:str= %@",resultStr);
}
sleep(1);
pthread_rwlock_unlock(&_rwLock);
}
- (void)__writeileThred:(id)obj{
pthread_rwlock_wrlock(&_rwLock);
// 字符串讀取的方法
if ([obj integerValue] == 0) {
if ([[NSFileManager defaultManager] fileExistsAtPath:_filePath]) {
[[NSFileManager defaultManager] removeItemAtPath:_filePath error:nil];
}
}
if (![[NSFileManager defaultManager] fileExistsAtPath:_filePath]) {
[[NSFileManager defaultManager] createFileAtPath:_filePath contents:nil attributes:nil];
}
NSFileHandle *handler = [NSFileHandle fileHandleForUpdatingAtPath:_filePath];
[handler seekToEndOfFile];
[handler writeData:[[NSString stringWithFormat:@"\n線程開始寫入數據:%@",obj] dataUsingEncoding:NSUTF8StringEncoding]];
NSLog(@"__writeileThred thread:%@",[NSThread currentThread]);
sleep(1);
pthread_rwlock_unlock(&_rwLock);
}
-(void)dealloc{
pthread_rwlock_destroy(&_rwLock);
}
輸出:
2019-12-08 14:33:15.376561+0800 __writeileThred thread:<NSThread: 0x600002b4ddc0>{number = 3, name = (null)}
2019-12-08 14:33:16.382122+0800 __readFileThred=====:str=
線程開始寫入數據:0
2019-12-08 14:33:16.382244+0800 __readFileThred=====:str=
線程開始寫入數據:0
2019-12-08 14:33:17.385887+0800 __writeileThred thread:<NSThread: 0x600002b7d640>{number = 4, name = (null)}
2019-12-08 14:33:18.392534+0800 __writeileThred thread:<NSThread: 0x600002b4dfc0>{number = 5, name = (null)}
2019-12-08 14:33:19.395925+0800 __readFileThred=====:str=
線程開始寫入數據:0
線程開始寫入數據:1
線程開始寫入數據:2
2019-12-08 14:33:20.401841+0800 __writeileThred thread:<NSThread: 0x600002b4df40>{number = 6, name = (null)}
2019-12-08 14:33:21.409091+0800 __readFileThred=====:str=
線程開始寫入數據:0
線程開始寫入數據:1
線程開始寫入數據:2
線程開始寫入數據:3
2019-12-08 14:33:22.413197+0800 __writeileThred thread:<NSThread: 0x600002b7c640>{number = 7, name = (null)}
2019-12-08 14:33:23.417572+0800 __readFileThred=====:str=
線程開始寫入數據:0
線程開始寫入數據:1
線程開始寫入數據:2
線程開始寫入數據:3
線程開始寫入數據:4
從輸出中可以看出讀可以多個同時執行,而寫同時只能存在一個;