iOS多线程

iOS多线程

pthread

POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。是一套C语言写的线程库.

先要包含其头文件

1
#import <pthread.h>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)viewDidLoad {
[super viewDidLoad];
[self createPThread];
}
- (void)createPThread{
pthread_t pthreadID;
NSString *param = @"piaojin";
//pthreadID线程的标识符
//cFunc要调用的方法(C语言的方法)
//param传递的参数
int result = pthread_create(&pthreadID, NULL, &cFunc, (__bridge void *)(param));
printf("%d",result);
}
void * cFunc(void *param){
NSLog(@"%@,param:%@",[NSThread currentThread],param);
return NULL;
}

看代码就会发现他需要 c语言函数,这是比较蛋疼的,更蛋疼的是你需要手动处理线程的各个状态的转换即管理生命周期,比如,这段代码虽然创建了一个线程,但并没有销毁。

NSThread

这套方案是经过苹果封装后的,并且完全面向对象的。所以你可以直接操控线程对象,非常直观和方便。但是,它的生命周期还是需要我们手动管理,所以这套方案也是偶尔用用,比如 [NSThread currentThread],它可以获取当前线程类,你就可以知道当前线程的各种属性,用于调试十分方便。下面来看看它的一些用法。

创建并启动

先创建线程类,再启动

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
31
32
33
34
35
36
37
38
// 创建
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
// 启动
[thread start];
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
//使用 NSObject 的方法创建并自动启动
[self performSelectorInBackground:@selector(run:) withObject:nil];
//除了创建启动外,NSThread 还以很多方法,下面我列举一些常见的方法,当然我列举的并不完整,更多方法大家可以去类的定义里去看。
//取消线程
- (void)cancel;
//启动线程
- (void)start;
//判断某个线程的状态的属性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;
//设置和获取线程名字
-(void)setName:(NSString *)n;
-(NSString *)name;
//获取当前线程信息
+ (NSThread *)currentThread;
//获取主线程信息
+ (NSThread *)mainThread;
//使当前线程暂停一段时间,或者暂停到某个时刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;

GCD

Grand Central Dispatch,听名字就霸气。它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉干什么就行。同时它使用的也是 c语言,不过由于使用了 Block(Swift里叫做闭包),使得使用起来更加方便,而且灵活。所以基本上大家都使用 GCD 这套方案,老少咸宜,实在是居家旅行、杀人灭口,必备良药。不好意思,有点中二,咱们继续。

任务和队列

在 GCD 中,加入了两个非常重要的概念: 任务 和 队列。

任务:即操作,你想要干什么,说白了就是一段代码,在 GCD 中就是一个 Block,所以添加任务十分方便。任务有两种执行方式: 同步执行 和 异步执行,他们之间的区别是 是否会创建新的线程。

同步(sync)和异步(async)的主要区别在于会不会阻塞当线程,直到Block中的任务执行完毕!如果是 同步(sync)操作,它会塞当前线程并等待Block中的任务执行完毕,然后当前线程才会继续往运行。如果是异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。

队列:用于存放任务。一共有两种队列, 串行队列 和 并行队列。

放到串行队列的任务,GCD 会 FIFO(先进先出) 地取出来一个,执行一个,然后取下一个,这样一个一个的执行。

放到并行队列的任务,GCD 也会 FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//同步任务 + 并发队列(任务还是以同步的形式,一个一个完成),没有开启新线程
- (void)sync{
//创建并发队列DISPATCH_QUEUE_CONCURRENT,默认是同步队列DISPATCH_QUEUE_SERIAL,DISPATCH_QUEUE_PRIORITY_DEFAULT全局队列
dispatch_queue_t queue = dispatch_queue_create("同步任务 + 并发队列", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
for(int i = 0;i < 5;i++){
NSLog(@"currentThread:%@,->1<-%d",[NSThread currentThread],i);
}
});
dispatch_sync(queue, ^{
for(int i = 0;i < 5;i++){
NSLog(@"currentThread:%@,->2<-%d",[NSThread currentThread],i);
}
});
dispatch_sync(queue, ^{
for(int i = 0;i < 5;i++){
NSLog(@"currentThread:%@,->3<-%d",[NSThread currentThread],i);
}
});
}
//异步任务 + 串行队列(任务还是以同步的形式,一个一个完成),开启了新线程
- (void)async{
//创建并发队列DISPATCH_QUEUE_CONCURRENT,默认是同步队列DISPATCH_QUEUE_SERIAL,DISPATCH_QUEUE_PRIORITY_DEFAULT全局队列
dispatch_queue_t queue = dispatch_queue_create("异步任务 + 串行队列", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
for(int i = 0;i < 5;i++){
NSLog(@"currentThread:%@,->1<-%d",[NSThread currentThread],i);
}
});
dispatch_async(queue, ^{
for(int i = 0;i < 5;i++){
NSLog(@"currentThread:%@,->2<-%d",[NSThread currentThread],i);
}
});
dispatch_async(queue, ^{
for(int i = 0;i < 5;i++){
NSLog(@"currentThread:%@,->3<-%d",[NSThread currentThread],i);
}
});
}
#define ImageUrl @"http://upload-images.jianshu.io/upload_images/3362328-bdcbda41eac5e8a2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"
//线程间通讯,NSThread利用PerformSelector系列方法
- (void)communication{
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//耗时操作,此处为从网络获取一直图片
//耗时的操作
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:ImageUrl]];
UIImage *image = [UIImage imageWithData:imageData];
//把image传递给主线程(线程间的通讯)
dispatch_async(dispatch_get_main_queue(), ^{
//到主线程更新UI
weakSelf.imageView.image = image;
});
});
}
//延时
- (void)delay{
//NSObject的延时方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
//延时两秒
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self run];
});
//利用定时器也可以达到延时执行的效果,这时候repeats要设置为NO,只执行一次,用scheduledTimerWithTimeInterval方法创建的定时器会默认添加到当前的RunLoop中去,并且启动
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
}
- (void)run{
NSLog(@"延时两秒");
}
//一次性操作
- (void)once{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"一次性操作");
});
}
//多次操作
- (void)apply{
/**
*size_t iterations执行的次数
**/
dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
//index每次遍历的下标,注意并不是按顺序的
NSLog(@"%zu",index);
});
}
//队列组操作
- (void)group{
dispatch_group_t group = dispatch_group_create();
//异步的组操作
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 100; i++) {
NSLog(@"queue1:%d",i);
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 100; i++) {
NSLog(@"queue2:%d",i);
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"queue3");
});
//前面创建了三个任务,这边三个任务完成时回执行dispatch_group_notify中的block代码
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"三个任务都完成了!");
});
}

