最近遇到这一块的多线程开发,索性直接学习一下了,我们来介绍一下GCD的详细使用
GCD :全称是 Grand Central Dispatch
我们也称之为dispatch 闭包(Closure)函数
GCD队列包含两种:串行队列和并行队列
使用GCD创建队列的两种形式
- GCD串行队列创建方式
下面这种是创建一个串行GCD队列,
dispatch_queue_t 创建一个队列
dispatch_queue_creat 后面会带有两个参数 也就是队列标示符,
尽量设置成唯一的,使用我们的额工程设置,防止和其他队列冲突
DISPATCH_QUEUE_SERIAL 表示我们要创建一个串行GCD队列
串行队列:FIFO(先进先出原则)也就是说队列中的block函数是按照先进先出的顺序去执行,也就是单线程执行
一般我们在做下载任务时会用到串行队列下载,提高执行效率
dispatch_queue_t queue =
dispatch_queue_creat("com.example.serial",DISPATCH_QUEUE_SERIAL);
- GCD并发队列创建方式
并发执行 ,block 被分发到多个线程中去执行
并发执行创建方式
dispatch_queue_t queue =
dispatch_queue_create("com.dispatch.concurrent",DISPATCH_QUEUE_CONCURRENT);
- 获取全局的并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
获取程序进程缺省产生的并发队列,可根据优先级来选择高、中、低三个优先级,
由于这个是全局有系统控制的队列,所以我们无法对其进行 dispatch_resume() 继续 和 dispatch_suspend() 中断
学习注意点:
一个APP中可能有多个队列,但是每个队列并不一定只有一个线程,这个是我们的注意点,可能每个队列有多个线程,
而并发队列会根据当前手机的CPU利用情况产生合理的线程数,
如果觉得复杂,可以把队列理解成一个简单的线程池管理对象就好,明白线程需要有一个人去管理
- 最后一种获取主线程队列
dispatch_queue_t queue = dispatch_get_main_queue();
获取主线程的dispatch队列,实际上是一个串行队列,同样苹果是不允许我们来操作这个队列的,这个队列的执行,是有系统给予的,不允许我们打断或者继续。
怎么创建一个block块呢
那么我们这个block块所在的队列是串行还是并行呢,就要看我们加载那个队列里面了
异步block任务:
dispatch_asyn(dispatch_get_main_queue(),^{
wirte code : here
// block 内部我们需要实现的功能
这个是一个异步执行block,我们添加在主队列里面了,也就是说我们这个是串行任务
});dispatch_asyn(queue,^{
这个block任务我们也是添加到一个队列中,这个queue, 如果是串行队列,那就是串行任务,反之,就是并发任务线程,同样,这个也是异步的
});
同步block 任务块
- dispatch_syn(dispatch_get_main_queue(),^{
在主队列中进行同步执行block内部的代码,这个是阻塞线程的,如果我们要请求一个图片,用这个来发送请求,这个任务是跑在主线程的,并且是串行执行的,会阻塞当前线程,直到这个任务结束,用户才可以操作,一般我们是不会这么做的,这样做的后果就是如果数据很大,就会产生思死锁
});
下面我们使用GCD模仿一个网络请求模型
dispatch_asyn(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
// 子线程中开始网络请求数据
//更新数据Model
回到主线程刷新UI界面
dispatch_asyn(dispatch_get_main_queue(),^{
在主线程中更新UI代码写在这里
});
});
线程安全
- 写数据
- 共享数据
- 读数据
我们怎么才能保证我们在操作数据库的时候保证每次只有一个线程进行写数据,保证数据的完整性呢,我们可以用串行队列来操作,或者使用lock (锁)来进行操作
写数据串行队列保护如下写法
// 创建一个串行队列
dispatch_queue_t queue = dispatch_queue_create("com.dispatch_rw",DISPATCH_QUEUE_SERIAL);
//写数据到数据库
- (BOOL)writeDataToDB:(NSString *)string{
//异步串行线程,保证了数据的读写完整性,每次写数据,都要等待上一个线程写入完毕后才开始下一个线程
dispatch_async(queue,^{
// write database;
coding .....
});
}
GCD中还有一些其他常用的函数,如下:
同步函数
void dispatch_apply(size_t iterations,dispatch_queue_t queue,void (^block)(size_t));
这个函数会重复执行block内的内容,同时这个方法是同步返回,也就是说要等到所有的block执行完毕才返回,
如果需要异步返回,嵌套在dispatch_async中来使用,同时多个任务的运行是否支持并发还是串行依赖于所在的队列
异步写锁函数,
dispatch_barrier_async(dispatch_queue_t queue,dispatch_block_t block);
这个函数可以设置同步执行的block,他会等到他加入队列之前的block执行完毕后,才开始执行。
在它之后的加入的block,则等待这个block执行完毕后才开始执行。
dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
这个是同步返回函数,其他使用和上面一样
//延迟函数
dispatch_after(dispatch_time_t when,dispatch_queue_t queue,dispatch_block_t block);
延迟执行block
// 调换队列函数
diapatch_set_target_queue(dispatch_object_t object,dispatch_queue_t queue);
这个函数会把需要执行的任务对象指定到不同的队列中去处理,这个任务对象可以是dispatch队列,也可以是dispatch源,而且这个过程可以是动态的,可以实现队列的调度管理等,
形如两个队列 A 和 B 可以把A 指派到 B
dispatch_set_target_queue(A,B);
那么A队列还未运行的block会在B队列中运行,这个时候我们暂停A队列。
dispatch_suspend(A);
这个时候只会暂停A中原来的block的执行,B的block则不受影响如果暂停B的运行,则会暂停A的运行
dispatch 队列不支持cancle (取消)
没有实现dispatch_cancle ( )函数