首先,我们先来看看,在iOS领域中,常用的多线程方案,大致上有哪几种。基本上,我们最常用的应该是GCD、NSThread和NSOperation这三种,除此之外,还有一种不是很常用的pthread,这次,我们可以用一个表格来总结多线程的几种方案以及他们各自的特点:

在这里,我们就针对最常用的GCD,来看看多线程的使用。首先,我们需要了解的是,GCD的执行任务方式有两种:就是async(异步)和sync(同步)。以下是两种模式的执行代码定义:
dispatch_async(dispatch_queue_t queue, dispatch_block_t block); // 异步模式
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block); // 同步模式
这里同步和异步的函数都有两个参数,一是队列(queue),而是任务(block)。
任务其实很好理解,它就是一个执行代码的block。这里我们需要掌握的是Queue对象这个概念。首先,这里的队列有两种,并行和串行。从字面意思大家就知道,并行就是一起进行,串行就是单个进行。然后我们把队列和任务结合在一起讲就是,并行队列里面的任务,是一起执行的,基本不分先后;而串行队列的任务是依次执行的。
然后我们再回过头去看看dispatch_async、dispatch_sync这两个GCD的函数,首先大家需要知道,这两个同步和异步的函数,它们主要的区别(或者说是对程序的影响),在于是否能够开启新的线程。
1、同步:在当前线程中执行任务,当然不会也不需要再去开启一个新的线程。
2、异步:在新的线程中执行任务,具备开启新线程的能力。
接下来,我们还是用代码的方式来观察下同步、异步、串行、并行:
/**
异步串行
*/
- (void)async_serial {
dispatch_queue_t queue = dispatch_queue_create("new", DISPATCH_QUEUE_SERIAL); // 串行队列
dispatch_async(queue, ^{
NSLog(@"1 - %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2 - %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3 - %@", [NSThread currentThread]);
});
}
// 打印如下:
2019-09-03 17:41:20.025056+0800 001-多线程的理解以及常规使用[24993:30713871] 1 - <NSThread: 0x600001d4cdc0>{number = 3, name = (null)}
2019-09-03 17:41:20.025377+0800 001-多线程的理解以及常规使用[24993:30713871] 2 - <NSThread: 0x600001d4cdc0>{number = 3, name = (null)}
2019-09-03 17:41:20.025503+0800 001-多线程的理解以及常规使用[24993:30713871] 3 - <NSThread: 0x600001d4cdc0>{number = 3, name = (null)}
可以看到,异步串行,开启了新的线程,但是串行一次执行任务
/**
异步并行
*/
- (void)async_concurrent {
dispatch_queue_t queue = dispatch_queue_create("new", DISPATCH_QUEUE_CONCURRENT); // 并行队列
dispatch_async(queue, ^{
NSLog(@"1 - %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2 - %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3 - %@", [NSThread currentThread]);
});
}
// 打印如下:
2019-09-03 17:44:18.598563+0800 001-多线程的理解以及常规使用[25051:30717738] 2 - <NSThread: 0x600001a68700>{number = 4, name = (null)}
2019-09-03 17:44:18.598564+0800 001-多线程的理解以及常规使用[25051:30717741] 3 - <NSThread: 0x600001a5c500>{number = 5, name = (null)}
2019-09-03 17:44:18.598598+0800 001-多线程的理解以及常规使用[25051:30717739] 1 - <NSThread: 0x600001a6cf80>{number = 3, name = (null)}
可以看到,异步并行,开启了新的线程,而且是没有顺序的执行任务
/**
同步串行
*/
- (void)sync_serial {
dispatch_queue_t queue = dispatch_queue_create("new", DISPATCH_QUEUE_SERIAL); // 串行队列
dispatch_sync(queue, ^{
NSLog(@"1 - %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2 - %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3 - %@", [NSThread currentThread]);
});
}
// 打印如下:
2019-09-03 17:46:10.533988+0800 001-多线程的理解以及常规使用[25089:30721269] 1 - <NSThread: 0x600001b20940>{number = 1, name = main}
2019-09-03 17:46:10.534154+0800 001-多线程的理解以及常规使用[25089:30721269] 2 - <NSThread: 0x600001b20940>{number = 1, name = main}
2019-09-03 17:46:10.534256+0800 001-多线程的理解以及常规使用[25089:30721269] 3 - <NSThread: 0x600001b20940>{number = 1, name = main}
可以看到,同步串行,没有开启新的线程,是在当前主线程下,依次执行任务
/**
同步并行
*/
- (void)sync_concurrent {
dispatch_queue_t queue = dispatch_queue_create("new", DISPATCH_QUEUE_CONCURRENT); // 并行队列
dispatch_sync(queue, ^{
NSLog(@"1 - %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2 - %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3 - %@", [NSThread currentThread]);
});
}
// 打印如下:
2019-09-03 17:47:31.950982+0800 001-多线程的理解以及常规使用[25122:30723769] 1 - <NSThread: 0x600001bad380>{number = 1, name = main}
2019-09-03 17:47:31.951118+0800 001-多线程的理解以及常规使用[25122:30723769] 2 - <NSThread: 0x600001bad380>{number = 1, name = main}
2019-09-03 17:47:31.951238+0800 001-多线程的理解以及常规使用[25122:30723769] 3 - <NSThread: 0x600001bad380>{number = 1, name = main}
可以看到,同步并行,没有开启新的线程,是在当前主线程下,依次执行任务
最后一个特别的点,就是在主队列中添加任务,并且在同步和异步的模式下执行
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"1 - %@", [NSThread currentThread]);
});
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2 - %@", [NSThread currentThread]);
});
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3 - %@", [NSThread currentThread]);
});
}
首先是在主线程中同步添加主队列,这种方式,会导致死锁,原因就是本身viewDidLoad就是主队列的任务,然后在这个任务中添加同步主队列任务的话,会形成相互等待,导致死锁。
其实,总结一点就是,使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"1 - %@", [NSThread currentThread]);
});
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2 - %@", [NSThread currentThread]);
});
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3 - %@", [NSThread currentThread]);
});
});
}
// 打印如下:
2019-09-04 17:34:31.971216+0800 001-多线程的理解以及常规使用[46330:33540097] 1 - <NSThread: 0x600003721380>{number = 1, name = main}
2019-09-04 17:34:31.972003+0800 001-多线程的理解以及常规使用[46330:33540097] 2 - <NSThread: 0x600003721380>{number = 1, name = main}
2019-09-04 17:34:31.972770+0800 001-多线程的理解以及常规使用[46330:33540097] 3 - <NSThread: 0x600003721380>{number = 1, name = main}
可以看到,在子队列中,同步添加主队列任务,是在当前主线程下,依次执行任务
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"1 - %@", [NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2 - %@", [NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"3 - %@", [NSThread currentThread]);
});
});
}
// 打印如下:
2019-09-04 17:38:09.383051+0800 001-多线程的理解以及常规使用[46419:33546730] 1 - <NSThread: 0x600002c82940>{number = 1, name = main}
2019-09-04 17:38:09.383188+0800 001-多线程的理解以及常规使用[46419:33546730] 2 - <NSThread: 0x600002c82940>{number = 1, name = main}
2019-09-04 17:38:09.383281+0800 001-多线程的理解以及常规使用[46419:33546730] 3 - <NSThread: 0x600002c82940>{number = 1, name = main}
可以看到,在子队列中,异步添加主队列任务,是在当前主线程下,依次执行任务
由上述,我们可以得到如下结论:

