目的掌握线程的概念、线程的调度、线程安全、用户线程与内核线程之间的映射关系等知识,在开发中常用到的就是什么耗时操作开个子线程中处理,处理完回到主线程刷新 UI 啥的,接下来也写几篇文章对 iOS 开发中运用到的多线程技术这一块进行一个梳理总结吧!

线程与进程的概念

进程:不要让 CPU 打盹,解决 CPU 等待问题,有最初的多道程序(Multiprogramming)的方法,到分时系统 (Time-Sharing System)程序协作模式,到目前现代操作系统的多任务(Muti-tasking)系统,其 CPU 的分配方式是抢占式的,所有应用程序都以进程的方式运行在操作系统中,每个进程有自己独立的地址空间相互隔离,每个进程都有机会得到 CPU, 但是操作系统分配给每个 CPU 时间都很短,即 CPU 在多个进程间快速切换,从而给我感觉就是多个进程都在同时运行,当然多核系统,是真具有了同时执行多个任务的能力了;

线程:有时被称为轻量级进程,是程序执行流的最小单元;在每个应程序的内部,存在一个或多个执行线程,它同时或在一个几乎同时发生的方式里执行不同的任务,多线程是一个比较轻量级的方法来实现单个应用程序内多个代码执行路径,系统本身管理这些执行的线程,调度它们在可用的内核上运行,并在需要让其他线程执行的时候抢先打断它们。内核级结构协助调度线程事件,并抢占式调度一个线程到可用的内核之上。应用级结构包括用于存储函数调用的调用堆栈和应用程序需要管理和操作线程属性和状态的结构。

iOS 开发中的多线程

在并发的应用程序中,系统只有一个主线程,该线程开始和结束于你应用程序的 main 循环。当然为了线程的性能我们可以创建子线程,这样可以提高应用程序的感知响应和提高应用程序在多核系统上的实时性能;其中主线程是保活的,原因是应用程序一启动 main 函数中开启的主线程添加了 run loop 的对象引用,而我们子线程是不具备长时间运行的,子线程是异步销毁的特点,如果需要保活需要添加到运行循环 run loop 中,iOS 中提供的线程技术,他们只是线程对象只是 OC 对象,并不是真的执行任务的线程,更不能代表线程,线程常驻的概念并不是线程对象保活和持有,真正的多线程(线程池)管理是由 CPU调度(进程调度) 决定的,如果thread 对象我们持有了,线程池中也许已经没有这个线程了;所有线程只要启动之后,线程就进入三个状态中的任何一个:运行(running)、就绪(ready)、阻塞(blocked)。如果一个线程当前没有运行,那么它不是处于阻塞,就是等待外部输入,或者已经准备就绪等待分配 CPU。线程持续在这三个状态之间切换,直到它最终退出或者进入中断状态。

iOS 中的多线程技术有四种:pthread,NSthread,GCD,NSOperation;

  • pthread:是纯 C 语言的,是一套通用的 API 跨平台和可移植,后面三种技术都是依赖于pthread,适用于 Unix\Linux\Windows 等系统,线程生命周期需要手动管理。
  • NSThread:OC 语言的面向对象的可以直接操作线程对象,线程生命周期需要手动管理,创建启动线程和销毁线程需要我们自己控制;
  • GCD:C 语言的,为的是替代 NSThread 的线程技术,充分利用了系统多核能力,线程生命周期自动管理的;
  • NSOperation:基于 GCD,也是面向对象的,对比 GCD 丰富了一些操作线程的功能,线程生命周期自动管理的;

phread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)testPthread {
// 创建线程——定义一个 pthread_t 类型变量 Thread ID
pthread_t thread;


// 开启线程——执行任务
/*
第一个参数 &thread 是线程对象
第二个和第四个是线程属性,可赋值NULL
第三个run表示指向函数的指针(run对应函数里是需要在新线程中执行的任务)
*/
pthread_create(&thread, NULL, pthreadStart, NULL);
}

void *pthreadStart (void *data) {
NSLog(@"pthreadStart-----%@",[NSThread currentThread]);
return NULL;
}

NSThread

NSThread 苹果基于 phread 封装的线程对象,面向对象易用;线程状态:创建(New)-> 进入就绪状态(Runnable)-> 运行状态(Running)。当线程任务执行完毕,自动销毁线程。中间可能会有:强制停止线程、阻塞(Blocked)线程;

创建线程的方法

1
2
3
4
5
6
7
8
9
// NSThread 创建线程方式一 需调用 start 方法启动
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(NSthreadMethod) object:nil];
[thread start];

// NSThread 创建线程方式二 自动启动线程
[NSThread detachNewThreadSelector:@selector(NSthreadMethod) toTarget:self withObject:nil];

// NSThread 创建线程方式三 NSObject 类的扩展方法 自动启动线程 (线程运行任务可以在后台执行)
[self performSelectorInBackground:@selector(NSthreadMethod) withObject:nil];

阻塞线程

1
2
3
4
5
// 休眠到指定日期
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3.0]];

// 休眠指定时长
[NSThread sleepForTimeInterval:2.0];

线程间通信

1
2
3
4
5
6
7
8
9
// 线程间通信方式一
[self performSelectorOnMainThread:@selector(backToMainThread:) withObject:@"回到主线程刷新UI方式一" waitUntilDone:NO];
// 线程间通信方式二
[self performSelector:@selector(backToMainThread:) onThread:[NSThread mainThread] withObject:@"回到主线程刷新UI方式二" waitUntilDone:YES];

- (void)backToMainThread:(NSString *)str {

NSLog(@"%@ \n %@",str,[NSThread currentThread]);
}

其它相关方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 获得当前线程
+ (NSThread *)currentThread;

// 获得主线程
+ (NSThread *)mainThread;

// 判断是否为主线程(实例方法)
- (BOOL)isMainThread;

// 判断是否为主线程(类方法)
+ (BOOL)isMainThread;

// 线程的名字(getter方法)
- (NSString *)name;

// 取消线程 并不会立刻取消线程,仅仅是将cancelled属性设置为YES,最终线程的控制还是内核操作线程池
- (void)cancel;

// 退出线程 调用之后会立即退出线程,即使任务还没有执行完成也会中断,慎用
+ (void)exit;