iOS多线程(二)--GCD详解

目录:

iOS多线程(一)--pthread、NSThread
iOS多线程(二)--GCD详解
iOS多线程(三)--NSOperation详解

1. GCD简介

Grand Central Dispatch (GCD) 是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。
GCD有很多优点具体如下:
1)GCD可用于多核的并行运算
2)GCD会自动利用更多的CPU内核(比如双核、四核)
3)GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
4)程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

2. 任务和队列

学习GCD之前,先来了解GCD中两个核心概念:任务和队列。

  1. 任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在GCD中是放在block中的。执行任务有两种方式:同步执行和异步执行。两者的主要区别是:是否具备开启新线程的能力。
  • 同步执行(sync):只能在当前线程中执行任务,不具备开启新线程的能力
  • 异步执行(async):可以在新的线程中执行任务,具备开启新线程的能力
  1. 这里的队列指任务队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。在GCD中有两种队列:串行队列和并行队列。
  • 并行队列(Concurrent Dispatch Queue):可以让多个任务并行(同时)执行(自动开启多个线程同时执行任务)。并行功能只有在异步(dispatch_async)函数下才有效(应该是目前开启了多少线程,才有多少个任务才并行执行吧)。注意特殊的并行队列-全局并行队列:dispatch_get_global_queue来创建全局并行队列。GCD默认提供了全局的并行队列,需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用0即可。
  • 串行队列(Serial Dispatch Queue):让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)。注意特殊的串行队列-主队列:dispatch_get_main_queue()获得主队列, 所有放在主队列中的任务,都会放到主线程中执行。

3. GCD的使用

GCD的使用步骤其实很简单,只有两步。
1.创建一个队列(串行队列或并行队列)
2.将任务添加到队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)

下边来看看队列的创建方法和任务的创建方法。

3.1 队列的创建方法

可以使用dispatch_queue_create来创建对象,需要传入两个参数,第一个参数表示队列的唯一标识符,用于DEBUG,可为空;第二个参数用来识别是串行队列还是并行队列。DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并行队列。

// 串行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

3.2 任务的创建方法

// 同步执行任务创建方法
dispatch_sync(queue, ^{
    NSLog(@"%@",[NSThread currentThread]);    // 这里放任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
    NSLog(@"%@",[NSThread currentThread]);    // 这里放任务代码
});

3.3 GCD的组合方式

队列的不同和任务执行方式的不同可以组成以下6种组合,尤其注意后两种主队列(主队列是一种特殊的串行队列,主队列里的任务只在主线程执行)的组合:

  1. 并行队列 + 同步执行
  2. 并行队列 + 异步执行
  3. 串行队列 + 同步执行
  4. 串行队列 + 异步执行
  5. 主队列 + 同步执行
  6. 主队列 + 异步执行

不同组合方式效果不同,直接参看表格如下:

并行队列 串行队列 主队列
同步(sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务
异步(async) 有开启新线程,并行执行任务 有开启新线程(1条),串行执行任务 没有开启新线程,串行执行任务

相信网上其他帖子的示例已经很多了,我这里只列举下主队列的同步执行和异步执行,其他的便不再一一赘述:

3.3.1 主队列同步执行

- (void)syncMain
{
    NSLog(@"syncMain---begin");

    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });   

    NSLog(@"syncMain---end");
}

输出结果
2016-09-03 19:32:15.356 GCD[11670:1908306] syncMain---begin

我们发现输出begin后则没有再继续往下执行了,现在来具体分析原因:我们在主队列上增加同步任务,同步任务有个特点是立即执行,而主队列里的任务一定是在主线程执行的,但主线程正在执行syncMain这个方法,我们增加到主队列里的任务必须要等syncMain方法执行完之后才能执行,但我们很容易发现syncMain方法由于方法体里的同步任务是无法执行完的,这便是死锁原因所在。

3.3.2 主队列异步执行

- (void)asyncMain
{
    NSLog(@"asyncMain---begin");

    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });  

    NSLog(@"asyncMain---end");
}

