线程和队列的关系
队列就跟我们排队买票一样,这是方便管理人员。在程序中也是方便管理线程。
正在排队的人就是等待要做的事情,服务窗口就是线程。
所以队列就是一个管理器,它管理同时进行的线程数,以免线程过多,导致效率低下,甚至系统崩溃。
它的主要用处就是跟售票窗口一样,当一个人办完手续后,这个人就走了,就意味着有一个窗口处于空闲状态,所以这个时候后面排队的可以去一个人继续办理。
那么多线程也一样,当然不需要每个窗口都排队,都排在一个列表里面,当某个线程完成时,必须想办法通知队列,说我已经完成了。这个时候队列就知道已经少了一个线程在做事了,就会开一个新的线程去做下一件事情,这样让同时进行的线程数总是维持在一个特定的数内。
队列创建
可以使用 dispatch_queue_create 方法来创建队列。该方法需要传入两个参数:
- 第一个参数表示队列的唯一标识符,用于 DEBUG,可为空。队列的名称推荐使用应用程序 ID 这种逆序全程域名。
- 第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。
// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.testQueue", DISPATCH_QUEUE_CONCURRENT);
队列获取
对于串行队列,GCD 提供了的一种特殊的串行队列:主队列(Main Dispatch Queue)
所有放在主队列中的任务,都会放到主线程中执行
可使用 dispatch_get_main_queue() 方法获得主队列
// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();
对于并行队列,Global Dispatch Queue 是所有应用程序都能够使用的Concurrent Dispatch Queue。没有必要通过dispatch_queue_create函数逐个生成Concurrent Dispatch Queue。只要获取 Global Dispatch Queue使用即可。
另外,Global Dispatch Queue有4个优先级,分别是高优先级,低优先级,默认优先级(一般为默认优先级)和后台优先级
// 全局并发队列(默认优先级)的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
对于Main Dispatch Queue和Global Dispatch Queue执行dispatch_retain函数和dispatch_release函数不会引起任何变化,也不会有任何问题。这也是获取并使用Global Dispatch Queue比生成,使用,释放Concurrent Dispatch Queue更轻松的原因。
源代码上在进行类似通过 dispatch_queue_create 函数生成Dispatch Queue的处理要更轻松时,可参照引用计数式内存管理的思考方式,直接在Main Dispatch Queue和Global Dispatch Queue中执行了 dispatch_retain函数和dispatch_release函数
标记符
dispatch_queue_set_specific(queue1, queueKey1, &queueKey1,NULL);
就是向queue1队列中设置一个queueKey1标识。
dispatch_get_specific就是在当前队列中取出标识,注意iOS中线程和队列的关系,所有的动作都是在队列中执行的
任务创建(dispatch_sync和dispatch_async)
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});
dispatch_async函数的“async”就是“非同步”,就是将指定的Block“非同步”地追加到指定的Dispatch Queue中,dispatch_async函数不做任何等待,一般会开启新线程
既然有“async”,那必定有“sync”,即dispatch_sync函数。它意味着“同步”,也就是将指定的Block“同步”追加到指定的Dispatch Queue中。在追加Block结束之前,dispatch_sync函数会一直等待,而等待就意味着当前线程停止
我们假设一种情况,执行Main Dispatch Queue时,使用另外的线程Global Dispatch Queue进行处理,处理结束后立即使用所得到的结果。在这种情况下就要使用dispatch_sync函数
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{
// do somethings
});
一旦调用dispatch_sync函数,那么在指定的处理执行结束之前,该函数不会返回。dispatch_sync函数可简化源代码,也可说是简易版的dispatch_group_wait函数
正因为dispatch_sync函数使用简单,所以也容易引起问题,即死锁
比如下段代码就会引起死锁:
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{
NSLog(@"main");
});
同理,下段代码也会造成死锁
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
dispatch_sync(mainQueue, ^{
NSLog(@"main");
});
});
他们的原因都是在主线程中执行指定的Block,并等待其执行结束,但其实这些源代码也是在主线程上执行的,就会造成互相等待
大概就是这么个意思:
当然,在Serial Dispatch Queue中也是一样的
dispatch_queue_t queue = dispatch_queue_create("example.SerialDispatchQueue", NULL);
dispatch_async(queue, ^{
dispatch_sync(queue, ^{
NSLog(@"queue");
});
});
线程和runloop
主线程默认开启runloop,而子线程默认不开启,所以有关runloop的操作,在子线程中不会执行,若想在子线程中执行,则需在子线程中手动创建和开启线程。
比如下段代码没有创建和开启runloop
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
Person *person = [[Person alloc] init];
[person performSelector:@selector(say) withObject:nil afterDelay:0];
});
调用的方法在指定的时间后执行,没有runloop时,无法处理NSTimer事件,所以无法执行。
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
// 加入port就会自动创建runloop
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
Person *person = [[Person alloc] init];
[person performSelector:@selector(say) withObject:nil afterDelay:0];
// 执行runloop
[[NSRunLoop currentRunLoop] run];
});
队列使用时可能用到的其他API
dispatch_set_target_queue变更队列优先级
dispatch_set_target_queue 函数有两个作用:第一,变更队列的执行优先级;第二,目标队列可以成为原队列的执行阶层。
//首先创建5个串行队列
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue1", NULL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue2", NULL);
dispatch_queue_t serialQueue3 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue3", NULL);
dispatch_queue_t serialQueue4 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue4", NULL);
dispatch_queue_t serialQueue5 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue5", NULL);
//每个队列上输出一个数字
dispatch_async(serialQueue1, ^{
NSLog(@"1");
});
dispatch_async(serialQueue2, ^{
NSLog(@"2");
});
dispatch_async(serialQueue3, ^{
NSLog(@"3");
});
dispatch_async(serialQueue4, ^{
NSLog(@"4");
});
dispatch_async(serialQueue5, ^{
NSLog(@"5");
});
创建五个串行队列,从一到五,每个队列的任务是打印队列数字,打印结果乱序。
//首先创建5个串行队列
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue1", NULL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue2", NULL);
dispatch_queue_t serialQueue3 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue3", NULL);
dispatch_queue_t serialQueue4 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue4", NULL);
dispatch_queue_t serialQueue5 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue5", NULL);
//创建目标串行队列
dispatch_queue_t targetSerialQueue = dispatch_queue_create("com.gcd.setTargetQueue2.targetSerialQueue", NULL);
//设置执行阶层
dispatch_set_target_queue(serialQueue1, targetSerialQueue);
dispatch_set_target_queue(serialQueue2, targetSerialQueue);
dispatch_set_target_queue(serialQueue3, targetSerialQueue);
dispatch_set_target_queue(serialQueue4, targetSerialQueue);
dispatch_set_target_queue(serialQueue5, targetSerialQueue);
//执行操作
dispatch_async(serialQueue1, ^{
NSLog(@"1");
});
dispatch_async(serialQueue2, ^{
NSLog(@"2");
});
dispatch_async(serialQueue3, ^{
NSLog(@"3");
});
dispatch_async(serialQueue4, ^{
NSLog(@"4");
});
dispatch_async(serialQueue5, ^{
NSLog(@"5");
});
给他们指定执行阶层,则他们的优先级被安排在串行目标队列targetSerialQueue上,所以可以有序执行。
在必须将不可并行执行的处理追加到多个SerialDispatchQueue中时,如果使用dispatch_target_queue函数将目标指定为某一个SerialDispatchQueue,即可防止处理并行执行。
注意: 这里的目标队列需要是串行队列,否则还是不能控制其执行顺序
dispatch_barrier_async 栅栏方法
我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async 方法在两个操作组间形成栅栏。
dispatch_barrier_async 方法会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在 dispatch_barrier_async 方法追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。具体如下所示:
- (void)barrier {
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_barrier_async(queue, ^{
// 追加任务 barrier
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 4
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
});
}
根据时间可以看出,使用 dispatch_barrier_async方法这一句是单独执行的
dispatch_after 延时执行方法
我们经常会遇到这样的需求:在指定时间(例如 3 秒)之后执行某个任务。可以用 GCD 的dispatch_after 方法来实现。
需要注意的是:dispatch_after 方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after 方法是很有效的。
- (void)after {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"asyncMain---begin");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0 秒后异步追加任务代码到主队列,并开始执行
NSLog(@"after---%@",[NSThread currentThread]); // 打印当前线程
});
}
dispatch_once 一次性代码(只执行一次)
我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 方法。使用 dispatch_once 方法能保证某段代码在程序运行过程中只被执行 1 次,并且即使在多线程的环境下,dispatch_once 也可以保证线程安全
/**
* 一次性代码(只执行一次)dispatch_once
*/
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行 1 次的代码(这里面默认是线程安全的)
});
}
dispatch_apply 快速迭代方法
通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的方法 dispatch_apply。dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
如果是在串行队列中使用 dispatch_apply,那么就和 for 循环一样,按顺序同步执行。但是这样就体现不出快速迭代的意义了。
我们可以利用并发队列进行异步执行。比如说遍历 0~5 这 6 个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply 可以 在多个线程中同时(异步)遍历多个数字。
还有一点,无论是在串行队列,还是并发队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait方法
/**
* 快速迭代方法 dispatch_apply
*/
- (void)apply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"apply---begin");
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");
}
和一般的并发队列加异步执行不同,会等待任务结束,因此apply—end总是最后打印
dispatch_group 队列组
有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。
- 调用队列组的 dispatch_group_async 先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的 dispatch_group_enter、dispatch_group_leave 组合来实现 dispatch_group_async。
- 调用队列组的 dispatch_group_notify 回到指定线程执行任务。或者使用 dispatch_group_wait 回到当前线程继续向下执行(会阻塞当前线程)
dispatch_group_notify 监听
监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。
/**
* 队列组 dispatch_group_notify
*/
- (void)groupNotify {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 2
[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---end");
});
}
从 dispatch_group_notify 相关代码运行输出结果可以看出:
当所有任务都执行完成之后,才执行 dispatch_group_notify 相关 block 中的任务
dispatch_group_wait 等待
- (void)groupWait {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
// 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group---end");
}
从 dispatch_group_wait 相关代码运行输出结果可以看出:
当所有任务执行完成之后,才执行 dispatch_group_wait 之后的操作。但是,使用dispatch_group_wait 会阻塞当前线程
dispatch_group_enter、dispatch_group_leave
- dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1
- dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。
- 当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait 解除阻塞,以及执行追加到 dispatch_group_notify 中的任务
- (void)groupEnterAndLeave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程.
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---end");
});
}
如果我把dispatch_group_leave(group);去掉,则group_end不会打印下面的任务也不会执行,线程被阻塞
dispatch_semaphore 信号量
GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。类似于过高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在 Dispatch Semaphore 中,使用计数来完成这个功能,计数为0时等待,计数为1或者大于1时,减去1而不等待
Dispatch Semaphore 提供了三个方法:
- dispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量
- dispatch_semaphore_signal:发送一个信号,让信号总量加 1
- dispatch_semaphore_wait:可以使总信号量减 1,信号总量等于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
注意: 信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量
Dispatch Semaphore 在实际开发中主要用于:
- 保持线程同步,将异步执行任务转换为同步执行任务
- 保证线程安全,为线程加锁
我们在开发中,会遇到这样的需求:异步执行耗时任务,并使用异步执行的结果进行一些额外的操作。换句话说,相当于,将异步执行任务转换为同步执行任务。比如说:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通过引入信号量的方式,等待异步执行任务结果,获取到 tasks,然后再返回该 tasks
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
用Dispatch Semaphore实现买票机制
@implementation ViewController
static dispatch_semaphore_t semaphoreLock;
- (void)viewDidLoad {
[super viewDidLoad];
semaphoreLock = dispatch_semaphore_create(1);
[self initTicket];
}
- (void)initTicket {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"semaphore---begin");
self.ticketSurplusCount = 50;
// queue1 代表北京火车票售卖窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 代表上海火车票售卖窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketSafe];
});
}
- (void)saleTicketSafe {
while (1) {
// 相当于加锁
//因为semaphoreLock, 计数为0时等待,计数为1或大于1时,减去1而不等待
dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
if (self.ticketSurplusCount > 0) { // 如果还有票,继续售卖
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
} else { // 如果已卖完,关闭售票窗口
NSLog(@"所有火车票均已售完");
// 相当于解锁
dispatch_semaphore_signal(semaphoreLock);
break;
}
// 相当于解锁
dispatch_semaphore_signal(semaphoreLock);
}
}