dispatch 之 常見函數小結


你好2019!一起努力呀!

 

直奔主題

1、dispatch_barrier_async VS  dispatch_barrier_sync

     Barrier blocks only behave specially when submitted to queues created with
     * the DISPATCH_QUEUE_CONCURRENT attribute; on such a queue, a barrier block
     * will not run until all blocks submitted to the queue earlier have completed,
     * and any blocks submitted to the queue after a barrier block will not run
     * until the barrier block has completed.
     * When submitted to a a global queue or to a queue not created with the
     * DISPATCH_QUEUE_CONCURRENT attribute, barrier blocks behave identically to
     * blocks submitted with the dispatch_async()/dispatch_sync() API.
 NSLog(@"main ---1--");

    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"test1 begin - ");

        sleep(3);
        NSLog(@"test1 - end - ");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"test2 begin - ");
        
        sleep(3);
        NSLog(@"test2 - end - ");
        
    });
    dispatch_barrier_async(self.concurrentQueue, ^{///分界線在這里 請注意是同步的
        NSLog(@"barrier -- start");
        sleep(1);
        NSLog(@"barrier -- end");

    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"test4 begin - ");
        
        sleep(3);
        NSLog(@"test4 - end - ");
        
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"test5 begin - ");
        sleep(3);
        NSLog(@"test5 - end - ");
        
    });
    NSLog(@"main ---6--");
示例代碼
     2019-01-26 14:10:42.327067+0800 HaiFeiArrangeProject[28551:342894] main ---1--
     2019-01-26 14:10:42.327227+0800 HaiFeiArrangeProject[28551:342894] main ---6--
     2019-01-26 14:10:42.327229+0800 HaiFeiArrangeProject[28551:342934] test1 begin -
     2019-01-26 14:10:42.327253+0800 HaiFeiArrangeProject[28551:342935] test2 begin -
     2019-01-26 14:10:45.331341+0800 HaiFeiArrangeProject[28551:342934] test1 - end -
     2019-01-26 14:10:45.331341+0800 HaiFeiArrangeProject[28551:342935] test2 - end -
     2019-01-26 14:10:45.331612+0800 HaiFeiArrangeProject[28551:342935] barrier -- start
     2019-01-26 14:10:46.336684+0800 HaiFeiArrangeProject[28551:342935] barrier -- end
     2019-01-26 14:10:46.336910+0800 HaiFeiArrangeProject[28551:342935] test4 begin -
     2019-01-26 14:10:46.336911+0800 HaiFeiArrangeProject[28551:342934] test5 begin -
     2019-01-26 14:10:49.341715+0800 HaiFeiArrangeProject[28551:342934] test5 - end -
     2019-01-26 14:10:49.341715+0800 HaiFeiArrangeProject[28551:342935] test4 - end -
dispatch_barrier_async 執行結果
     2019-01-26 14:10:03.909859+0800 HaiFeiArrangeProject[28531:342041] main ---1--
     2019-01-26 14:10:03.910086+0800 HaiFeiArrangeProject[28531:342080] test1 begin -
     2019-01-26 14:10:03.910101+0800 HaiFeiArrangeProject[28531:342081] test2 begin -
     2019-01-26 14:10:06.913917+0800 HaiFeiArrangeProject[28531:342081] test2 - end -
     2019-01-26 14:10:06.913964+0800 HaiFeiArrangeProject[28531:342080] test1 - end -
     2019-01-26 14:10:06.914284+0800 HaiFeiArrangeProject[28531:342041] barrier -- start
     2019-01-26 14:10:07.915035+0800 HaiFeiArrangeProject[28531:342041] barrier -- end
     2019-01-26 14:10:07.915219+0800 HaiFeiArrangeProject[28531:342041] main ---6--
     2019-01-26 14:10:07.915247+0800 HaiFeiArrangeProject[28531:342081] test4 begin -
     2019-01-26 14:10:07.915251+0800 HaiFeiArrangeProject[28531:342082] test5 begin -
     2019-01-26 14:10:10.919249+0800 HaiFeiArrangeProject[28531:342081] test4 - end -
     2019-01-26 14:10:10.919276+0800 HaiFeiArrangeProject[28531:342082] test5 - end -
dispatch_barrier_sync執行結果

結果分析:

