iOS 多线程之GCD

1 GCD简述

Apple源码--Dispatch

Grand Central Dispatch(GCD)Apple开发的一个多核编程的较新的解决方法.它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统.它是一个在线程池模式的基础上执行的并发任务.在Mac OS X 10.6雪豹中首次推出,也可在iOS 4及以上版本使用.

GCD优点

  • GCD可用于多核的并行运算
  • GCD会自动利用更多的CPU内核(比如双核、四核)
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

2 GCD任务和队列

任务

任务: 执行操作的意思,就是说你在线程中执行的那段代码.在GCD中是放在block中的.执行任务有两种方式同步执行异步执行

  • 同步执行(sync):
    (1) 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行
    (2) 能在当前线程中执行任务,不具备开启新线程的能力
  • 异步执行(async):
    (1) 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务
    (2) 可以在新的线程中执行任务,具备开启新线程的能力
    注意: <u>异步执行(async)虽然具有开启新线程的能力,但是并不一定开启新线程.这跟任务所指定的队列类型有关(下面会讲)</u>

任务的创建分为:同步任务dispatch_sync和异步任务dispatch_async

dispatch_sync(queue, ^{
    // 同步执行任务
    // code snippet
});
dispatch_async(queue, ^{
    // 异步执行任务
    // code snippet
});

队列(Dispatch Queue)

队列:队列指执行任务的等待队列,即用来存放任务的队列.队列是一种特殊的线性表,采用FIFIO(先进先出)的原则.

image

GCD队列分为两种:串行队列并行队列
主要区别: 执行顺序不同,以及开启线程数不同.

  • 串行队列(Serial Dispatch Queue):每次只有一个任务被执行.让任务一个接着一个地执行.(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
  • 并发队列(Concurrent Dispatch Queue):可以让多个任务并发(同时)执行.(可以开启多个线程,并且同时执行任务)

注意:<u>并发队列 的并发功能只有在异步(dispatch_async)方法下才有效.其他线程下,串行执行任务</u>

image

队列的创建:dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);

  • 参数0: 队列的唯一标识符,队列的名称推荐使用应用程序id这种逆序全程域名
  • 参数1: 用来识别是串行队列还是并发队列 (DISPATCH_QUEUE_SERIAL, DISPATCH_QUEUE_CONCURRENT)
// 串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_SERIAL);
// 并发队列
dispatch_queue_t concurrentlQueue = dispatch_queue_create("com.appleid.functionB", DISPATCH_QUEUE_CONCURRENT);
// 主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 全局并发队列 (参数0: 填写默认 , 参数1: 填写0)
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

3 队列和同步,异步任务组合

区别 并发队列 串行队列 主队列
同步(sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 死锁卡住不执行
异步(async) 有开启新线程,并发执行任务 有开启新线程(1条),串行执行任务 没有开启新线程,串行执行任务

4 GCD方法

4.1 dispatch_after:延时执行方法

主要:<u>dispatch_after方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中.准确来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after 方法是很有效的</u>

- (void)after {
    NSLog(@"当前线程%@", [NSThread currentThread]);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"线程after:%@", [NSThread currentThread]);  // 打印当前线程
    });
}

4.2 dispatch_once:只执行一次

在创建单例、或者有整个程序运行过程中只执行一次的代码时,就可以使用dispatch_once方法.dispatch_once方法能保证某段代码在程序运行过程中只被执行1次,并且即使在多线程的环境下, dispatch_once也可以保证线程安全.

- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行一次, 默认线程安全
        // code snippet
    });
}

4.3 dispatch_barrier_async: 栅栏函数

Apple官方文档

对这个函数的调用总是在block被提交之后立即返回,并且从不等block待被调用.当barrier block到达私有并发队列的前端时,它不会立即执行.相反,队列将等待,直到当前执行的块完成执行.此时,barrier block自己执行.在barrier block之后提交的任何block都不会执行,直到barrier block完成.
您指定的队列应该是您自己使用dispatch_queue_create函数创建的并发队列.如果传递给此函数的队列是一个串行队列或一个全局并发队列,则此函数的行为与dispatch_async函数类似.

image
  • dispatch_barrier_sync

- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任务2, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务3, %@", [NSThread currentThread]);
    });

    dispatch_barrier_async(queue, ^{
        [NSThread sleepForTimeInterval:2- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任务2, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务3, %@", [NSThread currentThread]);
    });

    
    dispatch_barrier_sync(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务4 barrier, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务5, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务6, %@", [NSThread currentThread]);
    });
    
    NSLog(@"任务7, %@", [NSThread currentThread]);
}];
        NSLog(@"barrier任务4, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务5, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务6, %@", [NSThread currentThread]);
    });
}

执行结果:

任务2, <NSThread: 0x139040570>{number = 4, name = (null)}
任务3, <NSThread: 0x139458a90>{number = 6, name = (null)}
任务1, <NSThread: 0x139043ce0>{number = 5, name = (null)}
任务4 barrier, <NSThread: 0x137e0b8d0>{number = 1, name = main}
任务7, <NSThread: 0x137e0b8d0>{number = 1, name = main}
任务5, <NSThread: 0x139043ce0>{number = 5, name = (null)}
任务6, <NSThread: 0x139458a90>{number = 6, name = (null)}
  • dispatch_barrier_async
- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任务2, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务3, %@", [NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务4 barrier, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务5, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务6, %@", [NSThread currentThread]);
    });

    NSLog(@"任务7, %@", [NSThread currentThread]);
}

执行结果:

任务7, <NSThread: 0x10360aea0>{number = 1, name = main}
任务2, <NSThread: 0x1035a4f90>{number = 3, name = (null)}
任务1, <NSThread: 0x105e79130>{number = 6, name = (null)}
任务3, <NSThread: 0x1036afae0>{number = 5, name = (null)}
任务4 barrier, <NSThread: 0x1036afae0>{number = 5, name = (null)}
任务5, <NSThread: 0x1036afae0>{number = 5, name = (null)}
任务6, <NSThread: 0x105e79130>{number = 6, name = (null)}

4.4 dispatch_apply:快速迭代(高效for循环)

Apple官方文档

此函数将多个调用的block提交给调度队列,并等待任务block的所有迭代完成后再返回.如果目标队列是由dispatch_get_global_queue返回的并发队列,则可以并发调用该block,因此它必须是reentrant安全的.在并发队列中使用此函数可以作为一种有效的并行for循环.
迭代的当前索引被传递给block的每次调用.

- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // dispatch_apply是同步的
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"同步index:%zu %@", index, [NSThread currentThread]);
    });

    // 如果想异步,包装一层
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_apply(10, queue, ^(size_t index) {
            NSLog(@"异步index:%zu %@", index, [NSThread currentThread]);
        });
    });
}

4.5 dispatch_group: 队列组

  • 调用队列组的dispatch_group_async先把任务放到队列中,然后将队列放入队列组中.或者使用队列组的dispatch_group_enter、dispatch_group_leave组合来实现
  • 调用队列组的dispatch_group_notify回到指定线程执行任务.或者使用dispatch_group_wait回到当前线程继续向下执行(会阻塞当前线程)
  • dispatch_group_notify: 监听group中任务的完成状态,当所有的任务都执行完成后,追加任务到group中,并执行任务
- (void)group {
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务2, %@", [NSThread currentThread]);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务3, %@", [NSThread currentThread]);
        NSLog(@"group任务完成");
    });
}

执行结果:

任务2, <NSThread: 0x281f87280>{number = 5, name = (null)}
任务1, <NSThread: 0x281fa0c00>{number = 8, name = (null)}
任务3, <NSThread: 0x281fc4d80>{number = 1, name = main}
group任务完成
  • dispatch_group_wait:暂停当前线程(阻塞当前线程),等待指定的group中的任务执行完成后,才会往下继续执行
- (void)group {
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务2, %@", [NSThread currentThread]);
    });

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"group任务完成");
 }

执行结果:

任务2, <NSThread: 0x2817f9540>{number = 4, name = (null)}
任务1, <NSThread: 0x2817ff9c0>{number = 6, name = (null)}
group任务完成
  • dispatch_group_enter(), dispatch_group_leave
- (void)group1 {
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务2, %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务3, %@", [NSThread currentThread]);
        NSLog(@"group任务完成");
    });
}

执行结果:

任务2, <NSThread: 0x281df3e40>{number = 4, name = (null)}
任务1, <NSThread: 0x281de3f80>{number = 7, name = (null)}
任务3, <NSThread: 0x281db0d80>{number = 1, name = main}
group任务完成

4.6 dispatch_semaphore: 信号量

dispatch_semaphoreGCD中的信号量,持有计数的信号, dispatch Semaphore中,使用计数来完成这个功能,计数小于0时等待,不可通过.计数为0或大于0时可通过.

主要使用:

  • 保持线程同步,将异步执行任务转换为同步执行任务
  • 保证线程安全,为线程加锁

dispatch_semaphore三个方法:

  • dispatch_semaphore_create: 创建一个semaphore并初始化信号的总量
  • dispatch_semaphore_signal: 发送一个信号,信号计数 + 1
  • dispatch_semaphore_wait: 可以使总信号量 - 1,信号总量小于0时就会一直等待(阻塞所在线程),否则就可以正常执行
- (void)semaphor {
    
    NSLog(@"当前线程:%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);

        dispatch_semaphore_signal(semaphore);
    });
    NSLog(@"当前线程1:%@", [NSThread currentThread]);

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"任务完成");

}

执行结果:

当前线程:<NSThread: 0x282784d80>{number = 1, name = main}
当前线程1:<NSThread: 0x282784d80>{number = 1, name = main}
任务1, <NSThread: 0x2827d6cc0>{number = 6, name = (null)}
任务完成

从打印结果可知执行流程为:

  • semaphore开始计数为0
  • 异步任务加入队列之后,不等待继续执行, 执行到dispatch_semaphore_wait方法, 信号量计数- 1-1小于0,当前线程进入等待状态
  • 任务1执行开始执行, 执行完成后,执行dispatch_semaphore_signal,信号量计数+ 10,阻塞线程恢复继续执行

完整代码见GitHub->多线程(附大厂面试讲解)


如有不足之处,欢迎予以指正, 如果感觉写的不错,记得给个赞呦!

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

推荐阅读更多精彩内容