012-GCD多线程技术
多线程
线程是进程内部执行任务的一种途径,多线程技术能适当提高程序执行效率和资源利用率,iOS 中的多线程技术主要有以下几种
- GCD
- NSOperation & NSOperationQueue
- NSThread
- Pthreads
多线程的创建是需要资源开销的,同时维护和调度线程也需要开销,程序设计和线程间通信也会因线程数目增多而变得复杂。
iOS 的主线程是在一个应用启动后默认开启的,主要负责显示、刷新和处理 UI,因此不能把比较耗时的任务放在主线程完成,会带来 UI 卡顿问题。
同时多线程也会带来线程安全的问题,当多个线程同时访问同一个对象或是存储空间时,由于读写操作的非原子性或是线程间的协同不够,就会带来严重的数据丢失或错乱问题,因此需要对资源进行同步加锁操作。
@synchronized(锁对象) { // 需要锁定的代码 };
当然在定义属性的时候也可以通过设置 atomic 特性来为属性的读写方法加锁。
GCD
GCD 是 iOS 用来管理多线程的技术,它会自动利用更多 CPU 内核,自动管理线程生命周期,使得使用线程变得更加轻便简洁。
任务和队列
GCD 中加入了两个重要的概念,任务和队列。
- 任务
在 GCD 中任务表现为一个个 block,包含需要执行的代码,任务的执行分两种,同步执行和异步执行,同步执行会阻塞当前线程,异步执行会创建新线程,不会阻塞当前线程。
dispatch_async 方法会异步执行任务,这意味着它不会阻塞当前线程
-(void)func{
dispatch_async(qQueue, ^{
NSLog(@"1");
});
NSLog(@"2");
}
这里 1 和 2 的打印顺序不固定,因为 async 执行任务不会阻塞,所以当前线程会继续向下执行。
dispatch_async 方法会同步执行任务,意味着当前线程一直阻塞到它完成任务才会继续执行
-(void)func{
dispatch_sync(qQueue, ^{
NSLog(@"1");
});
NSLog(@"2");
}
这里打印顺序一定是先 1 后 2 的。
- 队列
队列用于存放任务,然后由 Run Loop 从中取出任务分发给各个线程。队列也分为串行队列和并行队列。串行队列会按照 FIFO 顺序被执行,并行队列则会并行执行,并行队列只能在异步函数中起作用。
创建队列
- 主队列
主队列是一个特殊的串行队列,放在主队列的任务都会放到主线程执行
dispatch_queue_t queue = dispatch_get_main_queue();
- 全局并行队列
全局并行队列是系统提供的并行队列,无需手动创建。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
这里第二个 flag 参数是保留位,使用时要置0,第一个参数表示线程优先级,也就是说有4个可选的全局并行队列,优先级分别是
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
- 串行队列
// 创建串行队列(队列类型传递NULL或者DISPATCH_QUEUE_SERIAL)
dispatch_queue_t queue = dispatch_queue_create("serial_queue", NULL);
- 并行队列
dispatch_queue_t queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
自定义队列的优先级有两种定义方法
-
dispatch_queue_attr_make_with_qos_class 方法
dispatch_queue_attr_t queue_attr = dispatch_queue_attr_make_with_qos_class (DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY,-1); dispatch_queue_t queue = dispatch_queue_create("queue", queue_attr);
-
dispatch_set_target_queue
dispatch_queue_t queue = dispatch_queue_create("myqueue", NULL); dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_set_target_queue(queue, globalQueue); dispatch_async(queue, ^{ NSLog(@"async"); dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"main"); }); });
这个方法还可以设置队列层次结构,当我们想让不同队列的任务同步执行时,可以创建一个串行队列,将其他队列的 target 设置为这个队列,就可以实现了。
dispatch_queue_t targetQueue = dispatch_queue_create("myqueue", NULL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_set_target_queue(targetQueue, globalQueue);
dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
dispatch_async(queue1, ^{
NSLog(@"queue1 1");
});
dispatch_async(targetQueue, ^{
NSLog(@"async");
});
dispatch_async(queue2, ^{
NSLog(@"queue2 1");
});
dispatch_async(queue2, ^{
NSLog(@"queue2 2");
});
dispatch_async(queue2, ^{
NSLog(@"queue2 3");
});
dispatch_async(queue1, ^{
NSLog(@"queue1 2");
});
dispatch_async(queue1, ^{
sleep(1);
NSLog(@"queue1 3");
});
这样执行的结果如下
queue1 1
queue1 2
queue1 3
async
queue2 1
queue2 2
queue2 3
简单来说,设置了 target 以后,并不是按照设置的队列顺序来执行任务的,而是按照分发任务的队列顺序来执行,如果先设置了 queue1 的任务,就会将 queue1 的任务执行完再执行其他队列。
分发任务
dispatch_async
前面提到过,这个方法是异步执行代码块中任务。
dispatch_sync
这个方法会同步执行任务,执行顺序可以预测,但是要注意使用不当可能会造成死锁。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"123");
});
NSLog(@"456");
在这里,dispatch_sync 由于是在主线程运行,因此会阻塞主线程,一直等到 block 中的代码执行返回后才会继续执行,但是 block 又是在主线程执行的,因为主线程被阻塞所以不会执行 block,于是两者相互等待,就发生了死锁。
once
dispatch_once 保证 block 只会被执行一次,一般用于单例模式中初始化 static 的单例对象。
static dispatch_once_t onceToken;
NSLog(@"%ld", onceToken);
dispatch_once(&onceToken, ^{
});
NSLog(@"%ld", onceToken);
打印结果可以看到 onceToken 一开始是 0,执行完以后会变成 -1,从而标识这个 block 已被执行过。
apply
dispatch_apply 这个方法会循环执行任务,指定循环次数就会在 queue 中将 block 循环执行指定次数。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(5, queue, ^(size_t i) {
if (i == 3)
{
sleep(1);
}
NSLog(@"%zu", i);
});
当然至于是串行执行还是并行执行则要看队列的属性。
group
group 是一组执行的任务,适用于需要等待一组任务完成触发其他代码的场景。
-
创建一个 group 对象
dispatch_group_t group = dispatch_group_create();
-
插入到队列中
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ });
-
同步等待所有任务完成后继续执行后面的代码
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
这里 timeout 可以设置为 FOREVER,也可以设置为 dispatch_time_t 类型的参数
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)1 * NSEC_PER_SEC); dispatch_group_wait(group, time);
-
异步等待所有任务完成后执行
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"finish"); });
-
enter 和 leave
也可以使用 dispatch_group_enter 和 dispatch_group_leave 方法实现将任务加入到 group 中的操作
dispatch_group_t group = dispatch_group_create(); dispatch_group_enter(group); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSLog(@"group in"); sleep(1); dispatch_group_leave(group); }); NSLog(@"main");
这里效果与 dispatch_group_wait 相同。
after
dispatch_after 会在指定时间后将任务加入到指定队列中,当然不代表会立刻执行此任务。
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t) 3 * NSEC_PER_SEC);
dispatch_after(time, queue, ^{
NSLog(@"after 3 second");
});
barrier
dispatch_barrier_async 用于等待前面的任务执行完毕后自己才执行,而它后面的任务需等待它完成之后才执行。还有个串行函数 dispatch_barrier_sync 是会阻塞当前线程等待指定队列完成任务再继续执行的。
dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", NULL);
dispatch_async(queue1, ^{
NSLog(@"1 1");
});
dispatch_async(queue1, ^{
sleep(1);
NSLog(@"1 2");
});
dispatch_barrier_sync(queue1, ^{
NSLog(@"1 3");
});
dispatch_async(queue2, ^{
NSLog(@"2 1");
});
打印结果是
1 1
1 2
1 3
2 1
如果是执行的 dispatch_barrier_async 则 "2 1" 不会最后才打印。
block
可以看到插入到队列的任务一般就是 block 代码块中的代码,也可以自己定义一个 block 进行复用。
dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", NULL);
dispatch_block_t block1 = dispatch_block_create(0, ^{
NSLog(@"block1");
});
dispatch_block_t block2 = dispatch_block_create(0, ^{
NSLog(@"block2");
});
dispatch_async(queue1, block1);
dispatch_async(queue2, block2);
-
dispatch_block_cancel
这个函数可以取消 block 的执行,例如
dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL); dispatch_queue_t queue2 = dispatch_queue_create("queue2", NULL); dispatch_block_t block1 = dispatch_block_create(0, ^{ NSLog(@"block1"); }); dispatch_block_t block2 = dispatch_block_create(0, ^{ NSLog(@"block2"); }); dispatch_async(queue1, block1); dispatch_async(queue2, block2); dispatch_block_cancel(block1);
这样将只会打印 "block2"。
-
dispatch_block_wait
这个函数会阻塞当前线程,并等待前面的任务执行完毕。
dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL); dispatch_block_t block1 = dispatch_block_create(0, ^{ sleep(1); NSLog(@"block1"); }); dispatch_async(queue1, block1); dispatch_block_wait(block1, DISPATCH_TIME_FOREVER); NSLog(@"continue");
最终打印结果是
block1 continue
-
dispatch_block_notify
dispatch_block_notify 不会阻塞当前线程,会在指定的 block 执行结束后将指定 block 插入到指定的 queue 中。
dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL); dispatch_queue_t queue2 = dispatch_queue_create("queue2", NULL); dispatch_block_t block1 = dispatch_block_create(0, ^{ sleep(1); NSLog(@"block1"); }); dispatch_block_t block2 = dispatch_block_create(0, ^{ NSLog(@"block2"); }); dispatch_async(queue1, block1); dispatch_block_notify(block1, queue2, block2); NSLog(@"continue");
打印结果为
continue block1 block2