什么是GCD
Grand Central Dispatch (GCD)是Apple推出的一项多线程编程的方法以用来替代NSThread, NSOperationQueue, NSInvocationOperation 等技术。GCD配合block使用时同时也明显降低了多线程编程的复杂性使程序员能够轻松绕过多线程编程的一些坑。
GCD主要有队列与任务两个核心概念,使用GCD的也只有以下两个步骤:
1. 创建队列
2. 将任务提交给队列
应用举例
现在我们要做这样一个iPhone应用:用户点击一个按钮后将一个网页内容下载并显示到界面上的UITextView
中。
很简单的应用,但是我们需要将下载的过程放到其他的线程中去执行以防止阻塞UI线程(让界面看起来很卡),待数据下载完成后,我们再到UI线程中去刷新数据(Apple只允许在UI线程中操作UI界面)
下面看代码
- (IBAction)Fetch:(id)sender
{
//获取默认全局发队列并在其中执行任务
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString * urlString = @"http://www.baidu.com";
NSURL * url = [NSURL URLWithString:urlString];
NSString * pageContent = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
if(pageContent)
{
//在主线程(UI线程)中执行任务
dispatch_async(dispatch_get_main_queue(), ^{
self.textView.text = pageContent;
});
}
});
}
Fetch
方法是点击按钮时执行的方法,可以看到,很少的代码就能够将整个逻辑表达清楚并实现相应地功能
下面简单介绍下GCD中的核心概念以及使用方法
队列
GCD中的队列负责管理用户提交的任务。
GCD中的队列有以下两种
串行队列
在串行队列中,任务以串行方式执行,一次只能执行一个任务,只有执行完上一个任务才能开始执行下一个任务。
串行队列的底层只会维护一个线程池来处理用户提交的任务。
执行下面的函数
- (void)serialQueueTest
{
dispatch_queue_t serialQueueTest = dispatch_queue_create("serialQueueTest", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueueTest, ^{
for(int i = 0; i < 5; ++i)
{
NSLog(@"第一个任务在线程:%@->:%d",[NSThread currentThread],i);
}
});
dispatch_async(serialQueueTest, ^{
for(int i = 0; i < 5; ++i)
{
NSLog(@"第二个任务在线程:%@->:%d",[NSThread currentThread],i);
}
});
}
输出:
2015-09-06 20:34:47.735 GCDDemo[923:28498] 第一个任务在线程:<NSThread: 0x7fcac3566e40>{number = 2, name = (null)}->:0
2015-09-06 20:34:47.736 GCDDemo[923:28498] 第一个任务在线程:<NSThread: 0x7fcac3566e40>{number = 2, name = (null)}->:1
2015-09-06 20:34:47.736 GCDDemo[923:28498] 第一个任务在线程:<NSThread: 0x7fcac3566e40>{number = 2, name = (null)}->:2
2015-09-06 20:34:47.737 GCDDemo[923:28498] 第一个任务在线程:<NSThread: 0x7fcac3566e40>{number = 2, name = (null)}->:3
2015-09-06 20:34:47.737 GCDDemo[923:28498] 第一个任务在线程:<NSThread: 0x7fcac3566e40>{number = 2, name = (null)}->:4
2015-09-06 20:34:47.737 GCDDemo[923:28498] 第二个任务在线程:<NSThread: 0x7fcac3566e40>{number = 2, name = (null)}->:0
2015-09-06 20:34:47.737 GCDDemo[923:28498] 第二个任务在线程:<NSThread: 0x7fcac3566e40>{number = 2, name = (null)}->:1
2015-09-06 20:34:47.738 GCDDemo[923:28498] 第二个任务在线程:<NSThread: 0x7fcac3566e40>{number = 2, name = (null)}->:2
2015-09-06 20:34:47.738 GCDDemo[923:28498] 第二个任务在线程:<NSThread: 0x7fcac3566e40>{number = 2, name = (null)}->:3
2015-09-06 20:34:47.738 GCDDemo[923:28498] 第二个任务在线程:<NSThread: 0x7fcac3566e40>{number = 2, name = (null)}->:4
可以看到所有的任务都在同一个线程内执行并且执行完了第一个任务时才会去执行第二个任务
并发队列
并发队列可以同时处理多个任务,各个任务以并行方式执行。由于执行的各个任务所需的时长不一样,因此先提交的任务不一定先执行完毕。
并发队列的底层维护着多个线程池来处理用户提交的任务。
执行如下函数
- (void)concurrentQueueTest
{
dispatch_queue_t queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for(int i = 0; i < 5; ++i)
{
NSLog(@"第一个任务在线程:%@->:%d",[NSThread currentThread],i);
}
});
dispatch_async(queue, ^{
for(int i = 0; i < 5; ++i)
{
NSLog(@"第二个任务在线程:%@->:%d",[NSThread currentThread],i);
}
});
}
输出如下
2015-09-06 20:37:38.734 GCDDemo[956:29808] 第一个任务在线程:<NSThread: 0x7fd8b87466a0>{number = 3, name = (null)}->:0
2015-09-06 20:37:38.734 GCDDemo[956:29807] 第二个任务在线程:<NSThread: 0x7fd8b8508d90>{number = 2, name = (null)}->:0
2015-09-06 20:37:38.735 GCDDemo[956:29808] 第一个任务在线程:<NSThread: 0x7fd8b87466a0>{number = 3, name = (null)}->:1
2015-09-06 20:37:38.735 GCDDemo[956:29807] 第二个任务在线程:<NSThread: 0x7fd8b8508d90>{number = 2, name = (null)}->:1
2015-09-06 20:37:38.735 GCDDemo[956:29808] 第一个任务在线程:<NSThread: 0x7fd8b87466a0>{number = 3, name = (null)}->:2
2015-09-06 20:37:38.735 GCDDemo[956:29807] 第二个任务在线程:<NSThread: 0x7fd8b8508d90>{number = 2, name = (null)}->:2
2015-09-06 20:37:38.735 GCDDemo[956:29808] 第一个任务在线程:<NSThread: 0x7fd8b87466a0>{number = 3, name = (null)}->:3
2015-09-06 20:37:38.736 GCDDemo[956:29807] 第二个任务在线程:<NSThread: 0x7fd8b8508d90>{number = 2, name = (null)}->:3
2015-09-06 20:37:38.736 GCDDemo[956:29808] 第一个任务在线程:<NSThread: 0x7fd8b87466a0>{number = 3, name = (null)}->:4
2015-09-06 20:37:38.736 GCDDemo[956:29807] 第二个任务在线程:<NSThread: 0x7fd8b8508d90>{number = 2, name = (null)}->:4
可以看到两个任务位于不同的线程并且在执行第一个任务的同时也在执行第二个任务。
创建或访问队列
// 获取当前执行代码所在队列
dispatch_queue_t currentQueue = dispatch_get_current_queue();
/**
根据制定优先级、额外的旗标来获取系统的全局并发队列。第1个参数可接受
DISPATCH_QUEUE_PRIORITY_HIGH(2)、
DISPATCH_QUEUE_PRIORITY_DEFAULT(0)、
DISPATCH_QUEUE_PRIORITY_LOW(-2)、
DISPATCH_QUEUE_PRIORITY_BACKGROUND(INT16_MIN)
第二个参数目前未使用,传入0就可以
可以通过下面的代码获取系统默认的全局并发队列
**/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
//获取应用主线程所关联的串行队列,在IOS中应用主线程就是UI线程
dispatch_queue_t uiQueue = dispatch_get_main_queue();
/**
根据制定参数创建队列,第一个参数为队列标签,第二个参数有DISPATCH_QUEUE_SERIAL和DISPATCH_QUEUE_CONCURRENT两种,分别代表串行队列和并发队列
**/
//创建串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
//创建并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
任务
任务就是用户提交给队列的工作单元,也就是位于block块中的代码,这些任务将会交给队列底层维护的多线程池执行,因此这些任务会以多线程的方式执行
同步提交任务 vs 异步提交任务
同步提交是说只有在提交的任务完成后才会返回继续执行。因此,即使是提交给并发队列也会造成当前线程的阻塞。
同步提交一般使用下面的函数
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
而异步提交则是提交了任务后就理解返回而不必等到提交的任务执行完。因此,异步提交不会引起线程阻塞。
异步提交一般使用下面的函数
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
举几个例子对比
同步提交给串行队列 vs 异步提交给串行队列
且看下面的代码
//同步提交给串行队列
- (void)serialQueueTestWithSync
{
dispatch_queue_t serialQueueTest = dispatch_queue_create("serialQueueTest", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueueTest, ^{
for(int i = 0; i < 5; ++i)
{
NSLog(@"第一个任务在线程:%@->:%d",[NSThread currentThread],i);
}
});
NSLog(@"\n提交完成\n");
dispatch_sync(serialQueueTest, ^{
for(int i = 0; i < 5; ++i)
{
NSLog(@"第二个任务在线程:%@->:%d",[NSThread currentThread],i);
}
});
}
执行后的输出结果如下:
2015-09-06 20:56:00.387 GCDDemo[1103:36743] 第一个任务在线程:<NSThread: 0x7fc0d341c3f0>{number = 1, name = main}->:0
2015-09-06 20:56:00.388 GCDDemo[1103:36743] 第一个任务在线程:<NSThread: 0x7fc0d341c3f0>{number = 1, name = main}->:1
2015-09-06 20:56:00.388 GCDDemo[1103:36743] 第一个任务在线程:<NSThread: 0x7fc0d341c3f0>{number = 1, name = main}->:2
2015-09-06 20:56:00.388 GCDDemo[1103:36743] 第一个任务在线程:<NSThread: 0x7fc0d341c3f0>{number = 1, name = main}->:3
2015-09-06 20:56:00.388 GCDDemo[1103:36743] 第一个任务在线程:<NSThread: 0x7fc0d341c3f0>{number = 1, name = main}->:4
2015-09-06 20:56:00.388 GCDDemo[1103:36743]
提交完成
2015-09-06 20:56:00.389 GCDDemo[1103:36743] 第二个任务在线程:<NSThread: 0x7fc0d341c3f0>{number = 1, name = main}->:0
2015-09-06 20:56:00.389 GCDDemo[1103:36743] 第二个任务在线程:<NSThread: 0x7fc0d341c3f0>{number = 1, name = main}->:1
2015-09-06 20:56:00.389 GCDDemo[1103:36743] 第二个任务在线程:<NSThread: 0x7fc0d341c3f0>{number = 1, name = main}->:2
2015-09-06 20:56:00.389 GCDDemo[1103:36743] 第二个任务在线程:<NSThread: 0x7fc0d341c3f0>{number = 1, name = main}->:3
2015-09-06 20:56:00.389 GCDDemo[1103:36743] 第二个任务在线程:<NSThread: 0x7fc0d341c3f0>{number = 1, name = main}->:4
可以看到在第一个任务执行完毕后才会去执行打印提交完成
这句话。
再看异步提交的代码
- (void)serialQueueTestWithAsyc
{
dispatch_queue_t serialQueueTest = dispatch_queue_create("serialQueueTest", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueueTest, ^{
for(int i = 0; i < 5; ++i)
{
NSLog(@"第一个任务在线程:%@->:%d",[NSThread currentThread],i);
}
});
NSLog(@"\n提交完成\n");
dispatch_async(serialQueueTest, ^{
for(int i = 0; i < 5; ++i)
{
NSLog(@"第二个任务在线程:%@->:%d",[NSThread currentThread],i);
}
});
}
执行后输出结果如下
2015-09-06 20:58:22.248 GCDDemo[1138:37848]
提交完成
2015-09-06 20:58:22.248 GCDDemo[1138:37928] 第一个任务在线程:<NSThread: 0x7f9648419e20>{number = 2, name = (null)}->:0
2015-09-06 20:58:22.249 GCDDemo[1138:37928] 第一个任务在线程:<NSThread: 0x7f9648419e20>{number = 2, name = (null)}->:1
2015-09-06 20:58:22.249 GCDDemo[1138:37928] 第一个任务在线程:<NSThread: 0x7f9648419e20>{number = 2, name = (null)}->:2
2015-09-06 20:58:22.249 GCDDemo[1138:37928] 第一个任务在线程:<NSThread: 0x7f9648419e20>{number = 2, name = (null)}->:3
2015-09-06 20:58:22.250 GCDDemo[1138:37928] 第一个任务在线程:<NSThread: 0x7f9648419e20>{number = 2, name = (null)}->:4
2015-09-06 20:58:22.250 GCDDemo[1138:37928] 第二个任务在线程:<NSThread: 0x7f9648419e20>{number = 2, name = (null)}->:0
2015-09-06 20:58:22.250 GCDDemo[1138:37928] 第二个任务在线程:<NSThread: 0x7f9648419e20>{number = 2, name = (null)}->:1
2015-09-06 20:58:22.250 GCDDemo[1138:37928] 第二个任务在线程:<NSThread: 0x7f9648419e20>{number = 2, name = (null)}->:2
2015-09-06 20:58:22.250 GCDDemo[1138:37928] 第二个任务在线程:<NSThread: 0x7f9648419e20>{number = 2, name = (null)}->:3
2015-09-06 20:58:22.250 GCDDemo[1138:37928] 第二个任务在线程:<NSThread: 0x7f9648419e20>{number = 2, name = (null)}->:4
这次首先打印出的是提交完成
这句话,说明提交完第一个任务函数就立即返回了而没有等到第一个任务执行完成才返回。
同步提交给并发队列 vs 异步提交给并发队列
其实原理上跟提交给串行队列是差不多的,直接看例子吧
同步提交给并发队列
- (void)concurrentQueueTestWithSync
{
dispatch_queue_t queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
for(int i = 0; i < 5; ++i)
{
NSLog(@"第一个任务在线程:%@->:%d",[NSThread currentThread],i);
}
});
NSLog(@"第一个任务提交到线程:%@",[NSThread currentThread]);
dispatch_sync(queue, ^{
for(int i = 0; i < 5; ++i)
{
NSLog(@"第二个任务在线程:%@->:%d",[NSThread currentThread],i);
}
});
NSLog(@"第二个任务提交到线程:%@",[NSThread currentThread]);
}
再看输出
2015-09-06 21:06:18.141 GCDDemo[1190:40587] 第一个任务在线程:<NSThread: 0x7fef00e1ddd0>{number = 1, name = main}->:0
2015-09-06 21:06:18.142 GCDDemo[1190:40587] 第一个任务在线程:<NSThread: 0x7fef00e1ddd0>{number = 1, name = main}->:1
2015-09-06 21:06:18.142 GCDDemo[1190:40587] 第一个任务在线程:<NSThread: 0x7fef00e1ddd0>{number = 1, name = main}->:2
2015-09-06 21:06:18.142 GCDDemo[1190:40587] 第一个任务在线程:<NSThread: 0x7fef00e1ddd0>{number = 1, name = main}->:3
2015-09-06 21:06:18.142 GCDDemo[1190:40587] 第一个任务在线程:<NSThread: 0x7fef00e1ddd0>{number = 1, name = main}->:4
2015-09-06 21:06:18.143 GCDDemo[1190:40587] 第一个任务提交到线程:<NSThread: 0x7fef00e1ddd0>{number = 1, name = main}
2015-09-06 21:06:18.143 GCDDemo[1190:40587] 第二个任务在线程:<NSThread: 0x7fef00e1ddd0>{number = 1, name = main}->:0
2015-09-06 21:06:18.143 GCDDemo[1190:40587] 第二个任务在线程:<NSThread: 0x7fef00e1ddd0>{number = 1, name = main}->:1
2015-09-06 21:06:18.143 GCDDemo[1190:40587] 第二个任务在线程:<NSThread: 0x7fef00e1ddd0>{number = 1, name = main}->:2
2015-09-06 21:06:18.143 GCDDemo[1190:40587] 第二个任务在线程:<NSThread: 0x7fef00e1ddd0>{number = 1, name = main}->:3
2015-09-06 21:06:18.144 GCDDemo[1190:40587] 第二个任务在线程:<NSThread: 0x7fef00e1ddd0>{number = 1, name = main}->:4
2015-09-06 21:06:18.144 GCDDemo[1190:40587] 第二个任务提交到线程:<NSThread: 0x7fef00e1ddd0>{number = 1, name = main}
可以看到,当第一个任务提交并执行完毕后,函数才返回并打印第一个任务提交到线程XXXX
这句话,当第二个任务提交并执行完毕后,函数才返回并打印第二个任务提交到线程XXX
这句话。
并且由于同步提交这两个任务给同一个并发队列实际上是这两个任务在串行执行,因此,GCD并没有使用额外的线程去执行第二个任务。
再看异步提交给并发队列的
- (void)concurrentQueueTestWithAsync
{
dispatch_queue_t queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for(int i = 0; i < 5; ++i)
{
NSLog(@"第一个任务在线程:%@->:%d",[NSThread currentThread],i);
}
});
NSLog(@"第一个任务提交到线程:%@",[NSThread currentThread]);
dispatch_async(queue, ^{
for(int i = 0; i < 5; ++i)
{
NSLog(@"第二个任务在线程:%@->:%d",[NSThread currentThread],i);
}
});
NSLog(@"第二个任务提交到线程:%@",[NSThread currentThread]);
}
再看输出结果
2015-09-06 21:11:44.624 GCDDemo[1235:42766] 第一个任务提交到线程:<NSThread: 0x7fc2f1d24d80>{number = 1, name = main}
2015-09-06 21:11:44.624 GCDDemo[1235:42871] 第一个任务在线程:<NSThread: 0x7fc2f1f30a50>{number = 2, name = (null)}->:0
2015-09-06 21:11:44.625 GCDDemo[1235:42766] 第二个任务提交到线程:<NSThread: 0x7fc2f1d24d80>{number = 1, name = main}
2015-09-06 21:11:44.625 GCDDemo[1235:42872] 第二个任务在线程:<NSThread: 0x7fc2f1c06f00>{number = 3, name = (null)}->:0
2015-09-06 21:11:44.625 GCDDemo[1235:42871] 第一个任务在线程:<NSThread: 0x7fc2f1f30a50>{number = 2, name = (null)}->:1
2015-09-06 21:11:44.626 GCDDemo[1235:42872] 第二个任务在线程:<NSThread: 0x7fc2f1c06f00>{number = 3, name = (null)}->:1
2015-09-06 21:11:44.626 GCDDemo[1235:42871] 第一个任务在线程:<NSThread: 0x7fc2f1f30a50>{number = 2, name = (null)}->:2
2015-09-06 21:11:44.627 GCDDemo[1235:42872] 第二个任务在线程:<NSThread: 0x7fc2f1c06f00>{number = 3, name = (null)}->:2
2015-09-06 21:11:44.627 GCDDemo[1235:42871] 第一个任务在线程:<NSThread: 0x7fc2f1f30a50>{number = 2, name = (null)}->:3
2015-09-06 21:11:44.627 GCDDemo[1235:42872] 第二个任务在线程:<NSThread: 0x7fc2f1c06f00>{number = 3, name = (null)}->:3
2015-09-06 21:11:44.627 GCDDemo[1235:42871] 第一个任务在线程:<NSThread: 0x7fc2f1f30a50>{number = 2, name = (null)}->:4
2015-09-06 21:11:44.627 GCDDemo[1235:42872] 第二个任务在线程:<NSThread: 0x7fc2f1c06f00>{number = 3, name = (null)}->:4
从第一个任务提交到线程:<NSThread: 0x7fc2f1d24d80>{number = 1, name = main}
和第二个任务提交到线程:<NSThread: 0x7fc2f1d24d80>{number = 1, name = main}
两句话的位置也可以看出提交任务的线程并没有等到任务线程执行完才往下执行,而是提交任务后函数立即返回并继续往下执行。
同时,这次第一跟第二两个任务是在不同的线程内执行。