Threading Programming Guide 苹果文档
下面两图分别是可选择的线程技术手段和线程创建成本:
线程技术手段:Operation / GCD / 空闲时间通知 / 异步函数 / 定时器 / 独立的进程
一、进程和线程
1、概念
- 线程
- 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行;
- 进程若要执行任务则其至少要有一条线程;
- 程序会默认开启一个线程,称之为主线程or UI线程。
- 进程
- 系统中运行的一个应用程序;
- 进程间是独立的,进程运行在其专用且受保护的内存空间内。例如:iOS手机系统中多个APP。
2、线程和进程的区别
地址空间:
同一个进程的线程之间共享本进程的地址空间;
而不进程之间是独立的地址空间。
资源拥有:
同一个进程内的线程共享本进程的资源。例如:内存资源、I/O、CPU 等;
进程之间资源式独立的。
二、多线程
1、多线程的意义
- 优点
- 可以适当的提高程序执行效率;
- 提高资源利用率:CPU 内存等;
- 线程上的任务执行完成后,线程会自动销毁。
- 缺点
- 开启线程需要占用一定的内存空间(默认情况下,每个线程占
512 字节
); - 若开启大量线程,会占用大量的内存空间,降低程序性能;
- 线程越多,CPU在调度线程上的开销越大;
- 程序设计相较会更复杂,如:线程间通信、多线程的数据共享等。
tip:多线程的使用体验上的意义。例如:一个大的事务处理时长可能较久,直接等待完成再处理UI不合适,将其分割成多个小的事务,虽然整体上消耗大了,但对使用体验上来说更为流畅。
2、多线程的执行原理
多线程,是真的多个线程在同一时间同时执行吗?NO!
下图,以单核 CPU 为例:
时间片:CPU 在多个任务之间进行快速的切换,这个时间间隔就是时间片。
*
单核CPU 为例:
- 同一时间只可处理一个线程
即同一时间只有一个线程在执行。 - 多线程同时执行:
其实是 CPU 在多个线程之间快速的切换
CPU 调度现成的时间足够快,造成了多线程“同时”执行的假象。(*真正的多线程在于多核*
) - 若线程数量非常多
CPU 会在 N 个线程间不停切换,消耗大量 CPU资源;
每个线程的被调度次数会降低,进而执行效率降低。
3、线程的生命周期
3.1、线程生命周期
start
不会直接开始执行而是准备就绪等待被CPU调度执行。
3.2、线程池
- 饱和策略:
- AbortPolicy:直接抛出异 RejectedExecutionExeception 阻止系统正常运行
- CallerRunsPolicy:将任务回退给调用者
- DisOldesPolicy:丢掉等待最久的任务
- DisCardPolicy:直接丢弃任务
4、线程优先级
// 0x21 数值越大优先级越高
typedef NS_ENUM(NSInteger, NSQualityOfService) {
NSQualityOfServiceUserInteractive = 0x21,// 优先级最高
NSQualityOfServiceUserInitiated = 0x19,
NSQualityOfServiceUtility = 0x11,
NSQualityOfServiceBackground = 0x09,
NSQualityOfServiceDefault = -1
} API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
优先级指线程被调度的优先级,但其执行还和任务自身(资源的大小即:复杂度
)以及CPU调度
有关,优先级并不能决定任务的执行快慢
。
举例:车站购票(前提不可换队),购票的速度取决于1、自身购票数量多少(越多越慢)2、当前所选通道人流量(人越多越慢)3、即使是特殊通道人少,若售票员突然离开(等待...)
5、资源抢夺
购票案例:
如下图,同一时间同时多通道购票,余票量错误。
如何解决此问题?--> 锁
、信号量
、栅栏函数
.详见OC底层探索24、锁
6、线程通讯
6.1、performSelect
示例:加载网络图片
#pragma mark - performSelector -
- (void)my_threadConnect {
_imageView = [[UIImageView alloc] initWithFrame:CGRectMake(15, 400, 400, 300)];
[self.view addSubview:self.imageView];
// 1. 准备 URL
NSString *urlString = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1604589186135&di=873a7430fa3a955d1b8b08eaa8595ea3&imgtype=0&src=http%3A%2F%2Fimg.mp.itc.cn%2Fupload%2F20160923%2F03ece2d07d4f4cd69ba867220e89d6f8_th.jpg";
NSURL *url = [NSURL URLWithString:urlString];
// 2. 下载图像
//[self downloadImageWithURL:url];
// 异步下载图像
[self performSelectorInBackground:@selector(downloadImageWithURL:) withObject:url];
}
// 下载 URL 指定的网络图片
- (void)downloadImageWithURL:(NSURL *)url {
NSLog(@"%@", [NSThread currentThread]);
// 1. 所有从网络返回的都是二进制数据
NSData *data = [NSData dataWithContentsOfURL:url];
// 2. 将二进制数据转换成 image
UIImage *image = [UIImage imageWithData:data];
// 3. 在主线程更新 UI
// waitUntilDone: 是否等待 updateImage: 执行完成
[self performSelectorOnMainThread:@selector(updateImage:) withObject:image waitUntilDone:YES];
NSLog(@"完成");
}
// 更新图像 UI
- (void)updateImage:(UIImage *)image {
NSLog(@"更新 UI -> %@", [NSThread currentThread]);
// 3. 设置图像
_imageView.image = image;
// 4. 调整大小
// [_imageView sizeToFit];
}
6.2、端口通讯 - NSPort
To receive incoming messages, add
NSPort
objects to an instance ofNSRunLoop
as input sources.NSConnection
objects automatically add their receive port when initialized.