NSOperation和NSOperationQueue

NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列

NSOperation是一个抽象的类,并不具备封装操作的能力,必须使用它的子类:

  • NSInvocationOperation

  • NSBlockOperation

  • 自定义子类继承NSOperation是一个抽象的类,重写内部相应的方法(main方法)

操作步骤也很好理解:

  • 先将需要执行的操作封装到一个NSOperation对象中

  • 将NSOperation对象添加到NSOperationQueue中

  • 系统会自动将NSOperationQueue中的NSOperation取出来

  • 将取出来的NSOperation封装的操作放到一条线程中去执行

    NSOperationQueue的作用

如果将NSOperation添加到NSOperationQueue中,系统会默认异步执行NSOperation的操作

添加NSOperation到NSOperationQueue中

  • 调用addOperation:的方法

  • 调用addOperationWithBlock:方法

1
2
3
4
5
6
7
8
9
//自定义继承NSOperation
@implementation PJCustomOperation
//需要重写main方法,任务是在main方法中执行的
- (void)main{
NSLog(@"PJCustomOperation:%@",[NSThread currentThread]);
}
@end

没有添加到NSOperationQueue的情况

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
31
32
33
//NSInvocationOperation
- (void)invocationOperation{
//默认在主线程中执行,并且不会创建新的线程number = 1(主线程),相单与[self performSelector:@selector(run) withObject:nil];,只有放到NSOperationQueue才会开启新线程,异步执行
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//开启
[invocationOperation start];
}
//NSBlockOperation
- (void)blockOperation{
//默认在主线程中执行,并且不会创建新的线程number = 1(主线程),相单与[self performSelector:@selector(run) withObject:nil];
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation1%@",[NSThread currentThread]);
}];
//添加额外任务会开启新线程,number != 1,在新线程中完成
[blockOperation addExecutionBlock:^{
NSLog(@"blockOperation2%@",[NSThread currentThread]);
}];
[blockOperation start];
}
//自定义NSOperation,需要重写main方法,任务是在main方法中执行的
- (void)customOperation{
//此时是在主线程中执行,并没有开启新线程
PJCustomOperation *customOperation = [[PJCustomOperation alloc] init];
[customOperation start];
}
- (void)run{
NSLog(@"执行任务%@",[NSThread currentThread]);
}

添加到NSOperationQueue的情况

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
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//创建队列,默认创建的是并行队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//GCD的队列形式
//全局 主队列 串行队列 并行队列
//NSOperationQueue队列形式
//主队列 自己创建的队列
//创建NSOperation
NSInvocationOperation *invocationOperation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"invocationOperation1"];
//放入队列中,就会执行不用显示的去[invocationOperation start],会创建一个新线程,异步执行
[queue addOperation:invocationOperation1];
NSInvocationOperation *invocationOperation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"invocationOperation2"];
//放入队列中,就会执行不用显示的去[invocationOperation start],会创建一个新线程,异步执行
[queue addOperation:invocationOperation2];
//会创建一个新线程,异步执行
[queue addOperationWithBlock:^{
NSLog(@"addOperationWithBlock的形式:thread:%@",[NSThread currentThread]);
}];
}
- (void)run:(NSString *)name{
NSLog(@"name:%@,thread:%@",name,[NSThread currentThread]);
}

