iOS多线程

线程的定义:

线程是进程的基本执行单元,一个进程的所有任务都在线程中执行,程序启动会默认开启一条线程,这条线程被称为主线程或 UI 线程。

通常线程用于处理哪些耗时的操作,来缓解主线程的压力。

线程和进程是有很大区别的:
1、同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
2、同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的 资源是独立的。

线程的优点:

  • 能适当提高程序的执行效率
  • 能适当提高资源的利用率(CPU,内存)
  • 线程上的任务执行完成后,线程会自动销毁

缺点:

  • 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512 KB)
  • 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
  • 线程越多,CPU 在调用线程上的开销就越大
  • 程序设计更加复杂,比如线程间的通信、多线程的数据共享
多线程的原理

cpu具有调度的能力,可以调度该进程内的线程,在很小的时间片内切换线程;

但是只有多核cpu才能来回切换处理线程,单核的话一次只能处理一个线程。

时间片的概念:CPU在多个任务直接进行快速的切换,这个时间间隔就是时间片

多线程同时执行:

  • 是 CPU 快速的在多个线程之间的切换
  • CPU 调度线程的时间足够快,就造成了多线程的“同时”执行的效果
    如果线程数非常多
  • CPU 会在 N 个线程之间切换,消耗大量的 CPU 资源
  • 每个线程被调度的次数会降低,线程的执行效率降低
线程的生命周期:
iShot2020-10-31 14.46.20.png

线程池:


iShot2020-10-31 14.51.15.png
线程的优先级:

线程的优先级threadPriority是一个double类型,它被一个NSQualityOfService所取代,字面意思是服务质量。
NSQualityOfService有5个类型:

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、原子锁atomic和非原子锁nonatomic
atomic:线程安全,需要消耗大量的资源
nonatomic:非线程安全,适合内存小的移动设备

iOS开发的建议:

  • 所有属性都声明为 nonatomic
  • 尽量避免多线程抢夺同一块资源
  • 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力

互斥锁:

当发现其他线程执行,当前线程休眠(就绪状态),一直在等打开唤醒执行

互斥锁小节:

  • 保证锁内的代码,同一时间,只有一条线程能够执行!
  • 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
    互斥锁参数
  • 能够加锁的任意 NSObject 对象
  • 注意:锁对象一定要保证所有的线程都能够访问
  • 如果代码中只有一个地方需要加锁,大多都使用 self,这样可以避免单独再创建一个

自旋锁:
当发现其他线程执行,当前线程询问(一直询问),一直等待,耗费性能比较高。

所以说,当需求所需时间短,可以使用自旋锁,但是比较耗时的,一般使用互斥锁。

线程通讯

线程通讯通常使用的方法:

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
    // equivalent to the first method with kCFRunLoopCommonModes

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
    // equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

这些方法功能基本上没啥好说的,从字面意思上就可以理解,下面通过部分代码来演示一下:

- (void)viewDidLoad {
    [super viewDidLoad];
    _imageView = [[UIImageView alloc] init];
    // 1. 准备 URL
    NSString *urlString = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1529488045873&di=7c1cc40bec638406ef48717386a08094&imgtype=0&src=http%3A%2F%2Fpic2.ooopic.com%2F12%2F46%2F41%2F95bOOOPIC74_1024.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];
    // 5. 设置 contentSize
    _scrollView.contentSize = image.size;
}

还有一个点就是关于NSPort,这个在通常开发中基本不同,只说一个值得注意的点,当需要NSPort执行事件时,需要将NSPort加入到Runloop

线程与Runloop的关系

1:runloop与线程是一一对应的,一个runloop对应一个核心的线程,为什么说是核心 的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局 的字典里。
2:runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休 眠状态,有了任务就会被唤醒去执行任务。
3:runloop在第一次获取时被创建,在线程结束时被销毁。
4:对于主线程来说,runloop在程序一启动就默认创建好了。
5:对于子线程来说,runloop是懒加载的,只有当我们使用的时候才会创建,所以在子线 程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调。

关于iOS线程的一些基本的使用和概念就结束了;
GCD的使用和一些底层将在下一篇讲述。

你可能感兴趣的:(iOS多线程)