多线程实现的几种方案
技术方案 | 简介 | 语言 | 线程生命周期 | 使用频率 |
---|---|---|---|---|
pthread | 一套通用的多线程方案 适用于Linux、Unix、macOS等平台 跨平台/可移植,使用难度大 |
C | 程序员管理 | 机会不用 |
NSThread | 苹果封装,更加面向对象 简单使用,可直接操作线程对象 |
OC | 程序员管理 | 偶尔使用 |
GCD | 旨在替代NSThread多线程技术 充分利用设备的多核 |
C | 自动管理 | 经常使用 |
NSOperation | 基于GCD(底层GCD) 但比GCD多了一些更简单实用的功能 使用更加面向对象 |
OC | 自动管理 | 经常使用 |
GCD的简介
GCD中有两个很重要的概念: 任务
、队列
。队列
中存放的就是多个任务
,队列
的执行,就是执行其存放的各个任务
。
任务:即操作,说白了就是一段代码块,在GCD中就是一个block
,所以添加任务十分方便。任务有同步
(sync
)和异步
(async
)的区分。主要的区别在于会不会阻塞当前的线程。
-
同步任务(sync,即同步操作)
,它会阻塞当前线程,直到block
中的任务执行完毕,然后当前线程才会继续执行。 -
异步任务(async,即异步操作)
,它不会阻塞当前线程,当前线程会直接往下执行。
队列:用于存放任务,决定任务是顺序执行的还是并发执行的。队列对应的有两种:串行队列
、并行队列
。
-
串行队列
,根据定义,由于是队列(queue)
,它遵从队列的FIFO
特性,即按照任务添加的顺序存依次执行。 -
并行队列
,放到并行队列的任务,GCD 也会 FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。
线程(
thread
)与队列(queue
)的关系:线程
和队列
是两个不同的概念。队列
是用来存放任务的,而线程
是由系统去创建和管理,而且一个线程中可能有多个串行
和并行
队列。
创建队列
- 主队列:这是一个特殊的
串行队列
。主队列用于刷新UI,任何需要刷新UI的工作都是在主队列中执行,所以一般耗时的任务都是放到别的线程中执行。
//获取主队列,即主线程
dispatch_queue_t queue = dispatch_get_main_queue();
- 手动创建队列:
dispatch_queue_create(const char *_Nullable label,
dispatch_queue_attr_t _Nullable attr);
其中第一个参数是标识符,用于DEBUG时便于知道是哪个唯一的队列,可以为空。
第二个参数用来表示创建的队列是串行队列
、还是并行队列
:
- DISPATCH_QUEUE_SERIAL
或者NULL
表示创建串行队列;
- DISPATCH_QUEUE_CONCURRENT
表示创建的是并行队列。
- 全局并发队列:系统提供的一个并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
创建任务
- 同步任务:会阻塞当前线程(
sync
)
dispatch_sync(<#queue#>, ^{
//code here
NSLog(@"同步任务");
});
- 异步任务:不会阻塞(
async
)
dispatch_async(<#queue#>, ^{
//code here
NSLog(@"异步任务");
});
组合代码测试
- 同步任务 + 主队列
代码:
//主队列的任务(A)
- (void)syncAndMainQueue {
//同步任务 + 主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
NSLog(@"begin --- %@", [NSThread currentThread]);
for (NSInteger index = 0 ; index < 10; index ++) {
//同步任务(B)
dispatch_sync(mainQueue, ^{
NSLog(@"index : %ld --- %@", index , [NSThread currentThread]);
});
}
NSLog(@"end --- %@", [NSThread currentThread]);
}
打印信息:
结论:程序卡死,由于是同步任务(B),会阻塞当前线程,等待加入到线程的同步任务执行完成之后才会继续当前任务,而由于主队列是串行队列,会按按顺序(
FIFO
)执行队列中添加的任务,所以同步任何会等待正在执行的任务执行完毕,这样就造成了相互等待,继而造成死锁。
- 异步任务 + 主队列
代码:
- (void)asyncAndMainQueue {
//异步任务 + 主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
NSLog(@"begin --- %@", [NSThread currentThread]);
for (NSInteger index = 0 ; index < 10; index ++) {
dispatch_async(mainQueue, ^{
NSLog(@"index : %ld --- %@", index , [NSThread currentThread]);
});
}
NSLog(@"end --- %@", [NSThread currentThread]);
}
打印信息:
结论:在主线程串行执行。由于是异步任务,不会阻塞当前线程,由于主队列是串行队列,等待当前任务执行完成之后按顺序执行任务。
- 同步任务 + 串行队列
代码:
- (void)syncAndSerialQueue {
//同步任务 + 串行队列
dispatch_queue_t serailQueue = dispatch_queue_create("serialQueue", NULL);
NSLog(@"begin --- %@", [NSThread currentThread]);
for (NSInteger index = 0 ; index < 10; index ++) {
dispatch_sync(serailQueue, ^{
NSLog(@"index: %ld --- %@", index , [NSThread currentThread]);
});
}
NSLog(@"end --- %@", [NSThread currentThread]);
}
打印:
结论:没有开启新线程,在当前线程执行该同步任务。由于是同步任务,会阻塞当前线程,而队列是串行队列,会按顺序执行任务,等到该队列中的所有任务执行完毕之后,当前线程才会继续执行下去。
- 异步任务 + 串行队列
代码:
- (void)asyncAndSerialQueue {
//异步任务 + 串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", NULL);
NSLog(@"begin --- %@", [NSThread currentThread]);
for (NSInteger index = 0; index < 10; index ++) {
dispatch_async(serialQueue, ^{
NSLog(@"index: %ld --- %@", index , [NSThread currentThread]);
});
}
NSLog(@"end --- %@", [NSThread currentThread]);
}
打印:
结论:由于是异步任务,不会阻塞当前线程,开了新线程,由于是串行队列,会按顺序执行任务,所以只需开启一条新的线程。
- 同步任务 + 并行队列
代码:
- (void)syncAndConcurrent {
//同步任务 + 并行队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"begin --- %@", [NSThread currentThread]);
for (NSInteger index = 0; index < 10; index ++) {
dispatch_sync(concurrentQueue, ^{
NSLog(@"index: %ld --- %@", index , [NSThread currentThread]);
});
}
NSLog(@"end --- %@", [NSThread currentThread]);
}
打印:
结论:由于是同步任务,会阻塞当前线程,且不会开启新线程,所以同步任务会在当前线程执行完毕,等到完毕之后,当前线程执行执行。
- 异步任务 + 并行队列
- (void)asyncAndConcurrent {
//异步任务 + 并行队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"begin --- %@", [NSThread currentThread]);
for (NSInteger index = 0; index < 10; index ++) {
dispatch_async(concurrentQueue, ^{
NSLog(@"index: %ld --- %@", index, [NSThread currentThread]);
});
}
NSLog(@"end --- %@", [NSThread currentThread]);
}
打印
结论:由于是异步任务,不会阻塞当前线程,同时会开启新的线程,而且由于队列是并行队列,会开启多条线程,并发的执行这些异步任务。
总结
同步(sync) | 异步(async) | |
---|---|---|
串行队列 | 当前线程,顺序执行 | 其他线程,顺序执行 |
并行队列 | 当前线程,顺序执行 | 开多个线程,并发执行 |
主队列 | 程序死锁 | 主队列,顺序执行 |
-
- 开不开线程,取决于执行任务的函数,同步不开,异步开。
-
- 开几条线程,取决于队列,串行开一条,并发开多条(异步)
-
- 主队列: 专门用来在主线程上调度任务的"队列",主队列不能在其他线程中调度任务!