目录
GCD
在开始讨论queue和thread之前, 我们有必要先看看绕不开的GCD -- 一套被"神话"的并发APIs
如果想恶补一下基础知识, 请参考Objective-C学习 之 GCD
dispatch_sync
Apple Developer Documentation对dispatch_sync最权威的解释如下
Submits a block object for execution on a dispatch queue and waits until that block completes
dispatch_sync有两个很重要的特点
1: this function does not return until the block has finished
这句话似乎是老生常谈了, 那为什么还要接着谈呢? 这是因为它会引起deadlock
Calling this function and targeting the current queue results in deadlock
我们来通过一个"精美的插图"(在哪里? 我要看!)来理解发生deadlock时的情形
前面的等后面的, 后面的等前面的, 整个queue就这样"瘫痪"了
上面举的是main thread和main queue的例子, 其实
只要是运行dispatch_sync的queue和运行dispatch_sync block的是同一个queue都会出现deadlock
明白了原理, 我们来看看人家FMDB是怎么做的以屏蔽deadlock
// 第一个部分
/*
* A key used to associate the FMDatabaseQueue object with the dispatch_queue_t it uses.
* This in turn is used for deadlock detection by seeing if inDatabase: is called on
* the queue's dispatch queue, which should not happen and causes a deadlock.
*/
static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;
// 第二部分
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);
// 第三部分
- (void)inDatabase:(void (^)(FMDatabase *db))block {
/* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue
* and then check it against self to make sure we're not about to deadlock. */
FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
FMDBRetain(self);
dispatch_sync(_queue, ^() {
FMDatabase *db = [self database];
block(db);
FMDBRelease(self);
}
2: As an optimization, this function invokes the block on the current thread when possible
可能很多开发者都忽略了这点, so请反复读3遍以加深印象(至少我之前没有理解这个特点时就曾常常困惑)
为了证实, 我们来看下实际运行的结果如何
实例1:
- (IBAction)buttonOnClicked:(UIButton *)sender {
dispatch_queue_t myQueue = dispatch_queue_create("myQueue", NULL);
dispatch_sync(myQueue, ^{
for (NSInteger i = 0; i < 100; i++) {
// currentThread number = 1
NSLog(@"currentThread = %@, i = %ld", [NSThread currentThread], (long) i);
}
});
}
输出如下:
// 注意这里的thread number = 1, 表示和buttonOnClicked都处于同样的main thread
currentThread = <NSThread: 0x7fae2a607f20>{number = 1, name = main}, i = 0
currentThread = <NSThread: 0x7fae2a607f20>{number = 1, name = main}, i = 1
...
currentThread = <NSThread: 0x7fae2a607f20>{number = 1, name = main}, i = 99
dispatch_async
如果你确定"真正"了解了dispatch_sync, 那么理解dispatch_async起来就"简直"了
按照国际惯例, 我们先来看看Apple Developer Documentation对dispatch_async的权威解释
Submits a block for asynchronous execution on a dispatch queue and returns immediately
是不是很简单? 那我们就直接看例子吧
实例2:
- (IBAction)buttonOnClicked:(UIButton *)sender {
dispatch_queue_t myQueue = dispatch_queue_create("myQueue", NULL);
dispatch_async(myQueue, ^{
for (NSInteger i = 0; i < 100; i++) {
// currentThread number = 2
NSLog(@"currentThread = %@, i = %ld", [NSThread currentThread], (long) i);
}
});
}
输出如下:
currentThread = <NSThread: 0x7fae2a510fa0>{number = 2, name = (null)}, i = 0
currentThread = <NSThread: 0x7fae2a510fa0>{number = 2, name = (null)}, i = 1
...
currentThread = <NSThread: 0x7fae2a510fa0>{number = 2, name = (null)}, i = 99
�queue
queue又称dispatch queue, 作为一个"搬运工", 它在Apple Developer Documentation中的定义如下
A dispatch queue is an object-like structure that manages the tasks you submit to it
注意这里的关键词: structure
没错, queue只是一个数据结构, 并且是这样一个FIFO的数据结构
All dispatch queues are first-in, first-out data structures
对于上面queue的定义, 可能还有另外一个会让你困惑的地方, 那就是task, 那什么是task呢?
A task is simply some work that your application needs to perform
serial queue和concurrent queue
确定, 一定以及肯定理解以上概念后, 我们来看下实际开发中有哪些不同类型的queue
serial: Serial queues execute one task at a time in the order in which they are added to the queue
Concurrent: Concurrent queues execute one or more tasks concurrently, but tasks are still started in the order in which they were added to the queue
概念就这么念完了, 到底理解多少个体差异应该是很大的, 下面我就来"唠叨"几句
queue中的每个task在哪个线程执行, 是无法确定的(好吧, 我承认其实并不严谨, 但在没有深入讨论之前, 这么说是最简单明了不过了)
因为queue中task的执行和调度是GCD封装好的, 开发者不需要care, 这也是GCD最大的价值
不管是serial queue还是conconcurrent queue, 所有的task都是FIFO的(不然怎么说是queue嘛)
但是serial queue中的task是等上一个执行完了才执行下一个, 而concurrent queue中的task虽然也是FIFO的, 但不等上一个task执行下, 下一个task或许就执行了
所以实际运行的结果是, serial queue里的task都是在同一个thread执行的, 而concurrent queue的task可能在多个不同的thread中执行
注意这里的concurrent queue task用的是"可能"这个词, 为什么? concurrent难道不就是指多线程么?
是的, 并发通常是指多线程, 但是你也知道, dispatch queue这一套机制是GCD封装调度的
而concurrent queue里的task是否需要在多个线程中并发执行, GCD会根据实际task的情况来决定
为什么? 因为多线程并不意味着就高效率, 线程的开销和调度也是需要成本的, 这种task和线程成本的权衡, GCD帮我们做了(这也是我们为什么要使用GCD, 而不是自己去实现一套, GCD考虑得比开发者考虑的更完善)
main queue
main queue不需要自己创建, 直接使用如下接口即可获取
dispatch_queue_t dispatch_get_main_queue(void);
Apple Developer Documentation对main queue的准确定义如下
The main dispatch queue is a globally available serial queue that executes tasks on the application’s main thread
它有几个很重要的特点:
它是由系统创建并全局可见的
它是一个serial queue
它的task都是在main thread运行的
特点很简单, 但是使用main queue时, 有一点是需要注意的, 那就是deadlock, 所以main queue通常和diapatch_async一起使用
dispatch_async(dispatch_get_main_queue(), ^{
for (NSInteger i = 0; i < 100; i++) {
// currentThread number = 1
NSLog(@"currentThread = %@, i = %ld", [NSThread currentThread], (long) i);
}
});
global queue
除了main queue, 系统还为我们创建了concurrent的global queue, 方便开发者使用(为什么说GCD是一套"神"接口, 真是想开发者所想啊)
global queue通过以下接口获取
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);
系统默认已经我们创建好了4种不同优先级的global queue, 通过参数long identifier即可获取相对应的queue
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND
自己创建queue
上面讨论的serial类型的main queue和concurrent类型的global queue都是系统为我们创建好的
如果想要创建自己的queue怎么办呢? 答案是这个接口
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
重点需要关注这里的参数dispatch_queue_attr_t attr
想要创建serial queue, 把attr设置成DISPATCH_QUEUE_SERIAL或者NULL
想要创建concurrent queue, 把attr设置成DISPATCH_QUEUE_CONCURRENT
测验
概念和道理就这么多了, 不知道"聪明"的你理解了多少呢?(实不相瞒, 我自己也是花了好多心思才"入了门")
下面进入测验环节来检验下自己理解得怎么样吧
为了测试答案的一致性, 有以下几点说明
运行下面测试代码的app除了创建工程时的模板代码外, 只单独(注意这里的措辞!)包含下面每个测试题的代码
下面的buttonOnClicked都是在main thread中执行的
测试1
- (IBAction)buttonOnClicked:(UIButton *)sender {
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
for (NSInteger i = 0; i < 100; i++) {
// currentThread number = 1
NSLog(@"currentThread = %@, i = %ld", [NSThread currentThread], (long) i);
}
});
}
请问打印的currentThread的thread number是?
A: 1 (即主线程)
B: 2 (即子线程)
C: 2...N (即多个子线程)
D: 以上答案都是忽悠人的
测试2
- (IBAction)buttonOnClicked:(UIButton *)sender {
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
for (NSInteger i = 0; i < 100; i++) {
// currentThread number = 1
NSLog(@"currentThread = %@, i = %ld", [NSThread currentThread], (long) i);
}
});
}
请问打印的currentThread的thread number是?
A: 1 (即主线程)
B: 2 (即子线程)
C: 2...N (即多个子线程)
D: 以上答案都是忽悠人的
测试3
- (IBAction)buttonOnClicked:(UIButton *)sender {
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(concurrentQueue, ^{
for (NSInteger i = 0; i < 1000; i++) {
NSLog(@"currentThread = %@, i = %ld", [NSThread currentThread], (long) i);
}
});
}
请问打印的currentThread的thread number是?
A: 1 (即主线程)
B: 2 (即子线程)
C: 2...N (即多个子线程)
D: 以上答案都是忽悠人的
测试4
- (IBAction)buttonOnClicked:(UIButton *)sender {
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
for (NSInteger i = 0; i < 1000; i++) {
NSLog(@"currentThread = %@, i = %ld", [NSThread currentThread], (long) i);
}
});
}
请问打印的currentThread的thread number是?
A: 1 (即主线程)
B: 2 (即子线程)
C: 2...N (即多个子线程)
D: 以上答案都是忽悠人的
测验就到这里, 祝大家玩的愉快
慢着? 没有答案? 如果想要正确答案的话, 请联系我, 哈哈……
更多文章, 请支持我的个人博客