NSThread

1、进程 & 线程

#定义与划分尺度:

  • 程序的一次执行过程就是进程,它是系统进行资源分配和调度的一个独立单位。
  • 线程是进程的一个实体,是进程内的一个执行单元,一个进程至少有一个线程。

#地址空间:

  • 进程有自己独立的虚拟地址空间,而线程共享进程的地址空间。

#拥有的资源:

  • 进程是资源分配和拥有的单位;同一个进程内的线程共享进程资源。

#执行单位:

  • 线程是CPU调度和分派的基本单位,而进程不是。

#并发:

  • 二者均可并发执行(打开多个应用为进程并发;多线程同时执行下载任务为线程并发)。

2、并发 & 并行

1、场景:

并发,一个处理器同时处理多个任务。

并行,多个处理器或多核的处理器同时处理多个不同的任务。

2、概念区分:

并发(Concurrency):在同一时刻只能有一条指令执行,但多个指令被快速的轮换执行,使得在宏观上具有多个线程同时执行的效果,但在微观上并不是同时执行的。

并行(Parallel):在同一时刻,有多条指令在多个处理器上同时执行,无论从微观还是从宏观来看,二者都是一起执行的。

3、多线程

为什么要有多线程?

iOS应用中主线程用来处理界面更新、响应用户触摸事件等。在主线程中执行大量耗时操作会造成主线程阻塞,进而出现卡顿现象以致影响使用和用户体验。故需要将这种耗时操作放到其他的线程中执行。所以多线程编程是防止主线程堵塞,增加运行效率的最佳方法。

为什么不无限制的开辟N条新线程呢?

  • 首先,线程的创建需要开销,占用一定得内存空间;
  • 其次,有N条线程时处理器需要在多条线程之间频繁调度,这也需要大量开销;
  • 最后,N条线程时还需要考虑到这些线程之间的通信和资源共享等问题。

有哪些多线程解决方案?

iOS支持多个层次的多线程编程,层次越高的抽象程度越高,使用也越方便,也是苹果最推荐使用的方法。根据抽象程度由低到高列出如下:

  • NSThread :

是三种方法里面相对轻量级的,需要自己管理线程的生命周期、同步、加锁问题,这会导致一定的性能开销。

  • NSOperation:

是基于OC实现的,它以面向对象的方式封装了需要执行的操作,不必关心线程管理、同步等问题。NSOperation 是一个抽象基类,iOS提供了两种默认实现:NSInvocationOperation 和 NSBlockOperation,也可以自定义 NSOperation。

  • Grand Central Dispatch(简称GCD,iOS4才开始支持):

提供了一些新特性、运行库来支持多核并行编程,它的关注点更高:如何在多个CPU上提升效率。

4、NSThread

4.1.生命周期

#创建

  • 实例方法:创建后需要手动调用start函数来启动线程;
1
2
3
4
5
- (instancetype)initWithTarget:(id)target 
selector:(SEL)selector
object:(id)argument;

- (instancetype)initWithBlock:(void (^)(void))block ;

第一种实例化方法中,参数selector会在刚创建的线程对象执行start方法之后被调用,且是在当前新线程中调用。

  • 类方法:自动启动线程,无需手动调用start函数。
1
2
3
4
5
+ (void)detachNewThreadWithBlock:(void (^)(void))block ;

+ (void)detachNewThreadSelector:(SEL)selector
toTarget:(id)target
withObject:(id)argument;
  • 分类创建:NSObject(NSThreadPerformAdditions)
1
2
- (void)performSelectorInBackground:(SEL)aSelector 
withObject:(id)arg;

#就绪

  • 将线程放进可调度线程池,等待被CPU调度:
1
[threadObj start]//注意:同一个线程对象不能连续调用start方法!

#运行

  • CPU负责调度线程池中处于”就绪状态”的线程。

#阻塞

  • 正在运行的线程,可以用休眠或者锁来阻塞线程的执行。
1
2
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
  • 互斥锁
1
@synchronized(self)

#死亡

一般情况下(不考虑引用、runloop),线程执行完任务后会自动销毁。也可以在满足某条件后调用exit方法,强制线程退出。

1
[NSThread exit];

4.2.线程间通信

  • 指定当前线程执行操作
1
2
3
[self performSelector:@selector(run)];
[self performSelector:@selector(run) withObject:nil];
[self performSelector:@selector(run) withObject:nil afterDelay:5.0];
  • 在主线程指定其他线程执行操作
1
2
[self performSelector:@selector(run) onThread:newThread withObject:nil waitUntilDone:YES]; 
[self performSelectorInBackground:@selector(run) withObject:nil];
  • 在其他线程中指定主线程执行操作
1
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];

4.3.线程同步

多个线程同时读写同一份共享资源时,可能会引起冲突。线程同步是指在一定时间内只允许某一个线程访问某个资源。OC 中实现线程加锁有 NSLock 和 @synchronized 等方式。

  • NSLock 创建锁对象、加锁和解锁:
