多线程探索二-GCD

本文属于多线程系列:
多线程探索一-概念
多线程探索二-GCD
多线程探索三-NSOperation
多线程探索四-锁

概念

什么是GCD

Execute code concurrently on multicore hardware by submitting work to dispatch queues managed by the system
通过提交工作到由系统管理的调度队列,在多核硬件上并发执行代码。
Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用

优点

  • GCD自动管理线程的生命周期(创建/调度/销毁)
  • 不需要单独管理,直接使用API执行任务即可

API讲解

dispatch_barrier 栅栏

顾名思义,它起到了一个栅栏的作用,栅栏任务一定是在当前队列的任务完成之后开始,栅栏任务全部执行完之后才会开始之后的任务
栅栏函数的执行一定是要等到上一个栅栏任务完成之后才会开始下一个

dispatch_barrier.png

那么利用栅栏函数我们可以做些什么呢?

  • 实现高效率的数据访问和文件读写
  • 避免数据竞争,即线程安全的读写
    github上传了一个利用dispatch_barrier实现了一个多读单写的数组,当然这只是一个Demo
dispatch_barrier_async
 1. 立马返回 == 不阻塞当前线程
 2. 通过 dispatch_queue_create 创建的 <font color=red> 并发线程 </font>
 3. 当一个 barrier block到栈底了,它不会立马执行,会等到当前并发队列之行完当前的 block
 4. 如果你用了一个串行队列或者全局并发队列,这个函数的作用就和 dispatch_async 的作用一样了。
dispatch_barrier_sync
 1. 不会立马返回,block执行完之后返回 == 阻塞当前线程
 2. 通过 dispatch_queue_create 创建的 并发线程
 3. 当一个 barrier block到栈底了,它不会立马执行,会等到当前并发队列之行完当前的 block
 4. 如果你用了一个串行队列或者全局并发队列,这个函数的作用就和 dispatch_sync 的作用一样了
 5. 他不会对block进行copy,也不会对他进行retain,因为他是同步的
 6. 在当前队列中调用 dispatch_barrier_sync 会导致死锁

dispatch_semaphore

信号量 通过计数来控制线程的开关, 计数小于0时阻塞线程,当计数大于等于0后可通过

  1. dispatch_semaphore_create 创建信号量
  2. dispatch_semaphore_signal 发送信号,信号+1
  3. dispatch_semaphore_wait 使信号量-1,当小于0时,阻塞线程

信号量的使用场景

  1. 异步任务变成同步执行,保持线程同步
  2. 作为线程锁,保证线程安全
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

    NSLog(@"current thread %@", [NSThread currentThread]);

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    dispatch_async(queue, ^{
        NSLog(@"dispatch_async %@", [NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"dispatch_semaphore_wait %@", [NSThread currentThread]);

2021-08-22 00:37:54.097070+0800 GDDemo[80131:5289843] current thread <NSThread: 0x6000022f4400>{number = 1, name = main}
2021-08-22 00:37:54.097474+0800 GDDemo[80131:5289964] dispatch_async <NSThread: 0x60000227d2c0>{number = 7, name = (null)}
2021-08-22 00:37:54.097985+0800 GDDemo[80131:5289843] dispatch_semaphore_wait <NSThread: 0x6000022f4400>{number = 1, name = main}

dispatch_group

  1. dispatch_group_async 相当于 dispatch_async+dispatch_group_enter/dispatch_group_leave的组合
  2. dispatch_group_enter/dispatch_group_leave 当使用dispatch_async时,调用这两个方法通知group有任务加入/离开
  3. dispatch_group_notify group中任务完成后调用
  4. dispatch_group_wait 先阻塞线程不让其向下执行,等到group内的任务执行完之后继续向下执行。

dispatch_after

dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), queue, ^{
        NSLog(@"dispatch_after current thread %@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"dispatch_async at thread %@", [NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"dispatch_sync at thread %@", [NSThread currentThread]);
    });

打印结果

2021-08-21 23:43:24.588899+0800 GDDemo[79102:5235427] dispatch_sync at thread <NSThread: 0x600001edc700>{number = 1, name = main}
2021-08-21 23:43:24.589172+0800 GDDemo[79102:5235535] dispatch_async at thread <NSThread: 0x600001e59240>{number = 8, name = (null)}
2021-08-21 23:43:26.588595+0800 GDDemo[79102:5235536] dispatch_after current thread <NSThread: 0x600001e99280>{number = 7, name = (null)}

总结几点:

  1. dispatch_after 方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。
  2. dispatch_after 有能力开启一个新线程
  3. dispatch_after 延时时间并不能完全准确
  4. 如果 dispatch_time函数用 DISPATCH_TIME_NOW 的话,不如直接用dispatch_async

加个小tips

    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"dispatch_async thread %@", [NSThread currentThread]);
        [self performSelector:@selector(test) withObject:nil afterDelay:2];
    });

    NSLog(@"dispatch_async end thread %@", [NSThread currentThread]);

    - (void)test {
        NSLog(@"test at thread %@", [NSThread currentThread]);
    }

