美文网首页
第二十五节—线程

第二十五节—线程

作者: L_Ares | 来源:发表于2020-11-14 03:22 被阅读0次

    本文为L_Ares个人写作,以任何形式转载请表明原文出处。

    本节介绍线程的一些基本知识,为了后续开启和iOS相关的线程内容。个人建议要深入学习线程的话,可以看一下《posix多线程程序设计》。本章节只是介绍我认为需要介绍的基本知识,包括后续章节主要都是和iOS开发相关的线程探索。

    下面有一份苹果官方的线程文档,毕竟苹果也是要面子的人,线程这么重要的东西,肯定会有自己的官方文档的,你会发现,苹果的线程实现和pthread息息相关,但是个人不建议直接操控pthread,因为操控底层一般都是很"危险"的事情。

    资料 : 苹果官方关于线程的文档

    一、线程基础

    1. 什么是进程

    进程的定义分为广义和狭义两种。

    • 广义 : 进程是一个具有一定独立功能的程序关于某个数据集合的一次活动,是操作系统动态执行的基本单元,即是基本分配单元,又是基本执行单元。
    • 狭义 :进程是一个正在运行的程序实例。

    说直白一点,仅以iOSApp为例,因为iOS是不支持多进程的,每个进程之间都是相互独立的,每个进程均在其专用的、受保护的内存空间上运行,拥有独立运行所需要的全部资源,所以你可以直接用进程的狭义定义理解,也就是 :

    仅针对iOS来说,进程可以理解为一个正在运行App

    2. 什么是线程

    在计算机中,线程是一种能够实现某种功能的基本软件单元。而在iOS中,因为没有多进程App,我们可以具体化这句话 :

    线程是进程的基本执行单元。是程序执行流的最小单元,一个进程的所有任务都在线程中执行。

    3. iOS中的线程

    (1).在iOS中,进程想要执行任务,必须至少拥有一条线程。
    (2). iOS程序(进程)启动的时候,会默认开启一条线程,这就是我们常说的主线程。也有人叫它UI线程,负责处理UI事件,包括显示和刷新。

    4. 线程和进程的关联与区别

    • 地址空间 : 同一个进程的所有线程共享本进程的地址空间
    • 资源分配 : 同一个进程内的所有线程共享本进程的资源,例如:内存、I/OCPU等。
    • 基本单位 : 在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). 销毁 : 任务执行完毕,就可以退出了。也可以是满足某个条件后,在线程的内部或者主线程中手动终止线程的执行,从而退出线程。

    线程绝大多数时间都处于它生命周期中的三个状态 : 就绪运行阻塞

    另外,在销毁的时候,有cancelexit两种常见的方法,它们也是有区别的 :

    • exit : 强行终止线程。后续的所有代码都不会执行。
    • cancel : 不可以强行终止正在执行的线程。终止的也只是当前的线程。

    来张图,看的清楚点,看看线程在其生命周期中都经历了什么。

    图1.7.0.png

    8. 线程池

    这个就顾名思义了,线程池,就是拿来装线程的,一般情况下,以这种作为概念的设计,都会存在三种容量大小 : 最大容量核心容量当前容量

    下面也是直接上图吧,文字表述容易乱掉,这个图就可以理解线程池的一个原理。

    图1.8.0.png

    四个缓存策略 :

    1. Abort策略 : 这是饱和策略的默认策略。当线程池的大小已经和核心线程池大小一样大,并且工作队列已经饱和,工作队列中的线程也都在工作,那么当新的任务提交到线程的时候,直接抛出未检查异常,也就是RejectedExecutionExeception,该异常可由调用者捕获。
    2. CallerRuns策略 : 这是饱和策略的调节策略,即不放弃任务也不抛出异常,而是将某些任务回退到调用者。
      它不会在线程池的线程中执行新的任务,而是在exector的线程中运行新的任务。
    3. Discard策略 : 这是饱和策略中很直接的策略,直接抛弃新提交的任务。
    4. DiscardOldest策略 : 抛弃最长时间都没执行的任务,也就是队列头上的任务,然后尝试提交新任务。(不适合工作队列为优先队列的场景)。

    二、iOS中的多线程

    iOS开发中,多线程有4种大家常见的实现方案,其中以GCD最受官方推荐,以pthread使用最少,原因很简单,pthread是更底层的多线程实现方案,虽然可以有更多的自主性,但是容易引发问题。

    1. 多线程方案

    方案 简介 语言 线程生命周期 使用频率
    pthread 1. 一套通用的多线程API;
    2. 适用于UnixLinuxWindows等系统;
    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]);
    }
    
    

    三、线程间的通信

    iOSThreading Programming Guide官方文档里面提及了Communication mechanisms也就是通信机制,一共展示了7种线程间通信机制 :

    图1.0.0.png

    下面一一的翻译介绍一下 :

    通信机制 描述
    直接消息传递 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已经做到了非常大的优化和高效率了,没有必要给开发人员那么大的权限去切换进程玩,因为多进程意味着你对别的应用程序的骚扰性和影响都很大。

    相关文章

      网友评论

          本文标题:第二十五节—线程

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