1
2
3
4
5
6
7
NSLock * lock = [[NSLock alloc]init];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
// do work
[lock unlock];
});
  • @synchronized()指令对一段代码进行加锁。它需要一个参数,该参数可以是任何的OC对象,包括self。这个对象就是互斥信号量。
1
2
3
@synchronized(self)  {
// do work
}

5、线程保活

默认情况下,子线程在执行完任务之后,会自动销毁。如果想复用此线程或者继续在此线程上执行其他任务,则需要让此线程一直活着而不被销毁;

思路:给子线程所在runloop添加事件源,如端口或自定义source,保证此runloop不退出。需要停止子线程时,停止线程所在runloop即可~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 自定义线程
@interface HelThread : NSThread
@end


// 线程生命周期管理类
@interface HelThreadHelper : NSObject

/// 线程初始化
/// @param name 线程名
-(instancetype)initWithName:(NSString*)name;

/// 开启线程
- (void)start;

/// 停止当前线程
- (void)stop;

/// 获取线程
-(NSThread *)getThread;

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

//MAKR: -HelThread
@implementation HelThread

- (void)dealloc{
NSLog(@"++++THREAD IS DEALLOCED~");
}
@end


//MAKR: -HelThreadHelper

@interface HelThreadHelper()
@property (nonatomic, strong) HelThread *mThread;
@property (nonatomic, strong) NSCondition *mConditionLock;
@end

@implementation HelThreadHelper

//MARK: -APIs
-(instancetype)initWithName:(NSString*)name{
self = [super init];
if (self) {
_mConditionLock = [[NSCondition alloc] init];
_mThread = [[HelThread alloc] initWithTarget:self selector:@selector(onThreadInit:) object:nil];
_mThread.name = name;
}
return self;
}

- (void)start{
NSLog(@"+++call Start:%@", [NSThread currentThread]);
[_mConditionLock lock];
[_mThread start];
[_mConditionLock wait]; // 防止多线程环境下onThreadInit中尚未设置runloop就开始在_mThread上执行任务的情况
NSLog(@"++runloop Set finised~");
[_mConditionLock unlock];
}

- (void)stop{
// 回到所在线程 停止其runloop
[self performSelector:@selector(finish) onThread:_mThread withObject:nil waitUntilDone:NO];
}

-(NSThread *)getThread{
return _mThread;
}

//MARK: -Self Business
- (void)onThreadInit:(id)obj{

NSLog(@"+++runloop Start:%@", [NSThread currentThread]);

// 线程保活
CFRunLoopSourceContext context = {0};
context.perform = DoNothingRunLoopCallback;

CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &context);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);

[_mConditionLock lock];
[_mConditionLock signal]; // 线程创建和设置已完成,告诉调用者可以在_mThread上执行任务了
[_mConditionLock unlock];

// 开启runloop,开始处理任务
CFRunLoopRun(); // 开启循环,在被停止前会一直运行在这一行,不执行后面一行代码

// runloop已被停止 执行清理任务
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
CFRelease(source);

NSLog(@"+++runloop stop:%@", [NSThread currentThread]);
}

- (void)finish{
CFRunLoopStop(CFRunLoopGetCurrent()); // 结束runloop
}

static void DoNothingRunLoopCallback(void *info){
NSLog(@"+++runloop 回调~");
}

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

@interface ViewController ()
@property (nonatomic, strong) HelThreadHelper *mHelper;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
_mHelper = [[HelThreadHelper alloc] initWithName:@"com.Hel.MyThread"];
[_mHelper start];
}

// 触发任务
- (IBAction)onClick:(id)sender {
[self performSelector:@selector(onSel:)
onThread:[_mHelper getThread]
withObject:nil waitUntilDone:NO];
}

// 结束线程
- (IBAction)onStop:(id)sender {
[_mHelper stop];
_mHelper = nil; // _mHelper置为nil后,其中被强引用的_mThread线程才会销毁
}

- (void)onSel:(id)obj{
NSLog(@"+++call onSel: THREAD:%@",[NSThread currentThread]);
}

分别点击开始和结束按钮,执行任务和结束线程:

1
2
3
4
5
6
+++call Start:<NSThread: 0x600003000040>{number = 1, name = main}
+++runloop Start:<HelThread: 0x6000030602c0>{number = 5, name = com.Hel.MyThread}
++runloop Set finised~
+++call onSel: THREAD:<HelThread: 0x6000030602c0>{number = 5, name = com.Hel.MyThread}
+++runloop stop:<HelThread: 0x6000030602c0>{number = 5, name = com.Hel.MyThread}
++++THREAD IS DEALLOCED~

NSThread
https://davidlii.cn/2017/11/07/nsthread.html
作者
Davidli
发布于
2017年11月7日
许可协议