dispatch_barrier_sync(queue,void(^block)())會將queue中barrier前面添加的任務block全部執行后,再執行barrier任務的block,再執行barrier后面添加的任務block,同時阻塞住線程.

dispatch_barrier_async(queue,void(^block)())會將queue中barrier前面添加的任務block只添加不執行,繼續添加barrier的block,再添加barrier后面的block,同時不影響主線程(或者操作添加任務的線程)中代碼的執行!

簡單理解就是:sync 阻塞主線程;async:不阻塞! 參看打印的“main ---6--”!!!

需要注意的:

若將dispatch_barrier加入到global隊列中,dispatch_barrier無效

在使用柵欄函數時.使用自定義隊列才有意義,如果用的是串行隊列或者系統提供的全局並發隊列,這個柵欄函數的作用等同於一個同步函數的作用 

2、dispatch_after

     DISPATCH_TIME_NOW,表示從現在開始。
     DISPATCH_TIME_FOREVER,表示遙遠的未來
     NSEC:納秒。
     USEC:微妙。
     MSEC:毫秒
     SEC:秒
     PER:每
     1s=10的3次方 ms(毫秒)
       =10的6次方μs(微秒)
       =10v的9次方ns(納秒)
     #define NSEC_PER_SEC 1000000000ull 每秒有多少納秒
     #define NSEC_PER_MSEC 1000000ull   每毫秒有多少納秒
     #define USEC_PER_SEC 1000000ull    每秒有多少微秒。(注意是指在納秒的基礎上)
     #define NSEC_PER_USEC 1000ull      每微秒有多少納秒。
     dispatch_after函數並不是延遲對應時間后立即執行block塊中的操作,而是將任務追加到對應的隊列中,考慮到隊列阻塞等情況,所以這個任務從加入隊列到真正執行的時間並不准確!
     
     3.0 * NSEC_PER_SEC 表示:3秒

       dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        NSLog(@"執行任務"); 

    });

3、dispatch_once  

typedef void (^TestBlock)(void);
TestBlock myTestBlock=^(){
    static int count = 0;
    NSLog(@"count = %d",count ++);
    
};
- (void)dispatchOnceTest
{
    /*
     dispatch_once 一般多用於單例構造方法中,目前尚未在其他方法中使用過! 關於單例構造的具體實現也不僅僅只有這個還需要重寫其他的方法! 之后完善 單例!!!
     使用dispatch_once需要注意:其block中的包裹的內容,盡量避免與其他類耦合!
     */
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, myTestBlock);
    dispatch_once(&onceToken, myTestBlock);

    //雖然執行兩次,只有一個輸出
    /*
     2019-01-26 15:37:15.438356+0800 HaiFeiArrangeProject[29785:403238] count = 0
     */
}

4、dispatch_group_notify  

 //創建隊列組
    dispatch_group_t group = dispatch_group_create();
    NSLog(@"----group--start----");

    //封裝任務
    dispatch_group_async(group, self.concurrentQueue, ^{
        sleep(2);
        NSLog(@"1----------%@",[NSThread currentThread]);
    });
    
    dispatch_group_async(group, self.concurrentQueue, ^{
        sleep(1);
        NSLog(@"2----------%@",[NSThread currentThread]);
    });
    
    dispatch_group_async(group, self.concurrentQueue, ^{
        sleep(3);
        NSLog(@"3----------%@",[NSThread currentThread]);
    });
    
    //4.攔截通知
    dispatch_group_notify(group, self.concurrentQueue, ^{
        NSLog(@"---dispatch_group_notify------%@",[NSThread currentThread]);
    });
    //不用等待 隊列執行完就會執行這個代碼
    NSLog(@"----group--end----");
View Code

這個代碼是 加入到group中的異步操作 這個操作內部是同步的,在這樣的情況下 可以如下使用,但是如果異步操作內部也是異步 就需要配合enter和leave實現目前實現的效果! 參看enter 和 leave的操作

5、dispatch_group_leave 和 dispatch_group_leave

dispatch_group_t group =dispatch_group_create();

    
    dispatch_group_enter(group);
    
    //模擬多線程耗時操作
    dispatch_group_async(group, self.concurrentQueue, ^{
        
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"1---1--begin");
            sleep(3);
            NSLog(@"1---1--end");
            dispatch_group_leave(group);
            
        });
        
    });
    
    dispatch_group_enter(group);
    //模擬多線程耗時操作
    dispatch_group_async(group, self.concurrentQueue, ^{
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"2---2--begin");
            sleep(2);
            NSLog(@"2--2-end");
            dispatch_group_leave(group);
            
        });
    });
    
    dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%@---全部done。。。",[NSThread currentThread]);
    });
    
    NSLog(@"main");
