測(cè)試代碼MultiThread
NSOperation和GCD對(duì)比
兩者的對(duì)比箫踩,區(qū)別在一下這些方面:
- 任務(wù)之間添加依賴關(guān)系的不同
- NSOperation可以監(jiān)控任務(wù)的各種狀態(tài)并且可以實(shí)現(xiàn)取消
- NSOperation可以進(jìn)行繼承和封裝,從而實(shí)現(xiàn)代碼重用谭贪,并且邏輯規(guī)整境钟。
- GCD使用更快速方便,使用于簡(jiǎn)單臨時(shí)的任務(wù)
<br />
1. 添加依賴
1.1 NSOperation的依賴
首先實(shí)現(xiàn)一個(gè)自定義的NSOperation
,頭文件:
#import <Foundation/Foundation.h>
@interface TFTestOperation : NSOperation
@property (nonatomic, copy) NSString *taskPath;
@property (nonatomic, copy) void(^exeCompleteHandler)(NSData *data);
@property (nonatomic, copy) void(^freeHandler)(TFTestOperation *freeOperation);
@end
實(shí)現(xiàn)文件:
#import "TFTestOperation.h"
@interface TFTestOperation (){
NSURLSessionTask *_task;
}
@property (atomic, assign) BOOL taskExecuting;
@property (atomic, assign) BOOL taskFinished;
@end
@implementation TFTestOperation
-(void)start{
NSURL *url = [[NSURL alloc] initWithString:_taskPath];
if (url == nil) {
_exeCompleteHandler(nil);
return;
}
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:100];
_task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"%@",error);
}
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_taskExecuting = NO;
_taskFinished = YES;
NSLog(@"%@ data length: %lu",self.name,data.length);
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
_exeCompleteHandler(data);
}];
[_task resume];
_taskExecuting = YES;
NSLog(@"operation %@ started",self.name);
}
-(void)cancel{
[super cancel];
[_task cancel];
}
-(BOOL)isAsynchronous{
return YES;
}
-(BOOL)isExecuting{
return _taskExecuting;
}
-(BOOL)isFinished{
return _taskFinished;
}
-(void)dealloc{
if (self.freeHandler) {
self.freeHandler(self);
}
}
然后測(cè)試任務(wù)之間的依賴:
-(void)testDependency_operation{
TFTestOperation *operation1 = [[TFTestOperation alloc] init];
operation1.taskPath = kImagePath1;
operation1.name = @"operation1";
operation1.exeCompleteHandler = ^(NSData *data) {
dispatch_async(dispatch_get_main_queue(), ^{
_firstImgView.image = [UIImage imageWithData:data];
});
};
TFTestOperation *operation2 = [[TFTestOperation alloc] init];
operation2.name = @"operation2";
operation2.taskPath = kImagePath2;
operation2.exeCompleteHandler = ^(NSData *data) {
dispatch_async(dispatch_get_main_queue(), ^{
_secondImgView.image = [UIImage imageWithData:data];
});
};
[operation2 addDependency:operation1];
//NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// [queue addOperation:operation1];
// [queue addOperation:operation2];
_operationQueue = [[NSOperationQueue alloc] init];
[_operationQueue addOperation:operation1];
[_operationQueue addOperation:operation2];
}
TFTestOperation
實(shí)際就干了一個(gè)下載文件的任務(wù)俭识,這里operation1
和operation2
都下載一個(gè)圖片慨削,但是通過[operation2 addDependency:operation1];
讓任務(wù)2依賴任務(wù)1,這樣在operation1
執(zhí)行完之后operation2
才會(huì)執(zhí)行套媚。
但是什么時(shí)候operation1
執(zhí)行完成缚态?
這就要回到TFTestOperation
的實(shí)現(xiàn)里去,有個(gè)-(BOOL)isFinished
的方法堤瘤,就是它起作用玫芦。但是這個(gè)方法是不會(huì)自己調(diào)用的,在你的任務(wù)結(jié)束的時(shí)候本辐,你需要通知外界桥帆,你的任務(wù)完成了医增。
所以在下載完成后有[self didChangeValueForKey:@"isFinished"];
等一長串代碼,使用KVO的手段通知外界老虫。
我的猜測(cè)是:當(dāng)ope2依賴了ope1,那么ope2就會(huì)對(duì)ope1的isFinished
屬性進(jìn)行KVO的監(jiān)聽叶骨,當(dāng)ope1發(fā)出通知isFinished
改變,那么ope2得知后,就會(huì)去調(diào)用ope1的-(BOOL)isFinished
方法,如果真的完成了漂坏,那么就輪到它執(zhí)行了。
這也是為什么文檔里指出isFinished
是必須被重載的一個(gè)屬性吧缔恳。
如果是多個(gè)依賴,也就是加多個(gè)dependency的問題洁闰,本質(zhì)不變。
<br />
GCD的依賴
在我看來万细,“依賴關(guān)系”就是一個(gè)任務(wù)需要等另一個(gè)任務(wù)結(jié)束才執(zhí)行扑眉,或者等另外n個(gè)任務(wù)結(jié)束才執(zhí)行。對(duì)于GCD里赖钞,我想到的就是dispatch_group_xxx
這系列的方法了
-(void)testMultiDependencies_GCD{
dispatch_group_t group = dispatch_group_create();
int count = 100;
for (int i = 0; i<count; i++) {
dispatch_group_enter(group);
NSString *taskPath = kImagePath2;
NSURL *url = [[NSURL alloc] initWithString:taskPath];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:100];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"%@",error);
}
dispatch_async(dispatch_get_main_queue(), ^{
_firstImgView.image = [UIImage imageWithData:data];
NSLog(@"image %d",i);
});
dispatch_group_leave(group);
}];
[task resume];
}
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSString *taskPath = kImagePath1;
NSURL *url = [[NSURL alloc] initWithString:taskPath];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:100];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"%@",error);
}
dispatch_async(dispatch_get_main_queue(), ^{
_secondImgView.image = [UIImage imageWithData:data];
});
}];
[task resume];
});
}
- 新建一個(gè)
dispatch_group_t
- 在一個(gè)任務(wù)開始的時(shí)候
dispatch_group_enter(group)
- 在一個(gè)任務(wù)結(jié)束的時(shí)候
dispatch_group_leave(group)
- 把最后想要執(zhí)行的任務(wù)放在
dispatch_group_notify
里腰素,當(dāng)所有的enter和leave對(duì)等的時(shí)候,就會(huì)執(zhí)行雪营。
如果只是一次依賴弓千,那么dispatch_group
也不麻煩,就怕多個(gè)依賴献起,比如A依賴B洋访,B又依賴C,C再依賴D和F谴餐,這對(duì)于NSOperation來說就是很簡(jiǎn)單的事姻政,但對(duì)GCD就麻煩了。
- 每一個(gè)依賴環(huán)節(jié)都需要一個(gè)group岂嗓,需要一系列的處理
- 然后一個(gè)致命的問題是汁展,A依賴B,group leave的代碼卻需要插入到B的邏輯代碼里面去厌殉,假如你已經(jīng)寫好了A依賴B食绿,又來個(gè)C依賴B,那么又要去動(dòng)B的代碼公罕,而NSOperation就化解了這個(gè)問題:B執(zhí)行完成就發(fā)個(gè)KVO通知器紧,需要知道完沒完成的自己去檢查吧。這樣就把問題丟回給你依賴者熏兄,它自身的部分可以保持恒定不變品洛。
<br />
2. 對(duì)線程任務(wù)的狀態(tài)監(jiān)測(cè)和控制
NSOperation可以監(jiān)測(cè)狀態(tài)树姨,但GCD沒有;NSOperation可以取消桥状,而GCD只能暫停沒執(zhí)行的任務(wù)帽揪。
2.1 NSOperation狀態(tài)監(jiān)測(cè)
使用KVO監(jiān)測(cè):
-(void)testStateControl_operation{
_operationQueue = [[NSOperationQueue alloc] init];
TFTestOperation *operation1 = [[TFTestOperation alloc] init];
operation1.taskPath = kFilePath1;
operation1.name = @"1";
operation1.exeCompleteHandler = ^(NSData *data) {
NSLog(@"file download finished, data size:%ld",data.length);
};
operation1.freeHandler = ^(TFTestOperation *freeOperation) {
[freeOperation removeObserver:self forKeyPath:@"isFinished"];
[freeOperation removeObserver:self forKeyPath:@"isExecuting"];
[freeOperation removeObserver:self forKeyPath:@"isReady"];
};
[operation1 addObserver:self forKeyPath:@"isFinished" options:NSKeyValueObservingOptionNew context:nil];
[operation1 addObserver:self forKeyPath:@"isExecuting" options:NSKeyValueObservingOptionNew context:nil];
[operation1 addObserver:self forKeyPath:@"isReady" options:NSKeyValueObservingOptionNew context:nil];
TFTestOperation *operation2 = [[TFTestOperation alloc] init];
operation2.taskPath = kImagePath1;
operation2.name = @"2";
operation2.exeCompleteHandler = ^(NSData *data) {
NSLog(@"file download finished, data size:%ld",data.length);
};
operation2.freeHandler = ^(TFTestOperation *freeOperation) {
[freeOperation removeObserver:self forKeyPath:@"isFinished"];
[freeOperation removeObserver:self forKeyPath:@"isExecuting"];
[freeOperation removeObserver:self forKeyPath:@"isReady"];
};
[operation2 addObserver:self forKeyPath:@"isFinished" options:NSKeyValueObservingOptionNew context:nil];
[operation2 addObserver:self forKeyPath:@"isExecuting" options:NSKeyValueObservingOptionNew context:nil];
[operation2 addObserver:self forKeyPath:@"isReady" options:NSKeyValueObservingOptionNew context:nil];
[operation2 addDependency:operation1];
[_operationQueue addOperation:operation1];
[_operationQueue addOperation:operation2];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"cancel");
[operation1 cancel];
});
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([object isKindOfClass:[NSOperation class]]) {
NSLog(@"%@ %@: %@",((NSOperation *)object).name,keyPath,[[change objectForKey:NSKeyValueChangeNewKey] boolValue]?@"YES":@"NO");
}
}
- 建了operation1和operation2兩個(gè)任務(wù)執(zhí)行辅斟,并且operation2依賴operation1執(zhí)行完成转晰。
- 在任務(wù)開始3秒后把operation1取消掉
- 觀察的keyPath不是屬性名,而是getter方法名稱
- 通過log可以看到3個(gè)值的變化士飒,
[operation1 cancel]
取消operation1后查邢,operation2立馬執(zhí)行了。
關(guān)于使用KVO的一個(gè)注意點(diǎn)酵幕,operation執(zhí)行完扰藕,它就被釋放了,然后
self
還Observer著它芳撒,自然會(huì)奔潰邓深,而且這個(gè)釋放點(diǎn)外界很難抓。所以我做了個(gè)處理笔刹,給TFTestOperation
添加了一個(gè)freeHandler
芥备,就是在dealloc
的時(shí)候調(diào)用的,讓觀察者釋放舌菜。
最后萌壳,cancel
是需要自己重寫的,根據(jù)NSOperation的具體業(yè)務(wù)來做處理日月,這里測(cè)試使用的是文件下載袱瓮,那么只需把下載的SessionTask取消即可,不同的任務(wù)有不同的可能山孔。
2.2 GCD的任務(wù)取消
-(void)testStateControl_GCD{
dispatch_queue_t queue = dispatch_queue_create("GCD_stateControl", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i<10; i++) {
dispatch_async(queue, ^{
NSLog(@"task start%d",i);
sleep(1);
NSLog(@"task%d",i);
});
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_suspend(queue);
NSLog(@"suspend");
sleep(5);
NSLog(@"sleep finished");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_resume(queue);
NSLog(@"resume");
});
});
}
- 在一個(gè)串行隊(duì)列里新建了10個(gè)任務(wù)
- 在2秒后暫停這個(gè)隊(duì)列
- 在5秒后恢復(fù)
為什么是串行隊(duì)列懂讯,因?yàn)槿绻x用并行隊(duì)列,在建立任務(wù)的一瞬間台颠,他們就各自開始執(zhí)行了褐望,而dispatch_suspend
對(duì)已經(jīng)執(zhí)行的任務(wù)是不起作用的。使用串行隊(duì)列串前,因?yàn)槿蝿?wù)是一個(gè)接一個(gè)的執(zhí)行瘫里,那么2秒后,還有任務(wù)在等待中荡碾,他們就被暫停了谨读。
在2秒后暫停,可以看到坛吁,有些任務(wù)執(zhí)行了劳殖,而有的任務(wù)要等一會(huì)铐尚,這樣可以看出差距。
最后很坑爹的:
By suspending a dispatch object, your application can temporarily prevent the execution of any blocks associated with that object. The suspension occurs after completion of any blocks running at the time of the call. Calling this function increments the suspension count of the object, and calling dispatch_resume decrements it. While the count is greater than zero, the object remains suspended, so you must balance each dispatch_suspend
call with a matching dispatch_resume
call.
- 注意temporarily暫時(shí)阻止執(zhí)行
- 然后
dispatch_suspend
和dispatch_resume
必須平衡哆姻,就是暫停了后面一定要恢復(fù)宣增。試過不恢復(fù),奔潰矛缨。 - 更神奇的是
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_resume(queue);
NSLog(@"resume");
});
這種在n秒之后還沒執(zhí)行的代碼爹脾,有dispatch_resume
就沒問題,沒有就奔潰箕昭,鬼知道蘋果做了什么處理灵妨。
<br />
總得來說NSOperation的控制力度要大得多,而GCD像是為了一次性任務(wù)而生落竹,爽快的來爽快的去泌霍!
<br />
各種鎖
這部分我很喜歡,說實(shí)話這里體現(xiàn)了一些很有意思的思想述召。
- 互斥鎖:
NSLock
- 遞歸鎖:
NSRecursiveLock
- 條件鎖:
NSCondition
- 信號(hào)量:
dispatch_semaphore_t
- 讀寫鎖:
dispatch_barrier_sync
,這個(gè)比較特殊烹吵,不是鎖,但是可以是現(xiàn)實(shí)讀寫鎖的需求
1. NSLock
為了模擬情況桨武,新建了一個(gè)資源類:
頭文件
@interface TFResource : NSObject
+(instancetype)shareInstance;
@property (nonatomic, assign) NSInteger count;
-(void)addSomeResources;
-(void)useOne;
@end
實(shí)現(xiàn)代碼
@implementation TFResource
+(instancetype)shareInstance{
static TFResource *dataSource = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dataSource = [[TFResource alloc] init];
});
return dataSource;
}
-(instancetype)init{
if (self = [super init]) {
_count = 100;
}
return self;
}
-(void)addSomeResources{
int count = arc4random() % 10;
self.count += count;
NSLog(@"====================gen :%d",count);
}
-(void)useOne{
self.count = self.count - 1;
}
其實(shí)就維持一個(gè)數(shù)量,然后useOne
數(shù)量減1锈津,addSomeResources
增加10以內(nèi)隨機(jī)個(gè)數(shù)呀酸。這是模擬了跟經(jīng)典的賣票問題一樣的情況。
1.1 首先來個(gè)不加鎖的反例:
-(void)noSyncNSLocking{
dispatch_queue_t queue = dispatch_queue_create("resourceUseQueue", DISPATCH_QUEUE_CONCURRENT);
int taskCount = 10;
for (int i = 0; i<taskCount; i++) {
dispatch_async(queue, ^{
while (_resource.count > 0) {
NSLog(@"taskUse%d, <<<%ld",i,(long)_resource.count);
sleep(arc4random()%100/1000);
[_resource useOne];
}
NSLog(@"task%d end, real: %ld",i,(long)_resource.count);
});
}
}
開10個(gè)隊(duì)列琼梆,只要有票性誉,就useOne
,等沒票了,輸出當(dāng)前實(shí)際的票數(shù)茎杂。然后結(jié)果就是左后票數(shù)變成了-9错览。
<br />
1.2 然后只對(duì)使用加鎖,但是對(duì)檢查不加鎖
-(void)NSLock_DontLockCheckCountNSLocking{
dispatch_queue_t queue = dispatch_queue_create("resourceUseQueue", DISPATCH_QUEUE_CONCURRENT);
int taskCount = 10;
for (int i = 0; i<taskCount; i++) {
dispatch_async(queue, ^{
while (_resource.count > 0) {
NSLog(@"taskEnter%d, >>>%ld",i,(long)_resource.count);
[_resourceLock lock];
NSLog(@"taskUse%d, <<< %ld",i,(long)_resource.count);
[_resource useOne];
[_resourceLock unlock];
}
NSLog(@"task%d end, real: %ld",i,(long)_resource.count);
});
}
}
while (_resource.count > 0)
檢查是否可執(zhí)行的時(shí)候煌往,沒有加鎖倾哺,那么結(jié)果有沒有問題呢?答案是最后數(shù)量依然是-9刽脖。
這個(gè)情形我思考了很久羞海,是一個(gè)收獲點(diǎn)。問題出在哪里呢曲管?
假設(shè)現(xiàn)在有個(gè)房間却邓,放著資源,然后10個(gè)人去搬院水。某一刻腊徙,房間里有5個(gè)資源简十,然后10個(gè)人都看到了,每個(gè)人心里都想撬腾,(a)還有5個(gè)螟蝙,還可以去拿。然后他們都去了时鸵,雖然他們(b)一次排隊(duì)胶逢,并且每次只有一個(gè)人進(jìn)去搬,上一個(gè)人出來了饰潜,后一個(gè)人再進(jìn)去初坠。最后還是出現(xiàn)有人進(jìn)了房間沒有東西。
(a)這里的邏輯就是代碼里的_resource.count > 0
,(b)這里的邏輯就是對(duì)[_resource useOne];
加鎖彭雾。雖然使用加鎖碟刺,但判斷沒有,那么判斷就會(huì)失誤薯酝,導(dǎo)致過多的執(zhí)行使用半沽。
要保證資源使用的唯一,就必須:從檢查到使用吴菠,都必須唯一者填。所以代碼就成了:
-(void)NSLock_lockAllNSLocking{
dispatch_queue_t queue = dispatch_queue_create("resourceUseQueue", DISPATCH_QUEUE_CONCURRENT);
int taskCount = 10;
for (int i = 0; i<taskCount; i++) {
dispatch_async(queue, ^{
while (1) {
NSLog(@"taskEnter%d, >>>%ld",i,(long)_resource.count);
[_resourceLock lock];
NSLog(@"taskUse%d, <<< %ld",i,(long)_resource.count);
if (_resource.count > 0) {
[_resource useOne];
}else{
break;
}
[_resourceLock unlock];
}
NSLog(@"task%d end, real: %ld",i,(long)_resource.count);
});
}
}
<br />
2. NSCondition
上面的情形是,資源總是是固定做葵,用完就結(jié)束占哟,但還有種情況,資源同時(shí)在生產(chǎn)和被消耗酿矢。也就是榨乎,如果資源沒了,不是結(jié)束瘫筐,而是等一等蜜暑,等待資源又有了,又開始使用策肝。
如果用NSLock
實(shí)現(xiàn)肛捍,我想到的是這樣:
-(void)NSLock_WaitTestNSLocking{
dispatch_queue_t queue = dispatch_queue_create("resourceUseQueue", DISPATCH_QUEUE_CONCURRENT);
__block int addTimes = 10;
dispatch_async(queue, ^{
while (addTimes > 0) {
[_resourceLock lock];
[_resource addSomeResources];
[_resourceLock unlock];
addTimes --;
sleep(arc4random()%3);
}
});
//如果不使用NSCondition,那么在等待資源恢復(fù)的時(shí)候驳糯,就是要不斷的去檢查篇梭,而且這個(gè)過程伴隨不斷的加鎖、解鎖酝枢。
//NSCondition的的邏輯相當(dāng)于恬偷,所有人都在門外等,然后派一個(gè)人去看著帘睦,一旦有資源來了袍患,就喚醒所有的等待者坦康。就是“等待-喚醒”的模式。
int taskCount = 10;
for (int i = 0; i<taskCount; i++) {
dispatch_async(queue, ^{
while (1) {
NSLog(@"taskEnter%d, >>>%ld",i,(long)_resource.count);
[_resourceLock lock];
NSLog(@"taskUse%d, <<< %ld",i,(long)_resource.count);
if (_resource.count > 0) {
[_resource useOne];
}else{
[_resourceLock unlock];
continue;
}
[_resourceLock unlock];
}
});
}
}
- 使用一個(gè)線程循環(huán)的增加資源诡延,每次隨機(jī)0-9個(gè)
- 開另外10個(gè)線程來消耗資源滞欠,每次1個(gè)
- 在資源為空時(shí),釋放鎖肆良,然后從新開始判斷筛璧,如此循環(huán)。
注意到第三步是一個(gè)浪費(fèi)的操作惹恃,因?yàn)椴粩嗟募渔i夭谤、解鎖,而且線程完全不停歇巫糙。當(dāng)然也可以加個(gè)sleep稍微讓線程停歇下朗儒。但NSCondition
可以完美解決這些問題。
-(void)NSCondition_WaitTestNSLocking{
dispatch_queue_t queue = dispatch_queue_create("resourceUseQueue", DISPATCH_QUEUE_CONCURRENT);
__block int addTimes = 30;
int taskCount = 10;
for (int i = 0; i<taskCount; i++) {
dispatch_async(queue, ^{
while (1) {
[_resourceCondition lock];
while (_resource.count <= 0) {
NSLog(@"taskWait:%d",i);
//這個(gè)方法在signal之后會(huì)自動(dòng)獲取鎖参淹,只有獲取了鎖才會(huì)返回醉锄。開始調(diào)用的時(shí)候,會(huì)釋放鎖浙值。也就相當(dāng)于:unlock-wait-signal-reAcquireLock-return
//這樣的流程保證不會(huì)死鎖恳不,多個(gè)同時(shí)喚醒依然保證線程同步
//wait需要嵌套在while里面是因?yàn)椤氨粏拘选?=“條件滿足”,因?yàn)榭赡芡瑫r(shí)釋放多個(gè)鎖
[_resourceCondition wait];
NSLog(@"taskWakeUp:%d",i);
}
NSLog(@"taskUse%d, <<< %ld",i,(long)_resource.count);
[_resource useOne];
NSLog(@"unlock%d",i);
[_resourceCondition unlock];
}
});
}
dispatch_async(queue, ^{
while (addTimes > 0) {
[_resourceCondition lock];
[_resource addSomeResources];
//每句只會(huì)喚醒一個(gè)線程
[_resourceCondition signal];
// [_resourceCondition signal];
// [_resourceCondition signal];
// [_resourceCondition signal];
sleep(2);
NSLog(@"unlock add task");
[_resourceCondition unlock];
addTimes --;
sleep(arc4random()%3);
}
});
}
NSCondition
和互斥鎖一樣开呐,只是多了檢查妆够。當(dāng)條件不滿足(資源為空)時(shí),進(jìn)入等待[_resourceCondition wait]
负蚊,而不需要不斷的循環(huán)判斷,它會(huì)自動(dòng)解鎖并阻塞線程颓哮。在其他線程家妆,如果發(fā)現(xiàn)條件可能滿足(增加了新資源),就釋放
[_resourceCondition signal];
來喚醒一個(gè)等待的線程冕茅。這樣等待的線程會(huì)被喚醒然后去檢查伤极。如果做個(gè)形象的比喻:
NSLock的不斷循環(huán)檢查,就相當(dāng)于人不斷的去倉庫查看是不是有貨姨伤,如果有10個(gè)搬運(yùn)工哨坪,那么就10個(gè)人都去看,然后又回來乍楚,而且還是一個(gè)個(gè)的依次進(jìn)倉庫当编,還不是同時(shí)進(jìn)。
NSCondition的的邏輯相當(dāng)于徒溪,所有人都在門外等忿偷,然后派一個(gè)人去看著金顿,一旦有資源來了,就喚醒所有的等待者鲤桥。注意點(diǎn)1:
[_resourceCondition signal];
每次只會(huì)喚醒一個(gè)等待線程揍拆,如果同時(shí)調(diào)用了多次,那么就會(huì)同時(shí)有多個(gè)等待者進(jìn)行競(jìng)爭(zhēng)茶凳。所以要在[_resourceCondition wait];
外層加個(gè)while判斷條件嫂拴,就是喚醒了依然可能拿不到資源,比如有3個(gè)新資源贮喧,然后喚醒了4個(gè)線程筒狠,那么最后一個(gè)其實(shí)還是拿不到。也就是“喚醒”!="條件滿足"塞淹。-
注意點(diǎn)2:
[_resourceCondition wait];
干的事邏輯上是unlock-wait-signal-reAcquireLock-return窟蓝,也就是進(jìn)前等待錢它會(huì)解鎖,喚醒后饱普,會(huì)自動(dòng)重新獲取鎖运挫,只有重新獲取鎖了,才會(huì)return這個(gè)方法套耕,也就是表面上看起來谁帕,它從來沒有釋放鎖,只是睡了一會(huì)兒冯袍。
這是我從另一個(gè)線程lock時(shí)成功以及文檔的解釋得出的匈挖。文檔說的還算清楚When a thread waits on a condition, the condition object unlocks its lock and blocks the thread. When the condition is signaled, the system wakes up the thread. The condition object then reacquires its lock before returning from the wait or waitUntilDate: method. Thus, from the point of view of the thread, it is as if it always held the lock.
NSRecursiveLock
遞歸鎖比較容易理解,就是互斥鎖的一個(gè)特例康愤,已經(jīng)獲取鎖的線程可以多次獲取這個(gè)鎖儡循,而其他線程不能。
-(void)NSRecursiveLockNSLocking{
//use NSLock --> dead lock
dispatch_queue_t queue1 = dispatch_queue_create("resourceUseQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("resourceUseQueue", DISPATCH_QUEUE_SERIAL);
// dispatch_async(queue1, ^{
// [_resourceLock lock];
//
// [_resourceLock lock];
// NSLog(@"enter inner");
// if (_resource.count > 0) {
// NSLog(@"use one");
// [_resource useOne];
// }
// NSLog(@"inner unlock");
// [_resourceLock unlock];
//
//
// NSLog(@"outer unlock");
// [_resourceLock unlock];
// });
//use rcursive lock to acquire lock muiltiple times for the same thread
//自身線程可重復(fù)獲取鎖征冷,但是仍然必須lock和unlock對(duì)等择膝,即lock之后就要有匹配的unlock,否則一直控制鎖检激,其他線程就進(jìn)不來
dispatch_async(queue1, ^{
[_recursiveLock lock];
[_recursiveLock lock];
NSLog(@"1:enter inner");
if (_resource.count > 0) {
NSLog(@"1:use one");
[_resource useOne];
}
NSLog(@"1:inner unlock");
[_recursiveLock unlock];
NSLog(@"1:outer unlock");
sleep(1);
[_recursiveLock unlock];
});
dispatch_async(queue2, ^{
[_recursiveLock lock];
NSLog(@"2:enter inner");
if (_resource.count > 0) {
NSLog(@"2:use one");
[_resource useOne];
}
NSLog(@"2:inner unlock");
[_recursiveLock unlock];
});
}
- 注釋部分就是使用
NSLock
時(shí)肴捉,自身線程重讀獲取鎖,結(jié)果就是dead lock
信號(hào)量和讀寫鎖有時(shí)間再續(xù)叔收。齿穗。。