多线程:pthread、NSThread、GCD 和 NSOperation

在 iOS 开发中,可以通过以下四种方法创建子线程:

pthread

pthread 是来自 Clang 中的方法,使用前需要手动 #import <pthread.h>

1
2
3
4
5
6
7
8
9
10
11
/*
* <#pthread_t *restrict#> : 要在哪一个线程对象中执行(Thread Object 的地址)
* <#const pthread_attr_t *restrict#> : 要给Thread Object 的属性
* <#void *(*)(void *)#> : 要执行的函数(指向函数的指针)
* <#void *restrict#> : 要传入的参数
*/
pthread_create(<#pthread_t *restrict#>, <#const pthread_attr_t *restrict#>, <#void *(*)(void *)#>, <#void *restrict#>)
pthread_t thread; // 声明一个线程对象(Thread Object)
// 在 "thread" 线程对象中执行名为 "runInChildThread" 的函数,线程名为 "anChildThread"
pthread_create(&thread, NULL, runInChildThread, (__bridge void *)(@"anChildThread"));

NSThread

直接创建一个子线程 (alloc init)
1
convenience init(target target: AnyObject, selector selector: Selector, object argument: AnyObject?)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument
/*
<#(nonnull id)#> : 目标对象
<#(nonnull SEL)#> : @selector(),需要调用的方法
<#(nullable id)#> : 前面方法要接收的参数(对象)
*/
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument
NSThread *thread = [[NSThread alloc] initWithTarget:<#(nonnull id)#> selector:<#(nonnull SEL)#> object:<#(nullable id)#>];
thread.name = @"some name"; // operational
thread.threadPriority = 1.0 // operational 1.0 为最高优先级
// #warning: thread 在执行完成后将会死亡
// 即使有 strong 指向导致没有被释放,也不能再次 + (void)start;
// 否则会 carsh
[thread start];
// 休眠(阻塞)线程,直到 aDate 再继续执行
+ (void)sleepUntilDate:(NSDate *)aDate
// 休眠(阻塞)线程,直到 ti 秒之后再继续执行
+ (void)sleepForTimeInterval:(NSTimeInterval)ti
// 退出线程
+ (void)exit
分离出一条子线程
1
class func detachNewThreadSelector(_ selector: Selector, toTarget target: AnyObject, withObject argument: AnyObject?)
1
2
3
4
5
6
7
8
+ (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument
/*
* <#(nonnull SEL)#> : 要执行的方法
* <#(nonnull id)#> : 执行方法目标对象
* <#(nullable id)#> : 要传入的参数
*/
[NSThread detachNewThreadSelector:<#(nonnull SEL)#> toTarget:<#(nonnull id)#> withObject:<#(nullable id)#>];
创建一条后台线程
1
func performSelectorInBackground(_ aSelector: Selector, withObject arg: AnyObject?)
1
2
3
4
5
6
7
8
9
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
/*
* <#(nonnull SEL)#> : 要在后台调用的方法
* <#(nullable id)#> : 要传入的参数
*/
[object performSelectorInBackground:<#(nonnull SEL)#> withObject:<#(nullable id)#>];
[self performSelectorInBackground:@selector(run:) withObject:@"this is background thread"];
互斥锁
1
2
3
4
5
6
7
8
@synchronized(<#token#>) {
<#statements#>
}
// example:
@synchronized(self) {
// something run
}

互斥锁可以阻止多个线程访问同一个对象,将会消耗性能。
@propertyatomic 即对 getter/setter 方法加锁, nonatomic 则不加锁。

线程间通信

在子线程中,将不能进行 UI 操作,要更改显示状况,必须回到主线程(mainThread)进行操作。

直接回到主线程执行
1
func performSelectorOnMainThread(_ aSelector: Selector, withObject arg: AnyObject?, waitUntilDone wait: Bool, modes array: [String]?)
1
2
3
4
5
6
7
8
9
10
// 这个方法将直接回到主线程执行
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray<NSString *> *)array
/*
* <#(nonnull SEL)#> : @selector()要执行的方法
* <#(nullable id)#> : 为这个方法传入的参数
* <#(BOOL)#> : 是否等待直到结束再继续往下执行
* <#(nullable NSArray<NSString *> *)#> : 执行模式
[self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#> modes:<#(nullable NSArray<NSString *> *)#>];
*/
[self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:NO modes:nil];
选择要执行的线程
1
func performSelector(_ aSelector: Selector, onThread thr: NSThread, withObject arg: AnyObject?, waitUntilDone wait: Bool, modes array: [String]?)
1
2
3
4
5
6
7
8
9
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thread withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray<NSString *> *)array
// <#(nonnull NSThread *)# 为要执行的线程
[self performSelector:<#(nonnull SEL)#> onThread:<#(nonnull NSThread *)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#> modes:<#(nullable NSArray<NSString *> *)#>];
// 通常方法
[self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
// 直接调用 UIImage 中 image 的 setter 方法
[self.someImageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];

综合:[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];

GCD (Grand Central Dispatch) (form libdispatch)

先创建一个队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
* <#const char *label#> : 传入一个 C语言字符串 (char) 作为标签
* <#dispatch_queue_attr_t attr#> : 设置队列的类型
* NULL : 串行队列
* DISPATCH_QUEUE_SERIAL : 串行队列
* DISPATCH_QUEUE_CONCURRENT : 并发队列
*/
dispatch_queue_t queue = dispatch_queue_create(<#const char *label#>, <#dispatch_queue_attr_t attr#>)
// 创建一个串行队列
// 在串行队列中,一个任务执行完毕后才会执行下一个任务
dispatch_queue_t queue = dispatch_queue_create("concurrentQueueName", DISPATCH_QUEUE_SERIAL);
// 取得主队列(主线程中的串行队列)
dispatch_queue_t queue = dispatch_get_main_queue();
// 创建一个并发队列
// 在并发队列中,多个任务同时执行
dispatch_queue_t queue = dispatch_queue_create("concurrentQueueName", DISPATCH_QUEUE_CONCURRENT);
/*
* 取得全局的并发队列
* <#long identifier#> 为优先级
* #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
* #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
* #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
* #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
* <#unsigned long flags#> 是「为未来预留的参数」,填入 0
*/
dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)
// 获取默认优先级的全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

执行一个队列

1
2
3
4
5
6
7
8
9
10
// 同步执行一个 「队列」"queue",将不会开启新线程
dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>)
// 异步执行一个 "queue",将具备开启新线程的能力(但不一定开启新线程)
dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>)
// 等待前面的任务执行完成后才会执行,并且后面的任务要等这个任务执行完后才会执行
// 这个 queue 不能是全局的并发队列
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
#warning 使用同步函数往当前串行队列中中添加任务会卡住(死锁)
// eg. 从主线程中执行如下代码将会发生死锁
dispatch_sync(dispatch_get_main_queue(), ^(void)block)

从子线程回到主线程执行 UI 刷新操作(因为子线程不能操作 UI)

1
2
3
4
5
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
<#code#> // 要回到主线程执行的任务
})
})