NSOperation通信与依赖关系

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
31
32
33
34
35
36
37
//利用NSOperationQueue进行现场间通讯
- (void)queueMessage{
//利用NSOperation和NSOperationQueue来执行
__weak typeof(self) weakSelf = self;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
//耗时的操作
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:ImageUrl]];
//耗时操作执行完后会到主队列执行UI更新操作
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
weakSelf.imageView.image = [UIImage imageWithData:imageData];
}];
}];
}
//依赖 如果A依赖与B,则A等到B完成了才执行
- (void)dependency{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation1:%@",[NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation2:%@",[NSThread currentThread]);
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation3:%@",[NSThread currentThread]);
}];
//添加依赖 前者(operation1)依赖后者(operation2),后者(operation2)先执行,在执行前者(operation1)
//任务之间不能造成依赖循环 A -> B,B -> C,C -> A
[operation1 addDependency:operation2];
[queue addOperations:@[operation1,operation2,operation3] waitUntilFinished:NO];
}

NSOperation并发数与挂起

最大并发数

若是开启线程较多,会增大CPU工作量,从而降低了每个线程被调用的次数.为了合理的提高CPU的工作效率,我们可以设置队列最大并发数.

  • 可以通过为maxConcurrentOperationCount属性赋值即可,maxConcurrentOperationCount的默认值为-1,即不限并发数量.当其设置后队列变成串行队列.

1
2
3
4
5
@interface ViewController ()
@property (nonatomic, strong)NSOperationQueue *queue;
@end
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
31
//最大并发数量
- (void)maxConcurrentOperationsCount{
_queue = [[NSOperationQueue alloc] init];
//设置为2后表示每次只能并发执行2个任务,变成了串行执行,如果设置为1就是每次只能并发执行一个任务
//下面的执行就是1和2先并发执行,然后3和4并发执行,入股偶设置为1则按顺序1 2 3 4一个一个执行
_queue.maxConcurrentOperationCount = 2;
[_queue addOperationWithBlock:^{
for (int i = 0; i < 5000; i++) {
NSLog(@"--->1<---%@",[NSThread currentThread]);
}
}];
[_queue addOperationWithBlock:^{
for (int i = 0; i < 5000; i++) {
NSLog(@"--->2<---%@",[NSThread currentThread]);
}
}];
[_queue addOperationWithBlock:^{
for (int i = 0; i < 5000; i++) {
NSLog(@"--->3<---%@",[NSThread currentThread]);
}
}];
[_queue addOperationWithBlock:^{
for (int i = 0; i < 5000; i++) {
NSLog(@"--->4<---%@",[NSThread currentThread]);
}
}];
}

队列的挂起与取消

挂起

suspended:将其赋值为YES即挂起,赋值为NO即恢复

  • 当线程处于执行状态,设置队列挂起时不会影响其执行,受影响的是那些还没有执行的任务

  • 当队列设置为挂起状态后,可以修改其状态再次恢复任务

    取消

    设置取消的方法是cancelAllOperations,当取消任务后不可以恢复

    若任务操作时自定义的NSOperation类型的话,执行完一个耗时操作后需要加一是否取消任务的判断,再去执行另外一个耗时操作.同样取消不影响当前正在执行的线程,后面的线程会被取消.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//挂起与取消
- (void)suspendedAndCancel{
//挂起 当前正在执行的任务不受影响,后面还未执行的任务会被挂起,后面的任务可以被恢复
// [self.queue setSuspended:!self.queue.suspended];
//取消 当前所有任务会被取消,并且不可恢复
[self.queue cancelAllOperations];
}
- (void)customOperation{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
PJCustomOperation *customOperation1 = [[PJCustomOperation alloc] init];
[queue addOperation:customOperation1];
self.queue = queue;
}
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
31
32
33
34
35
36
37
38
39
40
41
@implementation PJCustomOperation
- (void)main{
//如果任务任务被取消了就不往下执行,不做这个判断的话,当队列被取消是当前的线程不受影响,故如果该线程有多个耗时操作会继续进行下去
if(self.isCancelled){
return;
}
//模拟在同一个线程里面做多个耗时操作
for (int i = 0; i < 50000; i++) {
NSLog(@"--->PJCustomOperation1<---%@",[NSThread currentThread]);
}
//如果任务任务被取消了就不往下执行,不做这个判断的话,当队列被取消是当前的线程不受影响,故如果该线程有多个耗时操作会继续进行下去
if(self.isCancelled){
return;
}
for (int i = 0; i < 50000; i++) {
NSLog(@"--->PJCustomOperation2<---%@",[NSThread currentThread]);
}
//如果任务任务被取消了就不往下执行,不做这个判断的话,当队列被取消是当前的线程不受影响,故如果该线程有多个耗时操作会继续进行下去
if(self.isCancelled){
return;
}
for (int i = 0; i < 50000; i++) {
NSLog(@"--->PJCustomOperation3<---%@",[NSThread currentThread]);
}
//如果任务任务被取消了就不往下执行,不做这个判断的话,当队列被取消是当前的线程不受影响,故如果该线程有多个耗时操作会继续进行下去
if(self.isCancelled){
return;
}
for (int i = 0; i < 50000; i++) {
NSLog(@"--->PJCustomOperation4<---%@",[NSThread currentThread]);
}
}
------本文结束😘感谢阅读------