最后,我们来看看队列组的使用
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("new", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
NSLog(@"111");
});
dispatch_group_async(group, queue, ^{
NSLog(@"222");
});
dispatch_group_async(group, queue, ^{
NSLog(@"333");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"444");
});
// 打印如下:
2019-09-04 17:43:37.865546+0800 001-多线程的理解以及常规使用[46513:33554033] 111
2019-09-04 17:43:37.865558+0800 001-多线程的理解以及常规使用[46513:33554031] 333
2019-09-04 17:43:37.865550+0800 001-多线程的理解以及常规使用[46513:33554038] 222
2019-09-04 17:43:37.865739+0800 001-多线程的理解以及常规使用[46513:33554033] 444
队列组,就是完成dispatch_group_async中所有的任务之后,最后返回到dispatch_group_notify中,其实就是dispatch_group_notify这个函数在监听队列组中的任务的完成情况,一旦完成,则会去执行dispatch_group_notify函数中的任务。
多线程的基本使用,先说到这里,接下来一篇文章,笔者准备分享多线程的安全隐患,以及一些常用的解决方案(包含iOS中常用的几大锁的类型),如下:
OSSpinLock
os_unfair_lock
pthread_mutex
dispatch_semaphore
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSRecursiveLock
NSCondition
NSConditionLock
@synchronized
下一篇文章见!
网友评论