延迟执行

1
2
3
4
// <#delayInSeconds#> : 要延迟的时间(秒)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
<#code to be executed after a specified delay#> // 要执行的内容
});

一次性代码

1
2
3
4
5
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 这里的内容在整个程序运行中都只将运行一次
<#code to be executed once#>
});

栅栏函数

1
2
3
// 会等待 <#dispatch_queue_t queue#> 之前的异步函数全部执行完毕再执行
dispatch_barrier_async(<#dispatch_queue_t queue#>, <#^(void)block#>)
// 并且等待 <#dispatch_queue_t queue#> 执行完毕之后才会继续执行后门的 queue

快速迭代

1
2
3
4
// <#size_t iterations#> : 要执行的次数
// <#dispatch_queue_t queue#> : 执行的队列
// <#^(size_t)block#> : 执行的行数(其中 size_t 为传入的序号,顺序不确定)
dispatch_apply(<#size_t iterations#>, <#dispatch_queue_t queue#>, <#^(size_t)block#>)

队列组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 创建队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
#warning 把 queue 添加到 group 的第一种方式:
// 把 `queue` 添加到 `group` 中,可添加多次
dispatch_group_async(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>)
#warning 把 queue 添加到 group 的第二种方式
// 先 enter
dispatch_group_enter(group);
// 再正常添加异步任务,并在任务的 block 最后 leave
dispatch_async(queue, ^{
NSLog(@"download1---%@",[NSThread currentThread]);
// 这里是要执行的任务的 block 中
//block已经执行完毕,通知group
dispatch_group_leave(group);
});
// 上面两种方法都可以添加多个 queue 或者一个 queue 中,可添加多次
// 均为异步执行,不会阻塞
// 当队列组中所有任务完成的时候会通知这个函数来执行
dispatch_group_notify(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>)

NSOperation & NSOperationQueue

将需要执行的任务封装到 NSOperation 中,然后将 NSOperation 加入到 NSOperationQueue 中,任务将会在新线程中执行。

//www.tuccuay.com/post-pic/2016/01/multiphreading-pthread-nsthread-gcd-nsoperation-queuesdiff.png