dispatch_semaphore使用

dispatch_semaphore是GCD采用線程同步的一種方式,與他相關的共有三個參數:

dispatch_semaphore_create
dispatch_semaphore_signal
dispatch_semaphore_wait

  • dispatch_semaphore_create 創建信號量
    dispatch_semaphore_create(long value); 給信號量初始一個值,當傳遞的值小于0,信號量將初始化失敗返回NULL。
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(2);
  • dispatch_semaphore_signal 發送信號量
    long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
    這個函數會使傳入的信號量dsema的值加1;
    dispatch_semaphore_signal(semaphore);
  • dispatch_semaphore_wait 等待信號量
    long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
    這個函數會使傳入的信號量dsema的值減1,如果傳入信號量的值等于0,函數將持續等待不返回。
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

這個函數的作用是這樣的,如果dsema信號量的值大于0,該函數所處線程就繼續執行下面的語句,并且將信號量的值減1;
  如果desema的值為0,那么這個函數就阻塞當前線程等待timeout(注意timeout的類型為dispatch_time_t,
  不能直接傳入整形或float型數),如果等待的期間desema的值被dispatch_semaphore_signal函數加1了,
  且該函數(即dispatch_semaphore_wait)所處線程獲得了信號量,那么就繼續向下執行并將信號量減1。
  如果等待期間沒有獲取到信號量或者信號量的值一直為0,那么等到timeout時,其所處線程自動執行其后語句。

  • 信號量的初始值,可以用來控制線程并發訪問的最大數量。
  • 信號量的初時值為1,代表同時只允許1條線程訪問資源,保證線程同步。

iOS線程同步方案性能比較
性能從高到低排序
os_unfair_lock // 缺點:iOS10才支持
OSSpinLock  // 缺點:可能出現優先級反轉 已經不再安全 蘋果也不推薦使用
dispatch_semaphore // 推薦使用
pthread_mutex  // 優點:跨平臺 互斥鎖(普通鎖) 推薦使用
dispatch_queue(DISPATCH_QUEUE_SERIAL) // c
NSLock   // oc
NSCondition   // oc
pthread_mutex(recursive) // 遞歸鎖
NSRecursiveLock  // oc
NSConditionLock // oc
@synchronized // 遞歸鎖 oc

從上可以知道線程同步除了os_unfair_lock 和
OSSpinLock之外,dispatch_semaphore的性能是很好的極力推薦使用。

dispatch_semaphore.jpg

如以上假如創建了A、B 、C、D四個子線程,假如A線程先執行了35行代碼后,此時信號量的值-1也就是1,并往下繼續執行。隨后B線程也執行了35行代碼后,此時信號量的值-1,也就是0。C線程、D線程都執行35行代碼時,此時信號量的值已經是0了,C線程和D線程就會進入休眠等待中,此時就卡住35行代碼處。假如第11秒時,線程A、B執行完了第39行代碼后,信號量+1,+1此時信號量就是2了,這樣C線程、D線程會被喚醒繼續執行。這樣信號量的初始值,可以用來控制線程并發訪問的最大數量。同理當我們設置信號量的初時值為1時,就可以實現線程同步。

dispatch_semaphore 使用案例

控制線程并發數
//定義一個信號量,初始化為10
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
    
//同時執行100個任務
 for (int i = 0; i < 100; i++)
 {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            
    //當前信號量-1
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            
    NSLog(@"任務%d執行",i+1);
            
    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:kUrlString]];

    dispatch_async(dispatch_get_main_queue(), ^{
        //TODO:刷新界面
    });
            
    //當前信號量+1
    dispatch_semaphore_signal(semaphore);
            
    });
}
線程安全

通過信號量可以用來控制線程并發訪問的最大數量,當我們設置信號量的初時值為1時,就可以實現線程同步。

- (void)viewDidLoad {
    [super viewDidLoad];
    semaphore =  dispatch_semaphore_create(1);
    self.array = [NSMutableArray array];
    for (int i=0; i<100; i++) {
        NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(test) object:nil];
        [thread start];
    }
}

- (void)test{
    NSLog(@"測試開始");
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    sleep(3);
    [self.array addObject:@"0"];
    dispatch_semaphore_signal(semaphore);
    NSLog(@"測試");
}

self.array直接在多個線程上進行做修改操作是不安全的,我可以通過信息量同步加鎖保證其線程安全。

多個網絡請求同步
  • 1、首先通過網絡請求一獲取用戶useid,之后用userid為參數發起網絡請求二。
