ObjC-多线程之GCD

介绍

GCD(Grand Central Dispatch)是基于C语言开发的一套多线程开发机制,也是目前苹果官方推荐的多线程开发方法。GCD 是一套低层API,基于C语言开发,完全面向过程的,用于将任务切分成单一任务提交至队列并发或者串行执行。遵循FIFO原则,先提交到队列的先执行。

iOS4.0中首度引入GCD,GCD是管理任务执行的一项技术,它使得我们对多任务处理变得更加方便和有效。它支持同步或异步任务处理,串行或并行的处理队列(Dispath Queue),非系统调用的信号量机制,定时任务处理,进程、文件或网络的监听任务等。这个庞大的任务处理技术大大减少了线程的管理工作,使基于任务的开发变得更加高效。

 GCD 和 block (<http://scottmaxiao.github.io/IOS-Block.html>)的配合使用,可以方便地进行多线程编程。

基本概念

串行和并行

串行:任务按先后顺序逐个执行。

并行:后面的任务不会等前面的任务完成了再执行,同样会遵循先添加先执行的原则,但添加间隔往往忽略不计。所以看上去像是一起执行。

并发和并行

并发是指两个或多个事件在同一时间间隔内发生。单核CPU并发切换执行多个任务。

并行是指两个或者多个事件在同一时刻发生。多核CPU可以平行执行多个任务。

下图描述的就是并发和并行的区别。

gcd

同步和异步

同步:一个同步函数只在完成了预定任务后才返回。会阻塞当前线程。

异步:异步时任务开启会立即返回,不阻塞当前线程去执行下一个函数。异步会开启其他线程。

函数说明

Dispatch Queue

Dispatch Queue是用来执行任务的队列,是GCD中最基本的元素之一。

Dispatch Queue分为两种:
- Serial Dispatch Queue,按添加进队列的顺序(先进先出)一个接一个的执行
- Concurrent Dispatch Queue,并发执行队列里的任务

简而言之,Serial Dispatch Queue只使用了一个线程,Concurrent Dispatch Queue使用了多个线程(具体使用了多少个,由系统决定)。
手动创建Dispatch Queue
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", nil);
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
//创建并行队列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_CONCURRENT);
获取系统提供的Dispatch Queue

系统提供的Dispatch Queue有两种类型

  • Main Dispatch Queue:
    实际上就是Serial Dispatch Queue(并且只有一个),一般只在需要更新UI时我们才获取Main Dispatch Queue

  • Global Dispatch Queue:
    实际上是一个Concurrent Dispatch Queue。大多数情况下,可以不必通过dispatch_queue_create函数生成Concurrent Dispatch Queue,而是只要获取Global Dispatch Queue使用即可。Global Dispatch Queue有4个优先级,分别是:High、Default、Low、Background。

//获取Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue()
//获取Global Dispatch Queue
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

需要注意一点,如果是在OS X 10.8或iOS 6以及之后版本中使用,Dispatch Queue将会由ARC自动管理,如果是在此之前的版本,需要自己手动释放,如下:

//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", nil);

dispatch_async(queue, ^{
        ...
});

dispatch_release(queue) ;

dispatch_async和dispatch_sync,dispatch_barrier_async

dispatch_async()

dispatch_async():异步添加进任务队列,它不会做任何等待。
    dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(concurrentQueue, ^{
        NSLog(@"2");
        [NSThread sleepForTimeInterval:5];
        NSLog(@"3");
    });
    NSLog(@"4");

输出
 11:42:43.820 GCDSeTest[568:303] 1

 11:42:43.820 GCDSeTest[568:303] 4

 11:42:43.820 GCDSeTest[568:1003] 2

 11:42:48.821 GCDSeTest[568:1003] 3//模拟长时间操作时间

dispatch_sync()

dispatch_sync():同步添加操作。他是等待添加进队列里面的操作完成之后再继续执行。
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_sync(concurrentQueue, ^(){
        NSLog(@"2");
        [NSThread sleepForTimeInterval:10];
        NSLog(@"3");
    });
    NSLog(@"4");
输出
11:36:25.313 GCDSeTest[544:303] 1

11:36:25.313 GCDSeTest[544:303] 2

11:36:30.313 GCDSeTest[544:303] 3//模拟长时间操作

11:36:30.314 GCDSeTest[544:303] 4

关于死锁:

//在main线程使用“同步”方法提交Block,必定会死锁。
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"I am block...");
});

dispatch_sync在等待block语句执行完成,而block语句需要在主线程里执行,所以dispatch_sync如果在主线程调用就会造成死锁

