iOS多線程:NSOperation和GCD對(duì)比以及各種鎖的測(cè)試

測(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ù)俭识,這里operation1operation2都下載一個(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_suspenddispatch_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ù)叔收。齿穗。。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末饺律,一起剝皮案震驚了整個(gè)濱河市窃页,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖腮出,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帖鸦,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡胚嘲,警方通過查閱死者的電腦和手機(jī)作儿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來馋劈,“玉大人攻锰,你說我怎么就攤上這事〖宋恚” “怎么了娶吞?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長械姻。 經(jīng)常有香客問我妒蛇,道長,這世上最難降的妖魔是什么楷拳? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任绣夺,我火速辦了婚禮,結(jié)果婚禮上欢揖,老公的妹妹穿的比我還像新娘陶耍。我一直安慰自己,他們只是感情好她混,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布烈钞。 她就那樣靜靜地躺著,像睡著了一般坤按。 火紅的嫁衣襯著肌膚如雪毯欣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天臭脓,我揣著相機(jī)與錄音仪媒,去河邊找鬼。 笑死谢鹊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的留凭。 我是一名探鬼主播佃扼,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蔼夜!你這毒婦竟也來了兼耀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瘤运,沒想到半個(gè)月后窍霞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拯坟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年但金,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片郁季。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡冷溃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梦裂,到底是詐尸還是另有隱情似枕,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布年柠,位于F島的核電站凿歼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏冗恨。R本人自食惡果不足惜答憔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望派近。 院中可真熱鬧攀唯,春花似錦、人聲如沸渴丸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谱轨。三九已至戒幔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間土童,已是汗流浹背诗茎。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留献汗,地道東北人敢订。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像罢吃,于是被迫代替她去往敵國和親楚午。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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