上面的代码,test函数永远不会执行,这是为什么呢?

dispatch_async我们知道时有可能开启新线程的,概念篇里我们提到,新线程默认是不会开启runloop的,而perform:afterDelay:是依赖timer的,那么如果我们在子线程里没有手动开启runloop的话,他就不会去执行了。
所以执行结果是这样的

2021-08-22 14:05:38.913990+0800 GDDemo[87282:5839362] dispatch_async end thread <NSThread: 0x600000110400>{number = 1, name = main}
2021-08-22 14:05:38.914842+0800 GDDemo[87282:5839511] dispatch_async thread <NSThread: 0x60000015d380>{number = 3, name = (null)}

dispatch_once

 dispatch_once_t one;
    dispatch_once(&one, ^{
        NSLog(@"1 at thread %@", [NSThread currentThread]);
    });

app生命周期内只调用一次,多用于单例
dispatch_once是同步任务,要等到block执行后才会返回
这里可以引申出两个问题:

  1. 单例如何销毁
  2. 单例如何避免创建多个,如果我在其他地方调用alloc init呢?

dispatch_apply

  1. dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
  2. 无论是在串行队列,还是并发队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait方法。
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

    CFAbsoluteTime currentTime0 = CFAbsoluteTimeGetCurrent();
    for (int i ; i < 10000; i++) { }
    CFAbsoluteTime totalTime0 = CFAbsoluteTimeGetCurrent() - currentTime0;
    NSLog(@"for loop total time is %f", totalTime0);

    CFAbsoluteTime currentTime1 = CFAbsoluteTimeGetCurrent();
    dispatch_apply(10000, queue, ^(size_t index) {

    });
    CFAbsoluteTime totalTime1 = CFAbsoluteTimeGetCurrent() - currentTime1;
    NSLog(@"dispatch_apply total time is %f", totalTime1);

    CFAbsoluteTime currentTime2 = CFAbsoluteTimeGetCurrent();
    dispatch_sync(queue, ^{
        for (int i ; i < 10000; i++) { }
    });
    CFAbsoluteTime totalTime2 = CFAbsoluteTimeGetCurrent() - currentTime2;
    NSLog(@"dispatch_sync total time is %f", totalTime2);

实际执行结果

2021-08-22 00:18:26.256352+0800 GDDemo[79814:5271453] for loop total time is 0.000017
2021-08-22 00:18:26.256790+0800 GDDemo[79814:5271453] dispatch_apply total time is 0.000249
2021-08-22 00:18:26.256949+0800 GDDemo[79814:5271453] dispatch_sync total time is 0.000010

看到这个结果我还挺奇怪的,dispatch_apply反而是最耗时的。

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

推荐阅读更多精彩内容