enter 和 leave
   2019-01-26 16:32:11.860953+0800 HaiFeiArrangeProject[30753:447579] 1---1--begin
     2019-01-26 16:32:11.860953+0800 HaiFeiArrangeProject[30753:447327] main
     2019-01-26 16:32:11.860957+0800 HaiFeiArrangeProject[30753:447367] 2---2--begin
     2019-01-26 16:32:13.861316+0800 HaiFeiArrangeProject[30753:447367] 2--2-end
     2019-01-26 16:32:14.866069+0800 HaiFeiArrangeProject[30753:447579] 1---1--end
     2019-01-26 16:32:14.866708+0800 HaiFeiArrangeProject[30753:447579] <NSThread: 0x6000000f0b40>{number = 3, name = (null)}---全部done。。。
使用后enter和leave的打印輸出
   2019-01-26 16:33:19.111523+0800 HaiFeiArrangeProject[30784:448544] 1---1--begin
     2019-01-26 16:33:19.111520+0800 HaiFeiArrangeProject[30784:448504] main
     2019-01-26 16:33:19.111544+0800 HaiFeiArrangeProject[30784:448871] 2---2--begin
     2019-01-26 16:33:19.111605+0800 HaiFeiArrangeProject[30784:448868] <NSThread: 0x6000019c3600>{number = 3, name = (null)}---全部done。。。
     2019-01-26 16:33:21.113975+0800 HaiFeiArrangeProject[30784:448871] 2--2-end
     2019-01-26 16:33:22.114889+0800 HaiFeiArrangeProject[30784:448544] 1---1--end
注釋掉enter和leave之后的打印輸出

 結論:

     1、在加入group的異步操作其內部如果是同步操作,enter和leave加不加均可,若其內部是異步操作,必須使用enter和leave

     2、enter 和 leave 必須是成對的出現:若一對enter和leave 只有enter 會導致notify用不執行,如果只有leave,會直接崩潰!

6、dispatch_group_wait

dispatch_group_t group = dispatch_group_create();
    //異步
    dispatch_group_async(group, self.concurrentQueue, ^{
        sleep(2);
        NSLog(@"1");
    });
    dispatch_group_async(group, self.concurrentQueue, ^{
        sleep(1.5);
        NSLog(@"2");
    });
    dispatch_group_async(group, self.concurrentQueue, ^{
        sleep(3);
        NSLog(@"3");
    });
    NSLog(@"aaaaa");
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
    long result = dispatch_group_wait(group, time);
    if (result == 0){
        // 屬於Dispatch Group的Block全部處理結束
        NSLog(@"全部處理結束");
    }else{
        // 屬於Dispatch Group的某一個處理還在執行中
        NSLog(@"某一個處理還在執行中");
    }
    NSLog(@"main");
wait代碼示例
2019-01-26 15:56:15.450252+0800 HaiFeiArrangeProject[30168:420683] aaaaa
     2019-01-26 15:56:16.453528+0800 HaiFeiArrangeProject[30168:420709] 2
     2019-01-26 15:56:17.451638+0800 HaiFeiArrangeProject[30168:420683] 某一個處理還在執行中
     2019-01-26 15:56:17.451927+0800 HaiFeiArrangeProject[30168:420683] main
     2019-01-26 15:56:17.453986+0800 HaiFeiArrangeProject[30168:420707] 1
     2019-01-26 15:56:18.453192+0800 HaiFeiArrangeProject[30168:420706] 3
wait2s的打印輸出
     2019-01-26 15:57:15.072096+0800 HaiFeiArrangeProject[30189:421617] aaaaa
     2019-01-26 15:57:16.075428+0800 HaiFeiArrangeProject[30189:421665] 2
     2019-01-26 15:57:17.076848+0800 HaiFeiArrangeProject[30189:421915] 1
     2019-01-26 15:57:18.072394+0800 HaiFeiArrangeProject[30189:421920] 3
     2019-01-26 15:57:18.072845+0800 HaiFeiArrangeProject[30189:421617] 全部處理結束
     2019-01-26 15:57:18.073139+0800 HaiFeiArrangeProject[30189:421617] main
