lufei's Studio.

浅谈runloop

字数统计: 1.4k阅读时长: 5 min
2018/10/08 Share

参考

深入理解RunLoop

RunLoop 是 iOS 和 OSX 开发中的概念,首先,先简单介绍我目前已知的东西

RunLoop和线程的关系:

  • RunLoop的作用就是用来管理线程的,当线程的RunLoop开启后,线程就会在执行完任务后,处于休眠状态,随时等待接受新的任务,而不是退出。
  • 只有主线程的RunLoop是默认开启的,所以程序在开启后,会一直执行,不会退出,其他线程的RunLoop如果需要开启,就手动开启。

RunLoop内部是如何实现的:

  • 有一个判断循环的条件,满足条件,就一直循环。
  • 线程得到唤醒事件被唤醒,事件处理完毕以后,回到睡眠状态,等待下次唤醒。

上面的谈论可能太笼统了,下面我们来详细谈谈RunLoop的那些事儿:

RunLoop的概念:

一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。所以我们需要这样一个机制,让线程能随时处理事件。

首先类似这样的模型,一般被称为 Event Loop ,这样的模型的关键在于:如何管理事件和消息,如何让线程在没有处理消息时休眠以避免资源占用,在有消息到来时立刻被唤醒。
所以,实际上,RunLoop是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个函数来执行 Event Loop 的逻辑。

线程执行了 Event Loop 这个函数后,就会一直处于这个函数内部接受消息->等待->处理的循环中,直到这个循环结束(例如quit),函数返回。

OSX/iOS中,提供了两个这样的对象:NSRunLoopCFRunLoopRef
CFRunLoopRef是在CoreFoundation框架内的,它提供了纯c的API,且都是线程安全的
NSRunLoop是基于CFRunLoopRef的封装,提供了面向对象的API,但是这些API不是线程安全的

tips

  • 关于线程安全:简单理解为一个方法或者一个实例可以在多线程环境中使用 而不会出现问题
  • 产生线程不安全的原因:在我理解下,这种情况大约是一或多个线程向相同的资源做了写操作才能发生,只要资源本身没有变化,那么多个线程读取相同资源应该是安全的(关于这一点,还需要查资料)

苹果不允许直接创建RunLoop,它提供了两个自动获取的函数CFRunLoopGetMain()和CFRunLoopGetCurrent(), 线程和RunLoop之间是一一对应的,其关系是保存在一个全局的Dictionary里。
线程刚创建时并没有RunLoop,如果你不主动获取,那它一直都不会有。RunLoop的创建是发生在第一次获取时,RunLoop的销毁是发生在线程结束时。你只能在一个线程的内部获取其RunLoop(主线程除外)
(RunLoop在NSRunLoop的实践,还需要自己去编码感受一下,source,timer,mode,Observer)

RunLoop的底层实现:

RunLoop 的核心是基于 mach port 的,其进入休眠时调用的函数是 mach_msg()
OSX/iOS 的系统架构基本上分为:应用层(SpringBoard等),应用框架层(Cocoa等框架),核心框架层(核心框架,openGL等),Darwin(操作系统核心,包括内核,驱动等)

Darwin这个核心的架构:

RunLoop.png

在硬件层上面的三个组成部分:MachBSDIOKit(还有其他内容),共同组成了XNU 内核.

XNU 内核的内环被称作 Mach,其作为一个微内核,仅提供了诸如处理器调度、IPC (进程间通信)等非常少量的基础服务。

BSD 层可以看作围绕 Mach 层的一个外环,其提供了诸如进程管理、文件系统和网络等功能。

IOKit 层是为设备驱动提供了一个面向对象(C++)的一个框架。

Mach 本身提供的 API 非常有限,而且苹果也不鼓励使用 Mach 的 API,但是这些API非常基础,如果没有这些API的话,其他任何工作都无法实施。在 Mach 中,所有的东西都是通过自己的对象实现的,进程、线程和虚拟内存都被称为”对象”。和其他架构不同, Mach 的对象间不能直接调用,只能通过消息传递的方式实现对象间的通信。”消息”是 Mach 中最基础的概念,消息在两个端口 (port) 之间传递,这就是 MachIPC (进程间通信) 的核心。

一条 Mach 消息实际上就是一个二进制数据包 (BLOB),其头部定义了当前端口 local_port 和目标端口 remote_port,发送和接受消息是通过同一个 API 进行的,其 option 标记了消息传递的方向。
为了实现消息的发送和接收,mach_msg() 函数实际上是调用了一个 Mach 陷阱 (trap),即函数mach_msg_trap(),陷阱这个概念在 Mach 中等同于系统调用。当你在用户态调用 mach_msg_trap() 时会触发陷阱机制,切换到内核态;内核态中内核实现的 mach_msg() 函数会完成实际的工作。

总结: RunLoop 的核心就是一个 mach_msg() ,RunLoop 调用这个函数去接收消息,如果没有别人发送 port 消息过来,内核会将线程置于等待状态。例如你在模拟器里跑起一个 iOS 的 App,然后在 App 静止时点击暂停,你会看到主线程调用栈是停留在 mach_msg_trap() 这个地方。

CATALOG
  1. 1. 参考
  2. 2. RunLoop和线程的关系:
  3. 3. RunLoop内部是如何实现的:
  4. 4. RunLoop的概念:
    1. 4.1. tips
  5. 5. RunLoop的底层实现:
    1. 5.1. Darwin这个核心的架构: