Grand Central Dispatch回顾

原创文章转载请注明出处,谢谢


重温了一遍关于GCD方面的一些知识,于是重新整理了一下。

关于Dispatch Queue

Dispatch Queue可以分为两种队列,一种是等待现在执行中处理的Serial Dispatch Queue,即串行执行;另一种是不等待现在执行中处理的Concurrent Dispatch Queue,即并行执行;

Concurrent Dispatch Queue并行执行的处理数是由CPU核数,CPU负荷以及Dispatch Queue中的处理所决定。

关于dispatch-queue-create

dispatch-queue-create用于创建Dispatch Queue,可以创建Serial Dispatch Queue和Concurrent Dispatch Queue两种。

在《Objective-C高级编程iOS与OSX多线程和内存管理》一书中说到dispatch-queue-create不受ARC控制,需要我们自己手动disaptch-retain和dispatch-release,这个其实是不对的。在官方的文档中已经说明在OSX10.8和iOS10.6以后,ARC已经支持自动管理Dispatch Queue的创建了,不要我们手动release和retain了;但是如果你需要在开启ARC的情况下同时手动retian/release,那么就需要在compiler flags设置-DOS-OBJECT-USE-OBJC = 0。

关于Main Disaptch Queue和Global Disaptch Queue

Main Disaptch Queue和Global Disaptch Queue是两个系统的标准Dispatch Queue。

Main Disaptch Queue是在主线程中执行的Dispatch Queue,因为主线程只有一个,所以Main Dispatch Queue就是Serial Dispatch Queue。

Global Disaptch Queue是所有线程都可以使用的Concurrent Dispatch Queue,Global Disaptch Queue有四个执行优先级:

  • High Priority(最高优先级)
  • Default Priority(默认优先级)
  • Low Priority(低优先级)
  • Background Priority(后台优先级)

但是通过XNU内核用于Global Disaptch Queue的线程并不能保持实时性,因此执行优先级只是大致的判断。Global Disaptch Queue的默认执行优先级是Default Priority。

/*
 * Main Dispatch Queue
 */
 dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
 
 /*
  * Global Dispatch Queue(High Priority)
  */
 dispatch_queue_t globalDispatchQueueHigh = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
 
 /*
  * Global Dispatch Queue(Default Priority)
  */
 dispatch_queue_t globalDispatchQueueDefault = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
  /*
  * Global Dispatch Queue(Low Priority)
  */
 dispatch_queue_t globalDispatchQueueLow = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
 
  /*
  * Global Dispatch Queue(Background Priority)
  */
 dispatch_queue_t globalDispatchQueueBackground = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
 

关于dispatch-set-target-queue

dispatch-set-target-queue一共有两个作用。

作用一:修改Dispatch Queue的执行优先级;通过dispatch-queue-create函数生成的Dispatch Queue默认优先级都是Default Priority。

/*
 *修改serialQueue优先级至Background Priority
 */
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.serialQueue", NULL);  
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);  
dispatch_set_target_queue(serialQueue, globalQueue); 

注意不要修改Main Disaptch Queue和Global Disaptch Queue的优先级,因为这种情况是不可预知的。

作用二:修改用户队列的目标队列,使多个Serial Dispatch Queue在目标Queue只能同时执行一个处理,防止并行执行。

dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.serialQueue", DISPATCH_QUEUE_SERIAL);  
      
dispatch_queue_t queue1 = dispatch_queue_create("com.example.gcd.queue1", DISPATCH_QUEUE_SERIAL); 
dispatch_queue_t queue2 = dispatch_queue_create("com.example.gcd.queue2", DISPATCH_QUEUE_SERIAL);  
dispatch_queue_t queue3 = dispatch_queue_create("com.example.gcd.queue3", DISPATCH_QUEUE_SERIAL);  
      
dispatch_set_target_queue(queue1, serialQueue);  
dispatch_set_target_queue(queue2, serialQueue);  
dispatch_set_target_queue(queue3, serialQueue);  
      
      
dispatch_async(queue1, ^{  
    NSLog(@"queue1-start");
    sleep(1.0f);
    NSLog(@"queue1-end");
});  
  
dispatch_async(queue2, ^{  
    NSLog(@"queue2-start");
    sleep(1.0f);
    NSLog(@"queue2-end");
});  
dispatch_async(queue3, ^{  
    NSLog(@"queue3-start");
    sleep(1.0f);
    NSLog(@"queue3-end");
});  

//out put  多个Serial Queue并发执行,每次只能执行一个serial Queue的内容
queue1-start
queue1-end
queue2-start
queue2-end
queue3-start
queue3-end

关于dispatch-after和dispatch-once

dispatch-after函数并不是在指定时间后执行处理,而是在指定时间追加处理时间到Dispatch queue后再进行执行。

关于dispatch-time-t的类型可以由dispatch-time和dispatch-walltime两个函数来生成。

dispatch-time函数能够获取从第一个参数dispatch-time-t类型值中指定时间开始,到第二个参数指定的毫微秒单位时间后的时间。此时间是指相对时间。

// 延时一秒以后
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);

dispatch-walltime函数能够获取从第一个参数struct timespec结构体时间开始,此时间是指绝对时间。

struct timespec类型的时间可以通过NSDate类对象转换而成。

NSDate *date = [NSDate date];
NSTimeInterval interval;
double second, subsecond;
struct timespec time;
    
interval = [date timeIntervalSince1970];
subsecond = modf(interval, &second);
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC;
dispatch_time_t milestone milestone = dispatch_walltime(&time, 0);

dispatch-once函数的目的是保证在应用程序中执行中只执行指定处理,经常出现在单例的初始化里面,通过disptach-once函数,即使在多线程环境下执行也是安全的。

static dispatch_once_t once;

dispatch_once(&once, ^{
    // init class
});

关于Dispatch Group的作用

Dispatch Group的作用是当队列中的所有任务都执行完毕后在去做一些操作,主要针对Concurrent Dispatch Queue中多个处理结束后追加的操作。

Dispatch Group分为两种方式可以实现上面需求。

第一种是使用dispatch-group-async函数,将队列与任务组进行关联并自动执行队列中的任务。

    dispatch_queue_t queue =
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk1");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk2");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk3");
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"done");
    });

    // output
    blk3
    blk2
    blk1
    done

dispatch-group-async函数会将队列与相应的任务组进行关联同时自动执行,当与任务组关联的队列中的任务都执行完毕后,会通过dispatch-group-notify函数发出通知告诉用户任务组中的所有任务都执行完毕了,有点类似于dispatch-barrier-async,另外dispatch-group-notify方式并不会阻塞线程。

但是如果我们使用dispatch-group-wait函数,那么就会阻塞当前线程,等待全部处理执行结束。

    dispatch_queue_t queue =
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk1");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk2");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk3");
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"done");
    
    //output
    blk2
    blk3
    blk1
    done

dispatch-group-wait函数的返回值不为0就意味着虽然经过了指定的时间,但是属于Dispatch Group的某一个处理还在执行中,如果返回值为0为全部执行结束,当等待时间为DISPATCH-TIME-FOREVER,由dispatch-group-wait函数返回时,由于属于Dispatch Group的处理必定全部执行结束,因此返回值一直为0。当指定为DISPATCH-TIME-NOW则不用任何等待即可判定属于Dispatch Group的处理是否执行结束。

第二种是使用手动的将队列与组进行关联然后使用异步将队列进行执行,也就是dispatch-group-enter与dispatch-group-leave方法的使用。dispatch-group-enter函数进入到任务组中,然后异步执行队列中的任务,最后使用dispatch-group-leave函数离开任务组即可。

    dispatch_queue_t queue =
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"blk1");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"blk2");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"blk3");
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"done");
    });
    
    // output
    blk2
     blk3
     blk1
     done

dispatch-group-enter和dispatch-group-leave必须同时匹配出现才可以,不然就会出现无法预知的情况。

关于dispatch-barrier-async

dispatch-barrier-async函数的目的基本就是为了读写锁的问题。

对于Concurrent Dispatch Queue可能会所产生的数据库同时读写的问题,使用dispatch-barrier-async就可以很好的避免这个问题。

    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.queue", DISPATCH_QUEUE_CONCURRENT);
    __block NSInteger index = 0;
    dispatch_async(queue, ^{NSLog(@"blk0_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk1_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk2_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk3_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk4_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk5_for_reading index %ld", index);});
    
    dispatch_barrier_sync(queue, ^{
        NSLog(@"----------------------------------");
        index++;
    });
    
    dispatch_async(queue, ^{NSLog(@"blk6_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk7_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk8_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk9_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk10_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk11_for_reading index %ld", index);});
    
//output
blk0_for_reading index 0
blk1_for_reading index 0
blk2_for_reading index 0
blk3_for_reading index 0
blk5_for_reading index 0
blk4_for_reading index 0
----------------------------------
blk6_for_reading index 1
blk7_for_reading index 1
blk8_for_reading index 1
blk9_for_reading index 1
blk10_for_reading index 1
blk11_for_reading index 1

dispatch-barrier-sync 函数会等待追加到Concurrent Dispatch Queue上的并行执行的处理全部结束以后,再将指定的处理追加到该Concurrent Dispatch Queue中。然后在由Concurrent Dispatch Queue函数追加的处理执行完毕后。Concurrent Dispatch Queue才恢复为一般的动作,追加到该Concurrent Dispatch Queue的处理又开始并行执行。

关于dispatch-suspend(挂起)/dispatch-resume(恢复)

dispatch-suspend/dispatch-resume一般用于当追加大量处理到Dispatch Queue时,在追加处理的过程中有时希望不执行已追加的处理。

    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_suspend(queue);
    dispatch_async(queue, ^{
        NSLog(@"blk0");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"blk1");
    });
    sleep(2);
    dispatch_resume(queue);
    // 两秒后恢复queue,然后才会执行queue里面的操作

关于dispatch-sync

dispatch-sync相对于dispatch-async的区别就在于它是就是同步的线程操作,只有指定的block完成以后dispatch-sync才会返回。

但是dispatch-sync会带来一些死锁的情况。

将主线程的Main Disaptch Queue,在主线程中执行dispatch_sync就会造成死锁。

    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"lock");
    });

因为主线程正在执行上述代码块,所以此时的block无法执行到Main Disaptch Queue,由于Main Disaptch Queue是Serial Dispatch Queue;但是由于block无法执行,所以dispatch-sync就会一直等待block的执行,主线程此时死锁。

下面的例子也是同样的道理。

    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
            NSLog(@"lock");
        });
    });

所以由此可知,我把Main Disaptch Queue替换成任何Serial Dispatch Queue都会造成死锁的问题。

    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.queue", NULL);
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
            NSLog(@"lock");
        });
    });

关于Dispatch Semaphore的问题

Dispatch Semaphore主要用于信号量的控制并发,当处理一系列线程的时候,当数量达到一定量时,我们需要控制线程的并发量。

在GCD中有三个函数是semaphore的操作,分别是:

  • dispatch-semaphore-create   创建一个初始指定数量的信号量
  • dispatch-semaphore-signal   发送一个信号,使信号量+1,计数为0时等待,计数为1或大于1时,减去1而不等待。
  • dispatch-semaphore-wait    等待信号,使信号量-1
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
    dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (NSInteger index = 0; index < 50; index++) {
        /**
         *等待处理,直到信号量>0,信号量减1,当信号量为0时不需要在减1
         */
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(global, ^{
            NSLog(@"thread %ld", index);
            /**
             *数据处理完毕,信号量加1
             */
            dispatch_semaphore_signal(semaphore);
        });
    }

总结

GCD算是OC中一项很基础的知识了,灵活使用GCD会很大程度上提高我们的代码质量。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 3.1 Grand Central Dispatch(GCD)概要 3.1.1 什么是CGD Grand Cent...
    SkyMing一C阅读 1,563评论 0 22
  • 我们知道在iOS开发中,一共有四种多线程技术:pthread,NSThread,GCD,NSOperation: ...
    请叫我周小帅阅读 1,438评论 0 1
  • 背景 担心了两周的我终于轮到去医院做胃镜检查了!去的时候我都想好了最坏的可能(胃癌),之前在网上查的症状都很相似。...
    Dely阅读 9,178评论 21 42
  • 同步/异步 同步:多个任务情况下,一个任务A执行结束,才可以执行另一个任务B。只存在一个线程也就是主线程。 异步:...
    XLsn0w阅读 281评论 0 0
  • 一、GCD的API 1. Dispatch queue 在执行处理时存在两种Dispatch queue: 等待现...
    doudo阅读 483评论 0 0