wait5s的打印輸出

這里起了3個異步線程放在一個組里,之后通過dispatch_time_t創建了一個超時時間(2秒),程序之后行,立即輸出了aaaaa,這是主線程輸出的,

當遇到dispatch_group_wait時,主線程會被掛起,等待2秒,在等待的過程當中,子線程分別輸出了1和2,2秒時間達到后,主線程發現組里的任務並沒有全部結束,然后輸出了main。

在這里,如果超時時間設置得比較長(比如5秒),那么會在3秒時第三個任務結束后,立即輸出main,也就是說,當組中的任務全部執行完畢時,主線程就不再被阻塞了。

如果希望永久等待下去,時間可以設置為DISPATCH_TIME_FOREVER。

7、dispatch_semaphore_wait

關於信號量的用途 

7.1:加鎖、

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i < 10000; i++) {
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        //臨界區,即待加鎖的代碼區域
        dispatch_semaphore_signal(semaphore);
    });
}

在這里,當第一條線程訪問臨界區時,信號量計數為初始值1,

dispatch_semaphore_wait() 函數判斷到計數大於0,於是將計數減1,從而線程允許訪問臨界區。其它線程因為信號量等於0,就在臨界區外等待。

在第一條線程訪問完臨界區后,這條線程需要發出一個信號,來表明我已經用完臨界區的資源了,下個正在等待的線程可以去訪問了。

dispatch_semaphore_signal()會將信號量計數加1,就好像發出了一個信號一樣,下個在臨界區前等待的線程會去接收它。接收到了信號的線程判斷到信號量計數大於零了,於是訪問臨界區。

通過重復這個過程,所有線程都會安全地訪問一遍臨界區。

可以參考YYKit中的簡單的加鎖代碼

- (instancetype)init {
    self = [super init];
    _lock = dispatch_semaphore_create(1);
    return self;
}

- (NSURL *)imageURL {
    dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
    NSURL *imageURL = _imageURL;
    dispatch_semaphore_signal(_lock);
    return imageURL;
}
YYKit部分源碼參考

7.2:異步返回、

- (NSArray *)tasksForKeyPath:(NSString *)keyPath

 {
    __block NSArray *tasks = nil;
 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);     [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }
 dispatch_semaphore_signal(semaphore);     }];
 dispatch_semaphore_wait(semaphore); return tasks;
}

這段代碼的功能是通過異步的請求取得鍵路徑為 keyPath 的任務數組 tasks,然后返回它。這個方法雖然是異步的,但是執行時間較短。

碰到這種情況,我們肯定最先想到的是用代碼塊 block 或者代理 delegate 來實現,然后我們就得去聲明一個代理,寫一個協議方法,或者寫一個帶有一個參數的代碼塊,這里AFNetworking巧妙地通過信號量解決了。

我們跟之前的加鎖對比,可以發現,信號量在創建時計數是0,
dispatch_semaphore_signal() 函數在 dispatch_semaphore_wait() 函數之前。

AFNetworking 把 dispatch_semaphore_wait() 函數放在返回語句之前,同時信號量計數初始為0,是為了讓線程在 tasks 有值之前一直等待。獲取 tasks 的異步操作結束之后,這時候 tasks 賦值好了,於是通過 dispatch_semaphore_signal() 函數發出信號,外面的線程就知道不用等待,可以返回 tasks 了。

7.3:控制線程並發數

  dispatch_group_t group = dispatch_group_create();

    for (int i = 0; i < 10; i++) {
        dispatch_group_async(group, self.concurrentQueue, ^{
            
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"%d --- 開始 --",i + 1 );
            // 線程操作區域 最多有兩個線程在此做事情
            sleep(2);
           
            NSLog(@"%d --- end --",i + 1 );

            dispatch_semaphore_signal(semaphore);
        });
    }
    // group任務全部執行完畢回調
    dispatch_group_notify(group, self.concurrentQueue, ^{
        NSLog(@"done");
    });
控制並發個數

備注:在應用場景上,限制線程並發數是為了性能考慮,而加鎖是為了安全而考慮。

遺留問題:信號量是否線程安全?

文中若有不對之處,還請勞駕之處,謝謝!

 

信號量部分分析參考自:https://www.jianshu.com/p/de75da4173cf !

 


免責聲明!

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



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