dispatch_barrier_async

dispatch_barrier_async用于等待前面的任务执行完毕后自己才执行,而它后面的任务需等待它完成之后才执行。一个典型的例子就是数据的读写,通常为了防止文件读写导致冲突,我们会创建一个串行的队列,所有的文件操作都是通过这个队列来执行,比如FMDB,这样就可以避免读写冲突。不过其实这样效率是有提升的空间的,当没有更新数据时,读操作其实是可以并行进行的,而写操作需要串行的执行,如何实现呢:
dispatch_queue_t queue = dispatch_queue_create("Database_Queue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"reading data1");
    });
    dispatch_async(queue, ^{
        NSLog(@"reading data2");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"writing data1");
        [NSThread sleepForTimeInterval:1];

    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"reading data3");
    });

我们将写数据的操作放在dispatch_barrier_async中,这样能确保在写数据的时候会等待前面的读操作完成,而后续的读操作也会等到写操作完成后才能继续执行,提高文件读写的执行效率。

dispatch_apply

dispatch_apply类似一个for循环,会在指定的dispatch queue中运行block任务n次,如果队列是并发队列,则会并发执行block任务,dispatch_apply是一个同步调用,block任务执行n次后才返回,循环迭代的执行顺序是不确定的。
dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
//并发的运行一个block任务5次
dispatch_apply(5, queue, ^(size_t i) {
    NSLog(@"do a job %zu times",i+1);
});
NSLog(@"go on");


Dispatch Block

添加到gcd队列中执行的任务是以block的形式添加的,block封装了需要执行功能,block带来的开发效率提升就不说了,gcd跟block可以说是一对好基友,能够很好的配合使用。
创建 block
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
//创建block
dispatch_block_t block = dispatch_block_create(0, ^{
        NSLog(@"do something");
    });
dispatch_async(queue, block);

----
在创建block的时候我们也可以通过设置QoS,指定block对应的优先级,在dispatch_block_create_with_qos_class中指定QoS类别即可:
----
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block = dispatch_block_create_with_qos_class(0, QOS_CLASS_USER_INITIATED, -1, ^{
        NSLog(@"do something with QoS");
    });
dispatch_async(queue, block);

dispatch_block_wait
当需要等待前面的任务执行完毕时,我们可以使用dispatch_block_wait这个接口,设置等待时间DISPATCH_TIME_FOREVER会一直等待直到前面的任务完成:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block = dispatch_block_create(0, ^{
    NSLog(@"before sleep");
    [NSThread sleepForTimeInterval:1];
    NSLog(@"after sleep");
});
dispatch_async(queue, block);
//等待前面的任务执行完毕
dispatch_block_wait(block, DISPATCH_TIME_FOREVER);
NSLog(@"coutinue");

dispatch_block_notify
dispatch_block_notify当观察的某个block执行结束之后立刻通知提交另一特定的block到指定的queue中执行,该函数有三个参数,第一参数是需要观察的block,第二个参数是被通知block提交执行的queue,第三参数是当需要被通知执行的block,函数的原型:
void dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue,
      dispatch_block_t notification_block);
      
-----

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
  dispatch_block_t previousBlock = dispatch_block_create(0, ^{
      NSLog(@"previousBlock begin");
      [NSThread sleepForTimeInterval:1];
      NSLog(@"previousBlock done");
  });
  dispatch_async(queue, previousBlock);
  dispatch_block_t notifyBlock = dispatch_block_create(0, ^{
      NSLog(@"notifyBlock");
  });
  //当previousBlock执行完毕后,提交notifyBlock到global queue中执行
  dispatch_block_notify(previousBlock, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), notifyBlock);

dispatch_block_cancel
之前在介绍nsopreration的时候提到它的一个优点是可以取消某个operation,现在在iOS8之后,提交到gcd队列中的dispatch block也可取消了,只需要简单的调用dispatch_block_cancel传入想要取消的block即可:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block1 = dispatch_block_create(0, ^{
    NSLog(@"block1 begin");
    [NSThread sleepForTimeInterval:1];
    NSLog(@"block1 done");
});
dispatch_block_t block2 = dispatch_block_create(0, ^{
    NSLog(@"block2 ");
});
dispatch_async(queue, block1);
dispatch_async(queue, block2);
dispatch_block_cancel(block2);


dispatch_semaphore