#pragma mark - 網絡請求一
- (void)getuserId:(dispatch_semaphore_t)semaphore{
   AFHTTPSessionManager *sessionmanger=[[AFHTTPSessionManager alloc]init];
   sessionmanger.responseSerializer=[AFHTTPResponseSerializer serializer];
   [sessionmanger POST:@"https://www.baidu.com/" parameters:nil constructingBodyWithBlock:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
         NSLog(@"請求成功1%@", [NSThread currentThread]);
        useid=@"1234";
       dispatch_semaphore_signal(semaphore);
   } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
       NSLog(@"%@",error);
        dispatch_semaphore_signal(semaphore);
   }];
   
}

#pragma mark - 網絡請求二
- (void)requestwithuserid:(NSString *)userid{
   NSDictionary *parms=[NSMutableDictionary dictionary];
   [parms setValue:userid forKey:@"userid"];
   AFHTTPSessionManager *sessionmanger=[[AFHTTPSessionManager alloc]init];
   sessionmanger.responseSerializer=[AFHTTPResponseSerializer serializer];
   [sessionmanger POST:@"https://www.baidu.com/" parameters:userid constructingBodyWithBlock:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
       NSLog(@"請求成功2");
   } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
       NSLog(@"%@",error);
   }];
}
#pragma mark - 使用信號量實現
- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_semaphore_t semaphore= dispatch_semaphore_create(0); // 創建信號量
    [self getuserId:semaphore];//獲取用戶useid
 
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);//當前信號量為0,一直等待阻塞線程
    [self requestwithuserid:useid];
}

command+R運行一下,沒有任何反應。
原因分析:線程卡住了。代碼執行到dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER)因為信號量為0,當前線程會被阻塞。而當前線程是主線程,網絡請求一成功后回調到主線程,因為主線程被阻塞 造成信號量無法釋放,一直卡住。

解決方案就是開啟一個異步線程

- (void)viewDidLoad {
    [super viewDidLoad];
    //創建一個并行隊列
    dispatch_queue_t queque = dispatch_queue_create("GoyakodCreated", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queque, ^{
        dispatch_semaphore_t semaphore= dispatch_semaphore_create(0); // 創建信號量
        [self getuserId:semaphore];
        dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
        [self requestwithuserid:useid];
    });
}
  • 2、多個網絡請求后刷新UI
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    dispatch_group_t group = dispatch_group_create();
    // 創建信號量
   semaphore = dispatch_semaphore_create(0);
    // 創建全局并行
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_async(group, queue, ^{

        [self request1];

    });
    
    dispatch_group_async(group, queue, ^{
       
        [self request2];
 
    });
    
    dispatch_group_notify(group, queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        //在這里 進行請求后的方法,回到主線程
        dispatch_async(dispatch_get_main_queue(), ^{
            
            NSLog(@"更新UI");
            
        });
    });
    NSLog(@"12344");
    
}

- (void)request1{
    AFHTTPSessionManager *manger=[[AFHTTPSessionManager alloc]init];
    manger.responseSerializer=[AFHTTPResponseSerializer serializer];
    [manger POST:@"https://www.baidu.com" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"請求成功1");
        dispatch_semaphore_signal(semaphore);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
         dispatch_semaphore_signal(semaphore);
    }];

}
- (void)request2{
    AFHTTPSessionManager *manger=[[AFHTTPSessionManager alloc]init];
    manger.responseSerializer=[AFHTTPResponseSerializer serializer];
    [manger POST:@"https://www.baidu.com" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"請求成功2");
        dispatch_semaphore_signal(semaphore);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
         dispatch_semaphore_signal(semaphore);
    }];
   
}

或者這樣

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    dispatch_group_t group = dispatch_group_create();
   
    // 創建全局并行
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_async(group, queue, ^{

        [self request1];

    });
    
    dispatch_group_async(group, queue, ^{
       
        [self request2];
 
    });
    
    dispatch_group_notify(group, queue, ^{

        //在這里 進行請求后的方法,回到主線程
        dispatch_async(dispatch_get_main_queue(), ^{
            
            NSLog(@"更新UI");
            
        });
    });
    NSLog(@"12344");
    
}
- (void)request1{
    // 創建信號量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    AFHTTPSessionManager *manger=[[AFHTTPSessionManager alloc]init];
    manger.responseSerializer=[AFHTTPResponseSerializer serializer];
    [manger POST:@"https://www.baidu.com" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"請求成功1");
         NSLog(@"%ld", dispatch_semaphore_signal(semaphore));
       ;
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
         dispatch_semaphore_signal(semaphore);
        NSLog(@"%@",semaphore);
    }];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
- (void)request2{
    // 創建信號量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    AFHTTPSessionManager *manger=[[AFHTTPSessionManager alloc]init];
    manger.responseSerializer=[AFHTTPResponseSerializer serializer];
    [manger POST:@"https://www.baidu.com" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"請求成功2");
        dispatch_semaphore_signal(semaphore);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
         dispatch_semaphore_signal(semaphore);
    }];
     dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
@end

參考:CodeVicent

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。