美文网首页
iOS多线程

iOS多线程

作者: 大橘猪猪侠 | 来源:发表于2020-11-07 11:46 被阅读0次
    线程的定义:

    线程是进程的基本执行单元,一个进程的所有任务都在线程中执行,程序启动会默认开启一条线程,这条线程被称为主线程或 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<NSString *> *)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<NSString *> *)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多线程

          本文链接:https://www.haomeiwen.com/subject/lamfbktx.html