输出结果:
2016-09-03 19:33:54.995 GCD[11706:1911313] asyncMain---begin
2016-09-03 19:33:54.996 GCD[11706:1911313] asyncMain---end
2016-09-03 19:33:54.996 GCD[11706:1911313] 1------{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 1------{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 2------{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 2------{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 3------{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 3------{number = 1, name = main}

分析上面输出,由于是在主队列上异步执行,不需要立即执行,但是在主队列上的任务必须是在主线程执行,所以往主队列上添加的三个异步任务是顺序一一输出结果的。

4 GCD线程之间的通讯

在iOS开发过程中,我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作,然后在主线程里边进行UI刷新,例如:点击、滚动、拖拽等事件。当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    for (int i = 0; i < 2; ++i) {
        NSLog(@"1------%@",[NSThread currentThread]);
    }

    // 回到主线程
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"2-------%@",[NSThread currentThread]);
    });
});

5 GCD的其他使用

5.1 多用派发队列,少用同步锁

如果有多个线程要执行同一份代码,则可能出现问题。在之前我们使用锁来实现同步机制,具体实现有以下两种方式:

//1. 内置的同步块
- (void)synchronizedMethod {
    @synchronized (self) {
        //save
    }
}
//2. NSLock对象
_lock = [[NSLock alloc] init];

- (void)synchronizedMethod {
    [_lock lock];
    //safe
    [_lock unlock];
}

但是滥用@synchronized (self)同步块非常危险,因为所有同步块都会抢夺同一个锁,比如一个类里有很多属性都需要同步时,那么每个属性的同步块都得等其他所有的同步块执行完,但我们只是想要每个属性各自独立的同步即可。而且synchronized要求被其锁住的对象一定要正确,不能应该锁A,但其实锁了B。
然后直接使用NSLock对象的话,一旦遇到死锁也是会非常的麻烦。
针对以上两种情况,GCD提供了一种非常简单的解决方案,就是串行同步队列,将读取操作和写入操作都写入同一个队列,即可保证数据同步。

5.2 多用派发队列,少用performSelector系列方法

performSelector系列有以下方法:

// NSObject (NSDelayedPerforming)
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;

//NSObject (返回对象是id,如果遇到返回对象是void或者基础数据类型就需要经过一番转换,而且最多能带两个参数)
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

下面我们来看performSelector方法的使用,如下例;

    SEL selector;
    if (/*conditon 1*/) {
        selector = @selector(fun1);
    }else if(/*conditon 2*/) {
        selector = @selector(fun2);
    }else {
        selector = @selector(fun3);
    }

代码看起来没什么问题,但是编译器却给出了警告信息:

warning:performSelector may cause a leak beacause its selector

原因在于编译器不知道将要调用的选择子,因此不了解其方法名及其返回值,所以没办法运用ARC的内存管理规则来判断返回值是不是该释放,因此ARC采用了比较谨慎的办法就是不添加释放操作。但这么做可能导致内存泄露,因为返回值在被返回时被方法保留了。
想把某任务放到主线程执行,可以有以下两种选择方式,但我们还是应该选择GCD

//1.
[self performSelectorOnMainThread:@selector(doSomething) withObject:nil waitUntilDone:NO];

//2.
dispatch_async(dispatch_get_main_queue(), ^{
        //[self doSomething]
    });

5.3 dispatch_after

延后执行也有两种选择方式,我们依然应该选择GCD

//1.
[self performSelectorOnMainThread:@selector(doSomething) withObject:nil waitUntilDone:NO];

//2.
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,(int64_t)(5.0*NSEC_PER_SEC));
    dispatch_after(time, dispatch_get_main_queue(), ^{
        //[self doSomething]
    });

5.4 dispatch_group,根据系统资源状态来执行任务

dispatch_group能把任务分组,调用者可以等待这组任务执行完毕,也可以在提供回调函数后继续往下执行,这组任务完成后,调用者会得到通知。这个功能有许多用途,最重要以及最值得注意的是要将并发执行的多个任务合为一个组,于是调用者就能知道这组任务什么时候才能全部执行完毕。
dispatch_group有以下几个相关方法:

5.4.1 dispatch_group_create

dispatch_group_t group =  dispatch_group_create(); //创建任务分组

5.4.2 dispatch_group_async

