iOS-關於GCD信號量那些事兒


隨便說說

其實GCD大家都有接觸過,也不在解釋GCD是什么,為什么突然想說信號量問題,最近這幾次面試,當我問到面試者怎么處理多個請求完成后的一系列操作時,有的說造一個臨時變量的做追加,其實這樣可以,也算是信號量的基本邏輯,有的說用線程做延時操作,怎么延時,怎么操作說的不清楚,有少部分會提到GCD信號量,但是可能說不出來怎么操作,通過信號量的增加與遞減,進行網絡的並發請求,最后再做網絡請求完成后的最終處理;其實實際上大家在做的時候,在網上一搜,基本都能找到;

GCD信號量的應用場景,一般是控制最大並發量,控制資源的同步訪問,如數據訪問,網絡同步加載等。

需求1:多個網絡請求完成后(無序)執行下一步 

先看下如果不用GCD線程組或信號量會怎么執行

- (void)dispatchSyncSignal{
    NSString *urlString = @"http://www.baidu.com";
    NSURL *url = [NSURL URLWithString:urlString];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];
    
    for (int i=0; i<5; i++) {
        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"請求回調 %d---%d",i,i);
            
        }];
        
        [task resume];
    }
    NSLog(@"end");
}

運行后后台打印輸出:

    

從上面兩次打印結果看出,end 先執行,由於網絡請求的異步回調,然后各個網絡請求的回調順序是無序的。下面針對需求進行操作;

使用GCD的線程組 dispatch_group_t

- (void)dispatchSyncSignal1{ //創建線程組
    dispatch_group_t group = dispatch_group_create(); NSString *urlString = @"http://www.baidu.com"; NSURL *url = [NSURL URLWithString:urlString]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLSession *session = [NSURLSession sharedSession]; for (int i=0; i<5; i++) { dispatch_group_enter(group); NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"請求回調 %d---%d",i,i); dispatch_group_leave(group); }]; [task resume]; } dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"end"); }); }

運行后后台打印輸出:

   

從兩次打印輸出結果可以看出,end 是在所有網絡請求之后才輸出,符合了我們的需求。然后說下用到的相關方法:

dispatch_group_create(); 創建一個dispatch_group_t;

dispatch_group_enter(); 每次網絡請求前調用;

dispatch_group_leave(); 每次網絡請求后調用;

dispatch_group_enter(); 和 dispatch_group_leave(); 必須配合使用,有幾次enter就要有幾次leave;

然后當所有dispatch_group_enter(); 的 block 都 dispatch_group_leave(); 后,會執行dispatch_group_notify的block。

使用GCD的信號量 semaphore_t

- (void)dispatchSyncSignal2{ dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); NSString *urlString = @"http://www.baidu.com"; NSURL *url = [NSURL URLWithString:urlString]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLSession *session = [NSURLSession sharedSession]; __block NSInteger count = 0; for (int i=0; i<5; i++) { NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"請求回調 %d---%d",i,i); count = count + 1; if (count == 5) { dispatch_semaphore_signal(semaphore); count = 0; } }]; [task resume]; } dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"end"); }); }

運行后后台打印輸出:

  

從兩次打印輸出結果可以看出,end 也是在所有網絡請求之后才輸出,也符合了我們的需求。然后說下用到的相關方法:

dispatch_semaphore 信號量如果計數為0,則等待。dispatch_semaphore_signal(semaphore)為計數+1,dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)為設置等待時間,這里設置的等待時間是永遠等待。對於以上代碼通俗一點講就是,開始為0,等待,等5個網絡請求都完成了,計數+1,然后計數-1返回,程序繼續執行,count變量,記錄網絡回調的次數,回調5次之后再發信號量,使后面程序繼續運行。

需求2:多個網絡請求完成后(按順序)執行下一步 

如果按照需求1的方式,讓多個網絡請求按順序執行完后,再進行下一步操作,那又應該怎么執行,當然還可以用信號量來操作:

- (void)dispatchSignal3{ dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); NSString *urlString = @"http://www.baidu.com"; NSURL *url = [NSURL URLWithString:urlString]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLSession *session = [NSURLSession sharedSession]; for (int i=0; i<5; i++) { NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"請求回調 %d---%d",i,i); dispatch_semaphore_signal(semaphore); }]; [task resume]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); } dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"end"); }); }

運行后后台打印輸出:

  

從兩次打印輸出結果可以看出,所有網絡請求按順序依次執行,end 在所有請求完成后輸出,符合了我們的需求。

方法中每一次遍歷,都執行以下 dispatch_semaphore_wait(),這個時候線程會等待,阻塞當前線程,直到dispatch_semaphore_signal(sem)調用之后,再繼續下一次遍歷。

簡單說說 

信號量是用於多線程同步的,跟鎖不一樣的是,信號量不一定是鎖定某一個資源,而是流程上的概念,比如:有A、B兩個線程,B線程要等A線程完成某一任務以后再進行自己下面的步驟,這個任務 並不一定是鎖定某一資源,其實還可以是進行一些計算或者數據處理之類;

下面再通過做一個基於 NSObject 的生產者、消費者的 XKGradeRoom 工作間,看下信號量的用法,我們可以給生產間定一個最大生產量(這里定2個),畢竟生產間也不能無限制的生產,通過生產者與消費者關系,合理的對生產間的產量進行把控,在產量達到最大產量時,就停止生產,等待消費者消費。

XKGradeRoom.h

#import <Foundation/Foundation.h>

/**
 生產消費工作間
 */
@interface XKGradeRoom : NSObject

/**
 生產
 */
