多线程(二):GCD

iOS多线程之GCD

Posted by Ted on May 23, 2016

1、添加任务到队列(同步、异步):

dispatch_sync:

同步添加,将指定的任务block同步追加到queue中,在追加的block结束之前,dispatch_sync会一直等待;

如果在主线程上执行同步任务(任务也在主线程执行),会造成死锁:

//会造成死锁
dispatch_sync(dispatch_get_main_queue(), ^{
	//
});

但是在异步任务里可以用来切换到主线程

dispatch_async(dispatch_get_global_queue(0,0), ^{
	//耗时操作;
	dispatch_sync(dispatch_get_main_queue(), ^{
		//回到主线程;
	});
});

可以用来阻塞线程:

dispatch_sync(dispatch_get_global_queue(0,0), ^{
  sleep(2);
  NSLog(@"2秒后执行,但是会阻塞线程");
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
   NSLog(@"2秒后执行,但是不阻塞线程");
});

dispatch_async:

异步添加,将指定的Block”非同步”地追加到指定的Queue中,该线程不做等待,继续往下执行;

2、队列

Dispatch Queue:执行处理任务的队列,通过dispatch_async等函数API,在Block语法中记述想要执行的处理任务并将其追加到Dispatch Queue中,Dispatch Queue按照追加的顺序(先进先出FIFO)执行处理。

Serial Dispatch Queue(串行队列):

等待现在执行中处理结束,一旦生成Serial Dispatch Queue并追加处理。系统对于一个Serial Dispatch Queue就只生成并使用一个线程,但是如果生成过多的线程,会导致消耗大量的内存,引起大量的上下文切换,大幅度降低系统的响应性能,因此只在为了避免多个线程更新相同的资源导致数据竞争时使用。

Concurrent Dispatch Queue(并行队列):

不等待现在执行中处理结束,可以并行执行多个处理,并行处理的处理数量取决于当前系统状态,生成所需的线程执行处理,当处理结束,应当执行的处理数减少时,XNU内核会结束不再需要的线程,因此当想并行执行不发生数据竞争等问题处理时使用并行队列,有效管理线程,不会出现太多线程。

系统默认有一个串行队列:主队列(main_queue)和并行队列:全局队列(global_queue),不需要自己手动释放,或者自己创建用户队列(需要手动释放)。

主队列(main):

dispatch_queue_t mainQ = dispatch_get_main_queue() 注意:不能sync向主队列提交任务,因为会造成死锁,只能async提交任务

全局队列(global_queue):并发队列

有执行优先级

dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

DISPATCH_QUEUE_PRIORITY_HIGH:             //优先级最高,在default,和low之前执行
DISPATCH_QUEUE_PRIORITY_DEFAULT           //默认优先级,在low之前,在high之后
DISPATCH_QUEUE_PRIORITY_LOW               //在high和default后执行
DISPATCH_QUEUE_PRIORITY_BACKGROUND        //提交到这个队列的任务会在high优先级的任务和已经提交到background队列的执行完后执行。

获取队列:(create_queue):

如果是串行队列:dispatch_queue_create(“createName”, DISPATCH_QUEUE_SERIAL)

如果是并行队列:推荐使用dispatch_get_global_queue

如果需要用到barrier和group,才来dispatch_queue_create(“createName”,DISPATCH_QUEUE_CONCURRENT)

尽管是ARC,使用结束后也要dispatch_release释放

dispatch_queue_t  concurrentQ = dispatch_queue_create("createName",DISPATCH_QUEUE_CONCURRENT)
dispatch_queue_t  serialQ = dispatch_queue_create("createName", DISPATCH_QUEUE_SERIAL)
改变队列优先级
dispatch_set_target_queue(restQueue, targetQueue);

暂停恢复队列

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_suspend(queue); //暂停队列queue
dispatch_resume(queue);  //恢复队列queue

3、延迟添加执行dispatch_after

并不是在多少秒后执行,而是在3秒后将任务添加到Dispatch Queue中

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3*NSEC_PER_SEC)),dispatch_get_main_queue(), ^{
	//秒
});

dispatch_after(dispatch_time(DISPATCH_TIME_NOW,150ull *NSEC_PER_MSEC),dispatch_get_main_queue(), ^{
    //毫秒
});

4、dispatch_group与dispatch_barrier

dispatch_group应用于线程依赖:当添加到Queue中的所有任务都完成了,再来执行某个任务。

举例,异步下载两张图片,这在这两张图片下载完毕之后,将两张图片合成

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.gcd-group.www", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
    #异步下载第一张图片
 });
 
dispatch_group_async(group, queue, ^{
    #异步下载第二张图片
});
     
dispatch_group_notify(group, queue, ^{
    #两张图片下载完后进行合成
});

dispatch_barrier:barrier以为栅栏,阻碍。在串行队列中,无区别,但是在并行队列中,用这个函数添加的任务可以保证阻碍线程执行,即这个任务是串行执行的。可用于解决数据竞争问题。有个坑要注意,并行队列必必须是自己dispatch_queue_create创建的,不能用dispatch_get_global_queue

实现原理:queue中的其他block已经开始在各自分配的线程执行,当从queue检出一个barrier时,是等待其他的block都执行完毕再执行barrier,此时不会并发执行其他的block,直到该barrier执行完毕。 这相当于给当前已经运行的bock们加了一个group和notify,在notify里面执行了barrier,然后再执行barrier下面的block

举例。多个操作对数据库进行读写,读的操作可以异步进行,但是为了保证写的线程安全,则必须用dispatch_barrier

dispatch_async(queue, ^{NSLog(@"read");});
dispatch_async(queue, ^{NSLog(@"read");});
dispatch_async(queue, ^{NSLog(@"read");});

#可以保证写的时候只有这一个操作
dispatch_barrier_sync(queue, ^{NSLog(@"writing");});

dispatch_async(queue, ^{NSLog(@"read");});
dispatch_async(queue, ^{NSLog(@"read");});

5、dispatch_semaphore

semaphore是信号的意思,dispatch_semaphore有三个函数,分别是dispatch_semaphore_create,dispatch_semaphore_signal,dispatch_semaphore_wait

dispatch_semaphore信号量控制

6、dispatch_apply

重复执行某段代码

在某些场景下使用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){...});

7、dispatch_block_cancel

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);