void dispatch_group_async(dispatch_group_t group, 
                          dispatch_queue_t queue, 
                          dispatch_block_t block); 
  • group ——对应的任务组,之后可以通过dispatch_group_wait或者dispatch_group_notify监听任务组内任务的执行情况
  • queue ——block任务执行的线程队列,任务组内不同任务的队列可以不同
  • block —— 执行任务的block

5.4.3 dispatch_group_enter

用于添加对应任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数加1,当未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞和dispatch_group_notify的block执行

void dispatch_group_enter(dispatch_group_t group);

5.4.4 dispatch_group_leave

用于减少任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数减1,dispatch_group_enter和dispatch_group_leave要匹配,不然系统会认为group任务没有执行完毕

void dispatch_group_leave(dispatch_group_t group);

5.4.5 dispatch_group_wait

等待组任务完成,会阻塞当前线程,当任务组执行完毕时,才会解除阻塞当前线程

long dispatch_group_wait(dispatch_group_t group, 
                         dispatch_time_t timeout); 
  • group ——需要等待的任务组
  • timeout ——等待的超时时间(即等多久),单位为dispatch_time_t。如果设置为DISPATCH_TIME_FOREVER,则会一直等待(阻塞当前线程),直到任务组执行完毕

5.4.6 dispatch_group_notify

待任务组执行完毕时调用,不会阻塞当前线程

void dispatch_group_notify(dispatch_group_t group,
                           dispatch_queue_t queue, 
                           dispatch_block_t block);
  • group ——需要监听的任务组
  • queue ——block任务执行的线程队列,和之前group执行的线程队列无关
  • block ——任务组执行完毕时需要执行的任务block

5.4.7 使用示例

最常用的用法如下:

dispatch_group_t group =  dispatch_group_create();
NSLog(@"group one start");
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"group one finish");
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"group finished");
});

输出结果如下:
2017-09-11 16:09:00.125 DispatchGroupDemo[254:11537] group one start
2017-09-11 16:09:00.127 DispatchGroupDemo[254:11569] group one finish
2017-09-11 16:09:00.135 DispatchGroupDemo[254:11537] group finished

我们在上面的demo上深挖一下,如果是在组里执行异步任务呢?

        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        NSLog(@"group one start");
    
        dispatch_group_async(group, queue, ^{
            dispatch_async(queue, ^{
                sleep(1); //这里线程睡眠1秒钟,模拟异步请求
                NSLog(@"group one finish");
            });
        });
    
        dispatch_group_notify(group, queue, ^{
            NSLog(@"group finished");
        });

输出结果如下:
2017-09-11 16:12:42.732 DispatchGroupDemo[263:12355] group one start
2017-09-11 16:12:42.734 DispatchGroupDemo[263:12379] group finished
2017-09-11 16:12:43.741 DispatchGroupDemo[263:12378] group one finish

我们发现输出结果并不是我们想要的,在group中嵌套了一个异步任务时,group并没有等待group内的异步任务执行完毕才进入dispatch_group_notify中,这是因为,在dispatch_group_async中又启了一个异步线程,而异步线程是直接返回的,所以group就认为是执行完毕了。
为了解决这个问题,需要引入dispatch_group_enter和dispatch_group_leave的使用了。

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"group one start");
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        dispatch_async(queue, ^{
            sleep(1); //这里线程睡眠1秒钟,模拟异步请求
            NSLog(@"group one finish");
            dispatch_group_leave(group);
        });
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"group finished");
    });

输出结果如下:
2017-09-11 16:16:39.149 DispatchGroupDemo[281:13448] group one start
2017-09-11 16:16:40.156 DispatchGroupDemo[281:13469] group one finish
2017-09-11 16:16:40.157 DispatchGroupDemo[281:13469] group finished

5.5 dispatch栅栏方法

有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。

假设我们原先有6个任务要执行,我们现在要插入一个任务0,这个任务0要在1、2、3都并发执行完了之后才能执行,而4、5、6号任务要在这个任务0结束后才允许并发。对于这样一种需求,很多朋友的第一反应就是用个group就解决了。确实如此,但是系统提供了一种更加简单地方法,那就是dispatch栅栏方法。
这就需要用到dispatch_barrier_async/dispatch_barrier_sync方法在两个操作组间形成栅栏。
下面我们具体来看看dispatch_barrier_async和dispatch_barrier_sync的区别:

