多线程介绍

线程和进程

线程和进程的定义

线程:markdown

  • 线程是进程的基本执行单元,一个进程的全部任务都在线程中执行
  • 进程要想执行任务,必须得有线程,进程至少要有一条线程
  • 程序启动会默认开启一条线程,这条线程被称为主线程或 UI 线程

进程:多线程

  • 进程是指在系统中正在运行的一个应用程序
  • 每一个进程之间是独立的,每一个进程均运行在其专用的且受保护的内存空间内
  • 经过“活动监视器”能够查看 Mac 系统中所开启的进程

进程与线程的关系

地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,可是进程之间的 资源是独立的。并发

  1. 一个进程崩溃后,在保护模式下不会对其余进程产生影响,可是一个线程崩溃整个进程都死掉。因此多进程要比多线程健壮。
  2. 进程切换时,消耗的资源大,效率高。因此涉及到频繁的切换时,使用线程要好于进程。一样若是要求同时进行而且又要共享某些变量的并发操做,只能用线程不能用进程
  3. 执行过程:每一个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。可是 线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
  4. 线程是处理器调度的基本单位,可是进程不是。
  5. 线程没有地址空间,线程包含在进程地址空间中

多线程的意义

优势:性能

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

缺点atom

  • 开启线程须要占用必定的内存空间(默认状况下,每个线程都占 512 KB)
  • 若是开启大量的线程,会占用大量的内存空间,下降程序的性能
  • 线程越多,CPU 在调用线程上的开销就越大

* 程序设计更加复杂,好比线程间的通讯、多线程的数据共享spa

多线程原理

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

* (单核CPU)同一时间,CPU 只能处理 1 个线程  * 换言之,同一时间只有 1 个线程在执行设计

* 多线程同时执行: * 是 CPU 快速的在多个线程之间的切换3d

  • CPU 调度线程的时间足够快,就形成了多线程的“同时”执行的效果

* 若是线程数很是多code

  • CPU 会在 N 个线程之间切换,消耗大量的 CPU 资源 

* 每一个线程被调度的次数会下降,线程的执行效率下降

多线程技术方案

image.png

C与OC的桥接

  • __bridge只作类型转换,可是不修改对象(内存)管理权;
  • __bridge_retained(也可使用CFBridgingRetain)将Objective-C的对象转换为 Core Foundation的对象,同时将对象(内存)的管理权交给咱们,后续须要使用 CFRelease或者相关方法来释放对象;
  • __bridge_transfer(也可使用CFBridgingRelease)将Core Foundation的对象 转换为Objective-C的对象,同时将对象(内存)的管理权交给ARC。

线程生命周期

image.png

  • 新建:new新建线程后,调用start后,并不会当即执行,而是进入就绪状态,等待CPU的调度。
  • 运行:CPU调度当前线程,进入运行状态,开始执行任务。若是当前线程还在运行中,CPU从可调度池中调用其余线程,来执行此任务。
  • 阻塞:运行中的任务,被调用sleep/等待同步锁时,会进入阻塞状态。全部线程都中止,等待sleep结束/获取同步锁,才会回到就绪状态。
  • 死亡:运行中的任务,在任务执行完或被强制退出时,线程自动进入Dead销毁。

线程池

image.png

饱和策略

  • AbortPolicy 直接抛出RejectedExecutionExeception异常来阻止系统正常运行
  • CallerRunsPolicy 将任务回退到调用者
  • DisOldestPolicy 丢掉等待最久的任务
  • DisCardPolicy 直接丢弃任务

这四种拒绝策略均实现的RejectedExecutionHandler接口

多线程常见问题

  1. 任务执行速度的影响因素有哪些
  • cup 的调度状况
  • 任务的复杂度
  • 优先级
  • 线程状态
  1. 优先级翻转

在看优先级反转前先了解什么是IO密集型线程和CPU密集型线程。

  • IO密集型线程:频繁等待的线程,等待的时候会让出时间片。
  • CPU密集型线程:不多等待的线程,意味着长时间占用着 CPU。

IO密集型线程比CPU密集型线程更容易获得优先级提高。

特殊场景下,当多个优先级都比较高的CPU 密集型线程霸占了全部 CPU 资源,而此时优先级较低的 IO 密集型线程将持续等待,产生线程饿死的现象。固然,为了不线程饿死,CPU会发挥调度做用去逐步提升被“冷落”线程的优先级(提升优先级不必定就会执行,有几率的问题),IO 密集型线程一般状况下比 CPU 密集型线程更容易获取到优先级提高。

  1. 线程优先级影响因素
  • 用户指定
  • 根据等待的频繁度提高或者下降
  • 长时间不执行会被提升优先级

自旋锁和互斥锁

在咱们使用多线程的过程当中会遇到一种现象,就是资源抢夺。

image.png

例如多窗口售票这个案例,假设如今有 1000 张票,窗口 1 售卖了一张还剩 999 张,可是窗口 2 并不能同步知道剩余票数,因此仍是按照 1000 张去售票,这种状况就会出现问题。这个时候咱们就须要借助加锁操做来解决这种问题。这里咱们介绍两种锁,自旋锁与互斥锁。

互斥锁:

  • 发现其余线程执行,当前线程休眠 (就绪状态) 一直在等打开 , 唤醒执行。
  • 保证锁内代码,同一时间,只有一条线程可以执行。
  • 互斥锁的锁定范围,应该尽可能小,锁定范围越大,效率越差。

互斥锁参数:

  • 可以加锁的任意NSObject对象。
  • 锁对象要保证全部线程都可以访问。
  • 若是代码只有一个地方须要加锁,大多都使用self,这样能够避免单独再建立一个锁对象。

自旋锁:

  • 发现其余线程执行,当前线程询问 , 忙等,耗费性能比较高。自旋锁内容应尽量小,保障尽快完成锁内任务。

互斥锁与自旋锁的区别:

  • 互斥锁是被动等待代码触发,再上锁。被动触发,任务资源较大(执行耗时长)时,选择互斥锁。
  • 自旋锁是主动轮循请求资源。因此自旋锁更消耗资源。要求当即执行,任务资源较小(执行耗时短)时,可选择自旋锁。

atomic 与 nonatomic

atomic:

  • atomic 是原子属性,是为多线程开发准备的,是默认属性!
  • 仅仅在属性的 setter 方法中,增长了锁(自旋锁),可以保证同一时间,只有一条线程对属性进行操做
  • 同一时间 单(线程)写多(线程)读的线程处理技术

nonatomic:

  • nonatomic 是非原子属性
  • 没有锁!性能高!

下面咱们来看一下 atomic 的底层实现:

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    // 判断是不是 atomic 标识,是的话就添加锁操做
    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}
复制代码

在源码中咱们能够看到 atomic 其实就是一个标识,底层根据 atomic 标识来判断是否加锁。

相关文章
相关标签/搜索