OC底层原理探索之多线程原理| 8月更文挑战

541 阅读5分钟

进程是什么

进程是指在系统中正在运行的一个应用程序(App);每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间中;通过活动监视器可以查看Mac系统中所开启的进程。 ​

线程是什么

线程是进程的基本执行单元,一个进程的所有任务都是在线程中执行;进程想要执行任务必读得有线程,进程至少有一条线程。程序启动后会默认开启一条线程,这条线程被称作主线程或者UI线程。 ​

进程与线程的关系

地址空间:同⼀进程的线程共享本进程的地址空间,⽽进程之间则是独⽴的地址空间。 资源拥有:同⼀进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独⽴的。

  1. ⼀个进程崩溃后,在保护模式下不会对其他进程产⽣影响,但是⼀个线程崩溃整个进程都死掉。所以多进程要⽐多线程健壮。
  2. 进程切换时,消耗的资源⼤,效率⾼。所以涉及到频繁的切换时,使⽤线程要好于进程。同样如果要求同时进⾏并且⼜要共享某些变量的并发操作,只能⽤线程不能⽤进程
  3. 执⾏过程:每个独⽴的进程有⼀个程序运⾏的⼊⼝、顺序执⾏序列和程序⼊⼝。但是线程不能独⽴执⾏,必须依存在应⽤程序中,由应⽤程序提供多个线程执⾏控制。
  4. 线程是处理器调度的基本单位,但是进程不是。
  5. 线程没有地址空间,线程包含在进程地址空间中
  6. 线程局部存储(ThreadLocalStorage,TLS)。线程局部存储是某些操作系统为线程单独提供的私有空间,但通常只具有很有限的容量。(获取同步锁的时候用到sync_data/在objc_init函数中也存在)

多线程的意义

多线程并不是真正原理上的并发,只是CPU在多个任务直接进⾏快速的切换,这个时间间隔就是时间⽚。真正的并发建立在一个多核的基础上

优点:

  1. 能适当提高程序的执行效率
  2. 能适当提高资源的利用率(cpu 内存)
  3. 线程上的任务执行完成后,线程会自动销毁 缺点:
  4. 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占512kb)
  5. 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
  6. 线程越多,CPU在调用线程上的开销就越大
  7. 程序设计的更加复杂比如线程之间的通信、多线程的数据共享

单核同一时间,cpu只能处理一个线程,换言之,同一时间只有1个线程在执行 多线程同时执行其实是cpu快速的在多个线程之间的切换;cpu线程调度的时间足够快,就造成了多线程的同时执行的假象。 如果线程数量非常多的话,cpu会在N个线程之间切换,消耗大量的cpu资源,每个线程被调度的次数会降低,线程的执行效率降低。 ​

多线程的几种方案

image.png

线程生命周期

线程的生命周期.png

线程调度的策略

线程池.png

饱和策略

  1. AbortPolicy 直接抛出RejectedExecutionExeception异常来阻⽌系统正常运⾏
  2. CallerRunsPolicy 将任务回退到调⽤者
  3. DisOldestPolicy 丢掉等待最久的任务
  4. DisCardPolicy 直接丢弃任务

任务执行速度的影响因素

  1. cpu
  2. 任务的优先级
  3. 任务的复杂度
  4. 线程当前的状态

优先级翻转

  1. IO密集型 :频繁等待
  2. CPU密集型 :很少等待 为了避免出现饿死状态,cpu调度 IO密集型会提高优先级

改变线程优先级的因素

  1. 用户指定优先级
  2. 根据等待的频繁度
  3. 长时间不执行的任务也会提高优先级

自旋锁和互斥锁

  • 互斥锁 发现其他线程执行 当前线程 休眠 (就绪状态) 一直在等打开 唤醒执行
  • 自旋锁 发现其他线程执行 当前线程 询问 - 忙等 耗费性能比较高 一般使用在短小的任务中

atomic :是原子属性,是为多线程开发准备的,是默认属性!仅仅在属性的 setter方法中,增加了锁(自旋锁),能够保证同一时间,只有一条线程对属性进行操作。但是同一个时间多个线程都可以取值 nonatomic 是非原子属性。没有锁!性能高!

看源码:

void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
  	 //....
    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }
}

从上面我们可以看出atomic不是锁,只是一个加锁的标识。 ​

线程和Runloop

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

补充

__bridge只做类型转换,但是不修改对象(内存)管理权; __bridge_retained将oc对象转换为CoreFoundation,内存管理权限交给我们,后续需要使用CFRealeas或者相关的方法来释放对象 __bridge_transferCoreFoundation转换为oc对象,同时将内存的管理权限交回给ARC