在GCD中有三个函数是semaphore的操作,分别是:
  dispatch_semaphore_create   创建一个semaphore
  dispatch_semaphore_signal   发送一个信号
  dispatch_semaphore_wait    等待信号
  简单的介绍一下这三个函数,第一个函数有一个整形的参数,我们可以理解为信号的总量,dispatch_semaphore_signal是发送一个信号,自然会让信号总量加1,dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1,根据这样的原理,我们便可以快速的创建一个并发控制来同步任务和有限资源访问控制。
  
  
http://www.cnblogs.com/zhidao-chen/p/3600399.html
http://blog.csdn.net/fhbystudy/article/details/25918451
http://www.w2bc.com/Article/16104
http://www.mamicode.com/info-detail-1218654.html


dispatch_after()

Dispatch Queue的延迟执行。
// 延迟 2 秒执行:
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
        
dispatch_after(popTime, dispatch_get_main_queue(), ^{
    // code to be executed on the main queue after delay
});

这里要说一下dispatch_time函数,其原型如下:

dispatch_time_t dispatch_time ( dispatch_time_t when, int64_t delta );
  • 第一个参数一般是DISPATCH_TIME_NOW,表示从现在开始。

  • 第二个参数就是真正的延时的具体时间。这里要特别注意的是,delta参数是“纳秒!”,就是说,延时1秒的话,delta应该是“1000000000”=。=,太长了,所以理所当然系统提供了常量,如下:

#define NSEC_PER_SEC 1000000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
关键词解释:
 NSEC:纳秒。
 USEC:微妙。
 SEC:秒
 PER:每

所以:
 NSEC_PER_SEC,每秒有多少纳秒。
 USEC_PER_SEC,每秒有多少毫秒。(注意是指在纳秒的基础上)
 NSEC_PER_USEC,每毫秒有多少纳秒。

[参考]http://www.cocoachina.com/ios/20150505/11751.html


dispatch_once

dispatch\_once是线程安全的。它能保证多个线程同时调用却只会执行块一次。在dispatch\_once返回之前,所有线程将会等待直到执行完毕。

dispatch\_once函数通常用在单例模式上,它可以保证在程序运行期间某段代码只执行一次,如果我们要通过dispatch\_once创建一个单例类。
+(id)getInstance{
    
    static GapSet *gapSet;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        gapSet = [[GapSet alloc] initObject];
    });
    
    return gapSet;
}

注意:dispatch_once_t必须是全局或static变量。

[参考]
http://blog.afantree.com/gcd/translation-dispatch-once-secret.html


dispatch_suspend和dispatch_resume

http://www.jianshu.com/p/85b75c7a6286

dispatch_group

如果想在dispatch\_queue中所有的任务执行完成后在做某种操作,在串行队列中,可以把该操作放到最后一个任务执行完成后继续,但是在并行队列中怎么做呢。这就有dispatch\_group 成组操作。比如
 dispatch_group_t group = dispatch_group_create();
 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
      // 并行执行的线程一
      NSLog(@"dispatch-1");
 });
 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
      // 并行执行的线程二
      NSLog(@"dispatch-2");
 });
 dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
      // 汇总结果
      NSLog(@"dispatch-end");
 });
dispatch_group_wait(group,DISPATCH_TIME_FOREVER);

Dispatch IO


Dispatch Source

http://www.jianshu.com/p/f9e01c69a46f


[参考]

http://www.jianshu.com/p/f9e01c69a46f

http://blog.devtang.com/blog/2012/02/22/use-gcd/

http://blog.csdn.net/zhangao0086/article/details/38904923

http://www.cocoachina.com/ios/20150505/11751.html

http://www.cnblogs.com/SnailFish/articles/3199863.html

http://www.cnblogs.com/zhidao-chen/category/461198.html

http://blog.afantree.com/ios/the-gcd-learning-directorygcd-debrief.html

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

推荐阅读更多精彩内容

  • 谈到iOS多线程,一般都会谈到四种方式:pthread、NSThread、GCD和NSOperation。其中,苹...
    攻城狮GG阅读 270评论 0 3
  • 程序中同步和异步是什么意思?有什么区别? 解释一:异步调用是通过使用单独的线程执行的。原始线程启动异步调用,异步调...
    风继续吹0阅读 1,031评论 1 2
  • GCD (Grand Central Dispatch) :iOS4 开始引入,使用更加方便,程序员只需要将任务添...
    池鹏程阅读 1,328评论 0 2
  • Dispatch Queues dispatch queues是执行任务的强大工具,允许你同步或异步地执行任意代码...
    YangPu阅读 643评论 0 4
  • 我爱你!没有任何人能代替你在我心里的位置!尽管我现在无法和你在一起,但是我相信我一定会和你共度余生的!我要和你一起...
    太虚环境阅读 232评论 0 0