学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

iOS中的多线程

GCD

使用GCD后,可以不用浪费精力去关注线程,你只需要想清楚任务的执行方法(同步还是异步)和队列的运行方式(串行还是并行)即可。

任务是一个抽象概念,表示用来执行的一段代码,可以是一个block或者一个函数

串行/并行和同步/异步

串行/并行

在使用GCD的时候,会把需要处理的任务放到Block中,然后将任务追加到相应的队列里,这个队列就叫Dispatch Queue。然而,存在于两种Dispatch Queue,一种是要等待上一个执行完,再执行下一个的Serial Dispatch Queue,这叫做串行队列;另一种,则是不需要上一个执行完,就能执行下一个的Concurrent Dispatch Queue,叫做并行队列.这两种,均遵循FIFO原则。

  • 串行队列
    串行队列一次只能执行一个任务,对应一个线程

  • 并行队列
    可以同时执行多个任务,系统会维护一个线程池来保证并行队列的执行。线程池会根据当前任务量自行安排线程的数量,以确保任务尽快执行。

队列对应到代码里就是一个dispatch_queue对象:

1
@property (nonatomic, strong)dispatch_queue_t queue;

同步/异步

dispatch_async表示异步,将指定的Block“异步”加入Dispach Queue,不做任何等待

dispatch_sync表示同步,将指定的Block“同步”的加入Dispatch Queue,在Block结束之前,dispatch_sync会一直等待

串行队列死锁

1
2
3
4
5
6
7
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3

//控制台输出:1

分析:

  1. dispatch_sync表示是一个同步线程;
  2. dispatch_get_main_queue表示运行在主线程中的主队列;
  3. 任务2是同步线程的任务。

首先执行任务1,这是肯定没问题的,只是接下来,程序遇到了同步线程,那么它会进入等待,等待任务2执行完,然后执行任务3。但这是队列,有任务来,当然会将任务加到队尾,然后遵循FIFO原则执行任务。那么,现在任务2就会被加到最后,任务3排在了任务2前面,问题来了:
任务3要等任务2执行完才能执行,任务2由排在任务3后面,意味着任务2要在任务3执行完才能执行,所以他们进入了互相等待的局面。【既然这样,那干脆就卡在这里吧】这就是死锁。

从这里看发生死锁需要2个条件:

  1. 代码运行的当前队列是串行队列
  2. 使用sync将任务加入到自己队列中

队列优先级设置

  • 队列创建
    1
    2
    3
    dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
    //label: 队列的名称,调试的时候可以区分其他的队列
    //attr: 队列的属性,dispatch_queue_attr_t类型。用以标识队列串行,并行,以及优先级等信息

attr三种传值方式

1
2
3
4
5
6
7
8
9
10
// 串行
#define DISPATCH_QUEUE_SERIAL NULL

// 并行
#define DISPATCH_QUEUE_CONCURRENT \
DISPATCH_GLOBAL_OBJECT(dispatch_queue_attr_t, \
_dispatch_queue_attr_concurrent)

// 自定义属性值
dispatch_queue_attr_t dispatch_queue_attr_make_with_qos_class(dispatch_queue_attr_t attr, dispatch_qos_class_t qos_class, int relative_priority);

dispatch_queue_attr_make_with_qos_class函数可以创建带有优先级的dispatch_queue_attr_t对象。通过这个对象可以自定义queue的优先级。

NSOperation 和 NSOperationQueue

NSOperation是基于GCD开发的,但是比GCD拥有更强的可控性和代码可读性,NSOperation是一个抽象基类,表示一个独立的计算单元,可以为子类提供有用且线程安全的建立状态,优先级,依赖,和取消等操作。使用比较多的就是NSInvocationOperation和NSBlockOperation,更多的是定制

NSInvocationOperation

1
2
3
4
5
6
7
    NSInvocationOperation *invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test:) object:nil];