- (void)xk_produce:(NSString *)sp;

/**
 消費

 @return NSString
 */
- (NSString*)xk_comsumer;
@end

XKGradeRoom.m

#import "XKGradeRoom.h"
@interface XKGradeRoom()
/**
 倉庫
 */
@property(strong,nonatomic) NSMutableArray* baseArray;

/**
 訪問倉庫(臨界區)的互斥訪問信號量
 */
@property(strong,nonatomic) dispatch_semaphore_t criticalSemaphore;

/**
 消費者-是否消費倉庫對象的標記
 */
@property(strong,nonatomic) dispatch_semaphore_t comsumerSemaphore;

/**
 生產者-是否生產對象的標記
 */
@property(strong,nonatomic) dispatch_semaphore_t productSemaphore;

/**
 倉庫裝載最大量
 */
@property(nonatomic,assign) int maxProductCount;

@end
@implementation XKGradeRoom
- (instancetype)init{
    self = [super init];
    if (self) {
        [self setup];
    }
    return self;
}
- (void)setup{
    _maxProductCount = 2;
    self.baseArray = [NSMutableArray array];
    self.productSemaphore = dispatch_semaphore_create(_maxProductCount);
    self.comsumerSemaphore = dispatch_semaphore_create(0);
    //初始化臨界區互斥訪問信號量,用信號量實現互斥,特殊初始值為1.
    //控制同一時刻只有一個線程對象在訪問倉庫
    self.criticalSemaphore = dispatch_semaphore_create(1);
}


/**
 生產
 */
-(void)xk_produce:(NSString *)sp{
   //先獲取訪問倉庫的信號量
    long baseCount = dispatch_semaphore_wait(self.criticalSemaphore,  5 * NSEC_PER_SEC);
    if(baseCount != 0){
        NSLog(@"倉庫有人正在使用,生產者處於等待");
    }else{
        //再判斷 倉庫是否還有可放物品的空間
        long maxSpaceCount = dispatch_semaphore_wait(self.productSemaphore, 5 * NSEC_PER_SEC);
        
        if(maxSpaceCount != 0){
            NSLog(@"倉庫%d個空間已經使用完,生產者處於等待:倉庫容量:%lu",_maxProductCount,[self.baseArray count]);
            //生產完了釋放臨界區的訪問鎖
            dispatch_semaphore_signal(self.criticalSemaphore);
        }else{
            
            [self.baseArray addObject:sp];
             NSLog(@"新生產一個,倉庫目前有:%lu",[self.baseArray count]);
            dispatch_semaphore_signal(self.criticalSemaphore);
            dispatch_semaphore_signal(self.comsumerSemaphore);
          
        }
    }
}

/**
 消費
 
 @return NSString
 */
-(NSString*)xk_comsumer{
    NSString* e = nil;
    long baseCount = dispatch_semaphore_wait(self.criticalSemaphore, 5 * NSEC_PER_SEC);        //先獲取訪問倉庫的信號量
    if(baseCount != 0){
        NSLog(@"倉庫有人正在使用,消費者處於等待");
    }else{
        //再判斷 倉庫是否還有可取,如果有物品,則取一個出來,否則t等待
        long avableCount = dispatch_semaphore_wait(self.comsumerSemaphore, 5 * NSEC_PER_SEC);
        if(avableCount != 0){
            NSLog(@"空倉,消費者處於等待");
            //生產完了釋放臨界區的訪問鎖
            dispatch_semaphore_signal(self.criticalSemaphore);
        }else{
            e = [self.baseArray objectAtIndex:[self.baseArray count] -1];
            [self.baseArray removeLastObject];
            NSLog(@"消費了:%@ 倉庫還有%lu:",e,[self.baseArray count]);
            //生產完了釋放臨界區的訪問鎖
            dispatch_semaphore_signal(self.criticalSemaphore);
            //將倉庫中的可放置的數量 +1
            dispatch_semaphore_signal(self.productSemaphore);
        }
    }
    return e;
}
@end

下面測試下這個工作間

XKGradeRoom * gradeRoom = [XKGradeRoom new];
    
    //創建一個myDispatchQueue,主要是用於防止資源的競爭,一個線程處使用完資源,然后另外一個才能繼續使用
    dispatch_queue_t myDispatchQueue =  dispatch_queue_create("com.example.gcd,myDispatchQueue", NULL);
    dispatch_async(myDispatchQueue, ^{
        [gradeRoom xk_produce:@"Queue1"];
        NSLog(@"Queue1-執行完畢");
    });
    dispatch_async(myDispatchQueue, ^{
        [gradeRoom xk_comsumer];
        NSLog(@"Queue2-執行完畢");
    });
    dispatch_async(myDispatchQueue, ^{
        [gradeRoom xk_comsumer];
        NSLog(@"Queue3-執行完畢");
    });
    dispatch_async(myDispatchQueue, ^{
        [gradeRoom xk_produce:@"Queue4"];
        NSLog(@"Queue4-執行完畢");
    });
    dispatch_async(myDispatchQueue, ^{
        [gradeRoom xk_produce:@"Queue5"];
        NSLog(@"Queue5-執行完畢");
    });
    dispatch_async(myDispatchQueue, ^{
        [gradeRoom xk_produce:@"Queue6"];
        NSLog(@"Queue6-執行完畢");
    });
    
    dispatch_async(myDispatchQueue, ^{
        [gradeRoom xk_comsumer];
        [gradeRoom xk_comsumer];
        [gradeRoom xk_comsumer];
        [gradeRoom xk_comsumer];
        NSLog(@"Queue7-執行完畢");
    });

打印結果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM