iOS底层探索22、多线程

Threading Programming Guide 苹果文档
下面两图分别是可选择的线程技术手段和线程创建成本:

线程技术手段:Operation / GCD / 空闲时间通知 / 异步函数 / 定时器 / 独立的进程

image.png

image.png

一、进程和线程

1、概念

  • 线程
  1. 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行;
  2. 进程若要执行任务则其至少要有一条线程;
  3. 程序会默认开启一个线程,称之为主线程or UI线程。
  • 进程
  1. 系统中运行的一个应用程序;
  2. 进程间是独立的,进程运行在其专用且受保护的内存空间内。例如:iOS手机系统中多个APP。

2、线程和进程的区别

地址空间:
同一个进程的线程之间共享本进程的地址空间;
而不进程之间是独立的地址空间。

资源拥有:
同一个进程内的线程共享本进程的资源。例如:内存资源、I/O、CPU 等;
进程之间资源式独立的。

二、多线程

1、多线程的意义

  • 优点
  1. 可以适当的提高程序执行效率;
  2. 提高资源利用率:CPU 内存等;
  3. 线程上的任务执行完成后,线程会自动销毁。
  • 缺点
  1. 开启线程需要占用一定的内存空间(默认情况下,每个线程占 512 字节);
  2. 若开启大量线程,会占用大量的内存空间,降低程序性能;
  3. 线程越多,CPU在调度线程上的开销越大;
  4. 程序设计相较会更复杂,如:线程间通信、多线程的数据共享等。

tip:多线程的使用体验上的意义。例如:一个大的事务处理时长可能较久,直接等待完成再处理UI不合适,将其分割成多个小的事务,虽然整体上消耗大了,但对使用体验上来说更为流畅。

2、多线程的执行原理

多线程,是真的多个线程在同一时间同时执行吗?NO!
下图,以单核 CPU 为例:

image.png

时间片:CPU 在多个任务之间进行快速的切换,这个时间间隔就是时间片。
*单核CPU 为例:

  • 同一时间只可处理一个线程
    即同一时间只有一个线程在执行。
  • 多线程同时执行:
    其实是 CPU 在多个线程之间快速的切换
    CPU 调度现成的时间足够快,造成了多线程“同时”执行的假象。(*真正的多线程在于多核*)
  • 若线程数量非常多
    CPU 会在 N 个线程间不停切换,消耗大量 CPU资源;
    每个线程的被调度次数会降低,进而执行效率降低。

3、线程的生命周期

3.1、线程生命周期

image.png

start 不会直接开始执行而是准备就绪等待被CPU调度执行。

3.2、线程池

image.png
  • 饱和策略:
  1. AbortPolicy:直接抛出异 RejectedExecutionExeception 阻止系统正常运行
  2. CallerRunsPolicy:将任务回退给调用者
  3. DisOldesPolicy:丢掉等待最久的任务
  4. 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、资源抢夺

购票案例:
如下图,同一时间同时多通道购票,余票量错误。


image.png

如何解决此问题?--> 信号量栅栏函数.详见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

NSPort

To receive incoming messages, add NSPort objects to an instance of NSRunLoop as input sources. NSConnection objects automatically add their receive port when initialized.

Demo 代码

三、多线程的使用 - 代码们

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。