//dispatch_barrier_async示例
- (IBAction)testBarrierAsync:(id)sender {
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"group one start");
    
    dispatch_async(queue, ^{
        NSLog(@"task 1 finish");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"task 2 finish");
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"== %@ == barrier finished",[NSThread currentThread]);
    });
    
    NSLog(@"aaa");
    dispatch_async(queue, ^{
        NSLog(@"task 3 finish");
    });
    
    NSLog(@"bbb");
    dispatch_async(queue, ^{
        NSLog(@"task 4 finish");
    });

输出结果如下:
2017-09-12 19:24:10.409 TestGCD[1142:139438] group one start
2017-09-12 19:24:10.410 TestGCD[1142:139438] aaa
2017-09-12 19:24:10.410 TestGCD[1142:139438] bbb
2017-09-12 19:24:11.419 TestGCD[1142:139792] task 2 finish
2017-09-12 19:24:11.419 TestGCD[1142:139770] task 1 finish
2017-09-12 19:24:11.420 TestGCD[1142:139770] == {number = 4, name = (null)} == barrier finished
2017-09-12 19:24:11.420 TestGCD[1142:139770] task 3 finish
2017-09-12 19:24:11.421 TestGCD[1142:139770] task 4 finish

我们现在将上面的示例里的dispatch_barrier_async改成dispatch_barrier_sync试试,其他的代码不变,则会发现输出变成了:

输出结果如下:
2017-09-12 19:25:30.066 TestGCD[1142:139438] group one start
2017-09-12 19:25:30.067 TestGCD[1142:139927] task 1 finish
2017-09-12 19:25:30.068 TestGCD[1142:139927] task 2 finish
2017-09-12 19:25:30.069 TestGCD[1142:139438] == {number = 1, name = main} == barrier finished
2017-09-12 19:25:30.069 TestGCD[1142:139438] aaa
2017-09-12 19:25:30.070 TestGCD[1142:139438] bbb
2017-09-12 19:25:30.072 TestGCD[1142:139941] task 3 finish
2017-09-12 19:25:30.073 TestGCD[1142:139927] task 4 finish

根据输出结果我们可以看出,barrier块是肯定在1,2执行完之后,3,4执行完之前输出的,但aaa和bbb的输出位置却不一样。总结下dispatch_barrier_async与dispatch_barrier_sync的异同点如下。

相同点:
  • 都会等待在它前面插入队列的任务(1、2)先执行完
  • 都会等待他们自己的任务(barrier)执行完再执行后面的任务(3、4)
不同点:
  • dispatch_barrier_sync需要等待自己的任务(barrier)结束之后才会继续程序,然后插入被写在它后面的任务(3、4),然后执行后面的任务
  • dispatch_barrier_async将自己的任务(barrier)插入到queue之后,不会等待自己的任务结束,它会继续把后面的任务(3、4)插入到queue。dispatch_barrier_async的不等待(异步)特性体现在将任务插入队列的过程,它的等待特性体现在任务真正执行的过程。

关于barrier这块的使用我在实际写示例时遇到一个问题,当将dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);这句换成系统的全局队列时,输出结果不尽人意,目前我也没有弄懂为什么,在此处暂时留一个疑问。

看叶孤城的iOS进阶指南时,他给出了这样的解释:

在global queue中使用barrier没有意义,为什么?因为barrier实现的基本条件是,要写在同一队列中。举个例子,你现在创建了两个并行队列,你在其中一个队列中插入了一个barrier任务,那么你不可能期待他在第二个队列里生效,对吧,同样的,每一次使用global queue,系统分配给你的可能是不同的并行队列,你在其中插入一个barrier任务,又有什么意义呢?

5.7 dispatch_semaphore信号量

信号量的用法相当简单,一共有是三个方法。

dispatch_semaphore_create    //创建一个信号量
dispatch_semphore_signal      //发送一个信号
dispatch_semaphore_wait       //等待信号

