本文为L_Ares个人写作,以任何形式转载请表明原文出处。
本节介绍线程的一些基本知识,为了后续开启和iOS
相关的线程内容。个人建议要深入学习线程的话,可以看一下《posix多线程程序设计》。本章节只是介绍我认为需要介绍的基本知识,包括后续章节主要都是和iOS
开发相关的线程探索。
下面有一份苹果官方的线程文档,毕竟苹果也是要面子的人,线程这么重要的东西,肯定会有自己的官方文档的,你会发现,苹果的线程实现和pthread
息息相关,但是个人不建议直接操控pthread
,因为操控底层一般都是很"危险"的事情。
资料 : 苹果官方关于线程的文档。
一、线程基础
1. 什么是进程
进程的定义分为广义和狭义两种。
- 广义 : 进程是一个具有一定独立功能的程序关于某个数据集合的一次活动,是操作系统动态执行的基本单元,即是基本分配单元,又是基本执行单元。
- 狭义 :进程是一个正在运行的程序实例。
说直白一点,仅以iOS
的App
为例,因为iOS
是不支持多进程的,每个进程之间都是相互独立的,每个进程均在其专用的、受保护的内存空间上运行,拥有独立运行所需要的全部资源,所以你可以直接用进程的狭义定义理解,也就是 :
仅针对
iOS
来说,进程可以理解为一个正在运行
的App
。
2. 什么是线程
在计算机中,线程是一种能够实现某种功能的基本软件单元。而在iOS
中,因为没有多进程App,我们可以具体化这句话 :
线程是进程的基本执行单元。是程序执行流的最小单元,一个进程的所有任务都在线程中执行。
3. iOS中的线程
(1).在
iOS
中,进程想要执行任务,必须至少拥有一条线程。
(2).iOS
程序(进程)启动的时候,会默认开启一条线程,这就是我们常说的主线程
。也有人叫它UI线程
,负责处理UI事件
,包括显示和刷新。
4. 线程和进程的关联与区别
- 地址空间 : 同一个进程的
所有线程共享
本进程的地址空间
。- 资源分配 : 同一个进程内的
所有线程共享
本进程的资源
,例如:内存、I/O
、CPU
等。- 基本单位 : 在
iOS
中,线程
才是处理器调度的基本单位
。
5. 多线程
5.1 为什么要多线程?
为了在执行任务的时候不延误其他任务的执行。加快任务的处理效率。
5.2 多线程的原理?
对于
单核CPU
来说,同一时间,CPU
只能处理一个线程的任务,所以多线程的同时执行,其实只是单核CPU
在单位时间里,多条线程之间快速的切换调度,造成了单核CPU
多线程并行的假象。也有人叫它时间片轮转。
6. 多线程优缺点
这里就要知道在iOS
中虽然多进程是不行的,但是多线程是非常普遍的。所以说优缺点的话,我们主要说的是多线程
的优缺点。
优点 :
- 可以适当的提高程序的执行效率。
- 可以适当的提高资源的利用率,比如
CPU
、内存利用率
。- 线程上的任务执行完成后,线程是可以自动销毁的。
缺点 :
- 线程不够"健壮"。
例如 : 对比多进程,进程crash后,会有保护模式,不会影响其他的进程。但是多线程如果crash了一个线程,整个进程都会崩溃。- 开线程会占用一定的内存空间。
例如 : 一般情况下,iOS
系统中,主线程占有1M
的栈区内存,其他的二级线程占有512K
的栈区内存。- 线程越多,
CPU
在调用线程的时候的性能开销越大。
原因 :CPU
在线程间切换是需要资源的,要调用的线程越多,切换越频繁,每条线程被调用的频次越低,线程里面的任务执行的就越差。想要保证执行效率,就要加大资源的开销,让人感官上感受不到执行效率的变差。- 程序的设计更复杂。
原因 : 多线程间的通信,多线程的数据共享。
7. 线程的生命周期
(1).
新建
: 首先,你要有一个线程,新建可以理解为创建一个线程的实例对象。(2).
就绪
: 线程是能够运行的状态,并且被加入了可调度线程池
,但是在等待CPU
调度。可能因为刚刚启动、刚从阻塞中恢复、或者可能被其他线程抢占。(3).
运行
: 线程正在执行任务,也就是被CPU
调度了,在线程执行完任务之前,线程的状态可能在就绪
和运行
间发生多次切换,这个切换由CPU
决定,不由我们管理。(4).
阻塞
: 处于一种无法执行任务的状态。比如调用了sleep
、等待同步锁(@synchronized
)、从可调度线程池移出。(5).
销毁
: 任务执行完毕,就可以退出了。也可以是满足某个条件后,在线程的内部或者主线程中手动终止线程的执行,从而退出线程。
线程绝大多数时间都处于它生命周期中的三个状态 : 就绪
、运行
、阻塞
。
另外,在销毁
的时候,有cancel
和exit
两种常见的方法,它们也是有区别的 :
exit
: 强行终止线程。后续的所有代码都不会执行。cancel
: 不可以强行终止正在执行的线程。终止的也只是当前的线程。
来张图,看的清楚点,看看线程在其生命周期中都经历了什么。
图1.7.0.png8. 线程池
这个就顾名思义了,线程池,就是拿来装线程的,一般情况下,以这种池
作为概念的设计,都会存在三种容量大小 : 最大容量
、核心容量
、当前容量
。
下面也是直接上图吧,文字表述容易乱掉,这个图就可以理解线程池的一个原理。
图1.8.0.png四个缓存策略 :
Abort策略
: 这是饱和策略的默认策略
。当线程池的大小已经和核心线程池大小一样大,并且工作队列已经饱和,工作队列中的线程也都在工作,那么当新的任务提交到线程的时候,直接抛出未检查异常
,也就是RejectedExecutionExeception
,该异常可由调用者捕获。CallerRuns策略
: 这是饱和策略的调节策略,即不放弃任务也不抛出异常,而是将某些任务回退到调用者。
它不会在线程池的线程中执行新的任务,而是在exector
的线程中运行新的任务。Discard策略
: 这是饱和策略中很直接的策略,直接抛弃新提交的任务。DiscardOldest策略
: 抛弃最长时间都没执行的任务,也就是队列头上的任务,然后尝试提交新任务。(不适合工作队列为优先队列的场景)。
二、iOS中的多线程
在iOS
开发中,多线程有4种大家常见的实现方案,其中以GCD
最受官方推荐,以pthread
使用最少,原因很简单,pthread
是更底层的多线程实现方案,虽然可以有更多的自主性,但是容易引发问题。
1. 多线程方案
方案 | 简介 | 语言 | 线程生命周期 | 使用频率 |
---|---|---|---|---|
pthread | 1. 一套通用的多线程API ; 2. 适用于 Unix 、Linux 、Windows 等系统; 3. 具有跨平台性、可移植性; 4. 使用难度较大。 |
C | 程序员管理 | 极少使用 |
NSThread | 1. 面向对象 2. 比 pthread 简单,直接操作线程对象 |
OC | 程序员管理 | 较少使用 |
GCD | 1. 苹果推荐,替代NSThread 2. 充分利用设备的多核 |
C | 自动管理 | 经常使用 |
NSOperation | 1. 基于GCD 实现 2. 比 GCD 多一些简单实用的功能 3. 更加的面向对象 |
OC | 自动管理 | 经常使用 |
2. 多线程方案举例
pthread
要#import <pthread.h>
#pragma mark - pthread
- (void)jd_pthread
{
//定义一个线程标识符,程序中使用线程标识符来表示线程
pthread_t thread;
/**
创建线程 :
int pthread_create(pthread_t _Nullable * _Nonnull __restrict,
const pthread_attr_t * _Nullable __restrict,
void * _Nullable (* _Nonnull)(void * _Nullable),
void * _Nullable __restrict);
参数 :
1. pthread_t *restrict : 线程变量的指针
2. const pthread_attr_t *restrict : 线程的属性
3. void* (*)(void*) : 线程中要执行的函数的起始地址
4. void *restrict : 线程中要执行的函数的参数
*/
pthread_create(&thread, NULL, jd_pthread_test, NULL);
pthread_detach(thread);
}
void *jd_pthread_test(void *param)
{
NSLog(@"jd_pthread_test : %@ --- %@", [NSThread currentThread],[NSThread mainThread]);
return NULL;
}
#pragma mark - NSThread
- (void)jd_nsthread
{
[NSThread detachNewThreadSelector:@selector(thread_use_method:) toTarget:self withObject:@"jd_nsthread"];
}
#pragma mark - GCD
- (void)jd_gcd
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self thread_use_method:@"jd_gcd"];
});
}
#pragma mark - NSOperation
- (void)jd_nsOperation
{
NSOperationQueue *opqueue = [[NSOperationQueue alloc] init];
[opqueue addOperationWithBlock:^{
[self thread_use_method:@"jd_nsOperation"];
}];
}
#pragma mark - 线程调用的方法
- (void)thread_use_method:(NSString *)name
{
NSLog(@"%@ : %@ --- %@", name, [NSThread currentThread],[NSThread mainThread]);
}
三、线程间的通信
iOS
在Threading Programming Guide官方文档里面提及了Communication mechanisms
也就是通信机制,一共展示了7种线程间通信机制 :
下面一一的翻译介绍一下 :
通信机制 | 描述 |
---|---|
直接消息传递 | Cocoa应用程序支持直接在其他线程上执行方法。 这意味着一个线程可以直接在其他任何的线程上执行一个方法。 因为方法是在目标线程的上下文执行的,所以以这种方式通信发送的消息会自动在该线程上自动化。 |
全局变量、 共享内存、 对象 |
在两个线程间通信,另一种简单的方法就是通过全局变量、共享内存块、对象。 这种方法快速而简单,但是对比直接消息传递更脆弱。必须使用锁或者其他同步机制保护共享的变量,确保代码的正确。 如果不这样做的话,可能会引发竞态条件、损坏数据甚至崩溃。 |
Conditions | Conditions是一种同步工具,本身是一种特殊类型的锁。 使用Conditions可以控制线程中特定代码的执行时间。 可以把Conditions看作一个守卫,只有条件满足了,才允许线程运行。 |
Run loop sources | 通过自定义Runloop source的配置,可以用来让线程接收特定消息。 由于Runloop source是依靠事件来驱动的,所以Runloop source在无事可做的时候,会让线程进入自动休眠状态,这也提高了线程的效率。 |
Ports and sockets | 基于端口的通信是两个线程之间更复杂的一种通信方法,但是它更可靠。 端口和套接字还可以用于与其他进程和服务通信。 为了提高效率,端口是通过Runloop source实现的,所以端口上没有数据的时候,也会让线程进入休眠状态。 |
消息队列 | 传统的多进程服务定义了FIFO抽象队列,用来管理消息的传入和传出。 消息队列优点是简单方便。 缺点是没有其他通信机制的高效率。 |
Cocoa分布式对象 | 基于Cocoa的分布式对象,它提供了基于端口通信的高级实现,可以作用于线程间通信。但是资源开销大。 可以尝试用它做进程间通信,而不是线程间通信。 |
注: 一个小问题
Q : 苹果,应该说iOS
为什么不像安卓一样可以多进程,使用多进程通讯?
A :
(1). 因为进程之间的切换,消耗的资源非常的大,虽然效率是蛮高的。(2). 另外在设计方面,苹果的沙盒使得资源更加的安全,隐私性更加的好,别的App很难拿到另外的App的内容,这也是为什么
iOS
相比安卓更加流畅的原因之一。(3). 这是个人猜想,苹果应该是认为
iOS
已经做到了非常大的优化和高效率了,没有必要给开发人员那么大的权限去切换进程玩,因为多进程意味着你对别的应用程序的骚扰性和影响都很大。
网友评论