[invo start];
NSLog(@"111");
- (void)test:(NSString *)string {
sleep(3);
NSLog(@"test - %@ - %@", [NSThread currentThread], string);
}
1
2
2017-09-29 14:19:13.242517+0800 aegewgr[10143:3388734] test - <NSThread: 0x600000078180>{number = 1, name = main} - (null)
2017-09-29 14:19:13.242967+0800 aegewgr[10143:3388734] 111

可以看到NSInvocaionOperation是同步并且串行的,主要还是要和NSOperationQueue结合使用

NSBlockOperation

NSBlockOperation支持并发的实行一个或多个block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
NSBlockOperation *blockOperation = [[NSBlockOperation alloc]init];
[blockOperation addExecutionBlock:^{
NSLog(@"block 1 in thread:%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"block 2 in thread:%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"block 3 in thread:%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"block 4 in thread:%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
sleep(1);
NSLog(@"block 5 in thread:%@",[NSThread currentThread]);
}];
[blockOperation start];
NSLog(@"123");
1
2
3
4
5
6
2017-09-29 14:32:03.710936+0800 aegewgr[10335:3439694] block 1 in thread:<NSThread: 0x60400006d740>{number = 1, name = main}
2017-09-29 14:32:03.710939+0800 aegewgr[10335:3439916] block 3 in thread:<NSThread: 0x60400027a780>{number = 4, name = (null)}
2017-09-29 14:32:03.710943+0800 aegewgr[10335:3439920] block 4 in thread:<NSThread: 0x60400027a840>{number = 5, name = (null)}
2017-09-29 14:32:03.710961+0800 aegewgr[10335:3439919] block 2 in thread:<NSThread: 0x600000270c00>{number = 3, name = (null)}
2017-09-29 14:32:04.712532+0800 aegewgr[10335:3439920] block 5 in thread:<NSThread: 0x60400027a840>{number = 5, name = (null)}
2017-09-29 14:32:04.712932+0800 aegewgr[10335:3439694] 123

NSBlockOperation也是同步的,而block的执行是并发的

自定义NSOperation

自定义NSOperaion分两种,一种是自定义非并发的NSOperaion,一种是自定义并发的NSOperaion

定义非并发的NSOperaion

只需要重写main方法

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
#import "SerialNSOperation.h"

@implementation SerialNSOperation

- (void)main
{
NSLog(@"main begin");
@try {
//在这里我们要创建自己的释放池,因为这里我们拿不到主线程的释放池
@autoreleasepool {
// 提供一个变量标识,来表示需要执行的操作是否完成了,当然,没开始执行之前,为NO
BOOL taskIsFinished = NO;
// while 保证:只有当没有执行完成和没有被取消,才执行自定义的相应操作
while (taskIsFinished == NO && [self isCancelled] == NO){
// 自定义的操作
NSLog(@"currentThread = %@", [NSThread currentThread]);
sleep(10); // 模拟耗时操作
// 这里相应的操作都已经完成,后面就是要通知KVO我们的操作完成了。
taskIsFinished = YES;
}
}
}
@catch (NSException * e) {
NSLog(@"Exception %@", e);
}
NSLog(@"main end");
}

然后直接使用

1
2
SerialNSOperation *op = [[SerialNSOperation alloc]init];
[op start];
1
2
3
2017-09-29 15:12:59.151481+0800 aegewgr[10524:3564080] main begin
2017-09-29 15:13:09.152082+0800 aegewgr[10524:3564080] currentThread = <NSThread: 0x60400006e1c0>{number = 1, name = main}
2017-09-29 15:13:09.152299+0800 aegewgr[10524:3564080] main end

实用性不大

定义并发的NSOperation

  1. start方法:该方法必须实现,
  2. main:该方法可选,如果你在start方法中定义了你的任务,则这个方法就可以不实现,但通常为了代码逻辑清晰,通常会在该方法中定义自己的任务
  3. isExecuting isFinished 主要作用是在线程状态改变时,产生适当的KVO通知
  4. isAsynchronous :必须覆盖并返回YES;
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
//.h
#import <Foundation/Foundation.h>

@interface ConcurrentOperation : NSOperation{
BOOL executing;
BOOL finished;
}
//.m
#import "ConcurrentOperation.h"

@implementation ConcurrentOperation
- (id)init {
if(self = [super init])
{
executing = NO;
finished = NO;
}
return self;
}
- (BOOL)isAsynchronous {
return YES;
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isFinished {
return finished;
}
- (void)start {
//第一步就要检测是否被取消了,如果取消了,要实现相应的KVO
if ([self isCancelled]) {
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
//如果没被取消,开始执行任务
[self willChangeValueForKey:@"isExecuting"];
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
executing = YES;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)main {
NSLog(@"main begin");
@try {
@autoreleasepool {
//在这里定义自己的并发任务
NSLog(@"自定义并发操作NSOperation");
NSThread *thread = [NSThread currentThread];
NSLog(@"current Thread:%@",thread);
//任务执行完成后要实现相应的KVO
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
}
@catch (NSException * e) {
NSLog(@"Exception %@", e);
}
NSLog(@"main end");
}
1
2
3
4
5
6
7
8
//调用
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
ConcurrentOperation *op1 = [[ConcurrentOperation alloc]init];
ConcurrentOperation *op2 = [[ConcurrentOperation alloc]init];
ConcurrentOperation *op3 = [[ConcurrentOperation alloc]init];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
1
2
3
4
5
6
7
8
9
10
11
12
2017-09-29 15:34:15.158649+0800 aegewgr[10664:3638228] main begin
2017-09-29 15:34:15.158653+0800 aegewgr[10664:3638226] main begin
2017-09-29 15:34:15.158675+0800 aegewgr[10664:3638227] main begin
2017-09-29 15:34:15.158912+0800 aegewgr[10664:3638228] 自定义并发操作NSOperation
2017-09-29 15:34:15.159321+0800 aegewgr[10664:3638226] 自定义并发操作NSOperation
2017-09-29 15:34:15.159372+0800 aegewgr[10664:3638227] 自定义并发操作NSOperation
2017-09-29 15:34:15.159965+0800 aegewgr[10664:3638226] current Thread:<NSThread: 0x60400046b640>{number = 4, name = (null)}
2017-09-29 15:34:15.160014+0800 aegewgr[10664:3638228] current Thread:<NSThread: 0x60000026d140>{number = 5, name = (null)}
2017-09-29 15:34:15.160103+0800 aegewgr[10664:3638227] current Thread:<NSThread: 0x60400046b5c0>{number = 3, name = (null)}
2017-09-29 15:34:15.160799+0800 aegewgr[10664:3638226] main end
2017-09-29 15:34:15.160973+0800 aegewgr[10664:3638227] main end
2017-09-29 15:34:15.161154+0800 aegewgr[10664:3638228] main end

NSOperationQueue

NSOperationQueue就是执行NSOperation的队列,我们可以将一个或多个NSOperation对象放到队列中去执行。NSOpetaionQueue有两种不同类型的队列:主队列和自定义队列。主队列运行在主线程上,而自定义队列在后台执行。

1
2
3
4
5
6
7
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];  //主队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //自定义队列
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
//任务执行
}];
[queue addOperation:operation];

maxConcurrentOperationCount默认值

maxConcurrentOperationCount 最大并发操作数。用来控制一个特定队列中可以有多少个操作同时参与并发执行

maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。
maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行
maxConcurrentOperationCount大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值}。

GCD与NSOperationQueue有哪些异同?

  1. GCD 是纯 C 语言的 API 。NSOperationQueue 是基于 GCD 的 OC 的封装。
  2. GCD 只支持 FIFO 队列,NSOperationQueue 可以方便设置执行顺序,设置最大的并发数量。
  3. NSOperationQueue 可是方便的设置 operation 之间的依赖关系,GCD 则需要很多代码。
  4. NSOperationQueue 支持 KVO,可以检测 operation 是否正在执行(isExecuted),是否结束(isFinished),是否取消(isCanceled)
  5. NSOperationQueue可以很方便的取消一个NSOperation的执行。

使用场合:

  • 任务之间不太相互依赖:GCD
  • 任务之间有依赖或要监听任务的执行情况:NSOperationQueue

参考

  1. iOS多线程详解