dispatch_semaphore的使用场景是处理并发控制,类似于NSOperationQueue的maxConcurrentOperationCount属性,意思就是设定NSOperationQueue里的NSOperation同时运行的最大数量。
信号量同样可以实现这样的功能,

  1. 首先创建一个信号量:
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    创建方法里会传入一个long型参数,这个东西你可以想象是一个库存,有了库存才可以出货。
  2. dispatch_semaphore_wait,每运行一次,就会清一个库存,如果库存为0,则根据传入的等待时间,决定等待增加库存的时间,如果设置为DISPATCH_TIME_FOREVER,那么意思就是永远等待增加库存,否则就永远不会往下面走
  3. dispatch_semaphore_signal,每运行一次,增加一个库存
    现在我们尝试用dispatch_semaphore来实现并发的数量控制:
@implementation CustomOperationQueue
- (id)initWithConcurrentCount:(int)count {
     self = [super init];
     if(self) {
          if(count < 1) count = 5;
          semaphore = disptach_semaphore_create(count);
          queue = diapatch_queue_create("com.www",DISPATCH_QUEUE_CONCURRENT);
     }
     return self;
}

- (id)init {
     return [self initWithConcurrentCount:5];
}

- (void)addTask:(CallBackBlock)block {
     dispatch_async(queue,^{
           dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
           dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),0,^{
                 block();
                 dispatch_semaphore_signal(semaphore);
            });
     });
}

来解读这段代码:

  1. 创建了一个初始库存是5的信号量
  2. 在addTask方法里,由于初始库存是5,所以第一次添加了一个任务之后,dispatch_semaphore_wait直接放行,并减少一个库存。当完成一个任务之后,还回去一个库存,调用dispatch_semaphore_signal增加一个库存
  3. 那么,当我们每个任务耗时都特别长时,一直消耗库存却没有增加库存,那么添加到第6个任务时,库存为0,那么wait会一直等待不执行第六个任务,直到库存再次大于0时才会执行

5.7 dispatch_apply快速迭代

dispatch_apply类似一个for循环,会在指定的dispatch queue中运行block任务n次,如果队列是并发队列,则会并发执行block任务,dispatch_apply是一个同步调用,block任务执行n次后才返回。
在某些场景下,使用dispatch_apply会有很大的性能提升。例如你的代码需要以每个像素为基准来处理计算image图片。同时dispatch apply能够避免一些线程爆炸的情况发生(创建很多线程),具体看下例:

//危险,可能导致线程爆炸以及死锁
for (int i = 0; i < 999; i++){
   dispatch_async(q, ^{...});
}
dispatch_barrier_sync(q, ^{});

// 较优选择, GCD 会管理并发
dispatch_apply(999, q, ^(size_t i){...});

但需要注意的是dispatch_apply是一个同步调用,会阻塞当前线程,我们编码时应实时注意不要阻塞主线程。具体dispatch_apply的使用我也写了一个简单的示例如下:

dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        dispatch_apply(1000, queue,  ^(size_t index){
            NSLog(@"====%zu",index);
        });
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"主线程更新");
        });
    });
    NSLog(@"test");

以上结果会先输出test,然后循环1000次输出"主线程更新"。某些情况下使用dispatch_apply的效果与使用dispatch_group一样。

5.8 dispatch_once来执行只需运行一次的线程安全代码

单例模式的实现方法可以用如下同步块的方法实现:

+ (id)shareInstance {
    static TestClass *shareInstance = nil;
    @synchronized (self) {
        if (!shareInstance) {
            shareInstance = [[TestClass alloc] init];
        }
    }
    return shareInstance;
}

但GCD出现后,提供了一种更为简单的方式:

+ (id)shareInstance {
    static TestClass *shareInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        shareInstance = [[TestClass alloc] init];
    });
   return shareInstance;  
}

相对于同步块,dispatch可以简化代码且绝对的保证线程安全,根本无需使用重量级的加锁、取锁机制。需要注意的是只需执行一次的块来说,每次调用函数传入的标记必须是完全相同的,因此开发会将标记变量声明在static活global作用域里。

5.9 不要使用dispatch_get_current_queue

此函数已经废弃,只应做调试之用。

6 参考资料

终于写到尾声啦,整个过程参考了非常多的文章资料,以及代码示例均经过实际验证。
好文推荐:iOS GCD 死锁理解
主要参考:iOS多线程--彻底学会多线程之『GCD』

你可能感兴趣的:(iOS多线程(二)--GCD详解)