进程&线程

Posted by BY on April 12, 2019

进程&线程(Read The Fucking Source Code)

http://gityuan.com/android/

通用进程&线程的概念
  • 进程(Process),即过程的意思,是一个抽象的概念。计算机中的描述语言是“程序在操作系统中运行的实例”。实例的意思即意味着可以在操作系统中存在多个同时运行。

    In computing, a process is the instance of a computer program that is being executed by one or many threads.

操作系统进程:程序真的运行起来的实例,可以被实现为存放调度给CPU的任务和状态的数据结构。上述的英文描述中指出,一个操作系统进程是由一到多个线程组成,这里的线程是一个抽象的概念。在Linux中线程是被实现为轻量级进程的。也即在Linux中的进程和线程实现的本质是一样的,但存在以下两点区别:

在资源消耗上进程的消耗多,线程消耗相对少 内存空间上有一些不同:进程的虚拟内存彼此隔离,而线程则共享同一虚拟内存空间有些不同

  • 并发,可以理解为程序执行所在进程引申出来的概念(这里的进程是区别于上面的操作系统进程的)。

    Java的线程是表达并发的概念的类。这个类在绝大部分操作系统上使用操作系统内核中的【线程】实现。二者之间还是有一些细微的差异。即开发者用Java Thread写代码表达思路,和操作系统调度线程执行是两个层面的事情。

  • 并行,即同时执行,真正意义上的同一时刻执行。在软件系统中,【程序】是否能【并行】运行,要看物理上有多少个CPU核心可以同时干活(或者再扩展一下,有多少台可用的物理主机)。举例说明就是:你写了个Java程序,同时启动了4个线程,但CPU只有单核,那么同一时刻只有一个线程在运行。如果有4个CPU核心,那么可以做到4个线程完全【并行】运行。如果有2个核心,那么就处于一种中间态。比如你可以用“并发度=4“,”并行度=2“形容这种情况。
  • 并发是不是总是性能好于串行的?未必。如果干活的只有一个人,却将任务分成多份子任务,那么总耗时其实是大于等于串行的,因为这里会存在线程切换等的一些开销。但是当干活的人数多了,就可以达到一种类似并行的状态(非真正意义上的并行),同一时刻可以让多个人同时执行任务的不同阶段的子任务。
  • 并发和并行的区别

    Concurrency is not Parallelism Concurrency enables parallelism & makes parallelism (and scaling and everything else) easy

这里核心的一个观点就是并行的上限是由并发的方法设计决定的

误解:并发是多个任务交替使用CPU,同一时刻只有一个任务在跑;并行是多个任务同时跑

这里的说法并非全错,只不过认为并行和并发是两个并列的,非此即彼的概念,这就错了。 第一,针对前半段,正确的理解应该是:针对一个问题,想到了一个可以拆解为多个【并发】的任务,这些任务执行时因为只有一个CPU只能“切换”的跑。 第二,针对后半段,正确的理解应该是:如果这些并行执行的任务是解决同一个问题的,那么他们既是【并发】的,同时也是【并行】的。当然如果并行执行的任务不是同一个,是完全独立的几个任务,那么这种情况也是可以理解为并行是多个任务同时跑。

  • 理解了并行和并发的概念,终极的目标是为了高效的执行任务。参考文献
Android中的进程&线程
  • 概念

    当某个应用组件启动且该应用没有运行其他任何组件时,Android 系统会使用单个执行线程为应用启动新的 Linux 进程。默认情况下,同一应用的所有组件在相同的进程和线程(称为“主”线程)中运行。 如果某个应用组件启动且该应用已存在进程(因为存在该应用的其他组件),则该组件会在此进程内启动并使用相同的执行线程。 但是,您可以安排应用中的其他组件在单独的进程中运行,并为任何进程创建额外的线程。

  • 进程的生命周期:进程优先级(重要性层次结构)
    • 前台进程:用户当前操作所必需的进程。如果一个进程满足以下任一条件,即视为前台进程:托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)托管某个 Service,后者绑定到用户正在交互的 Activity托管正在“前台”运行的 Service(服务已调用 startForeground())托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())托管正执行其 onReceive() 方法的 BroadcastReceiver。通常,在任意给定时间前台进程都为数不多。只有在内在不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。 此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。
    • 可见进程:没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 如果一个进程满足以下任一条件,即视为可见进程:托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况托管绑定到可见(或前台)Activity 的 Service。

      可见进程:被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。

    • 服务进程:正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。
    • 后台进程:包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。 有关保存和恢复状态的信息,请参阅Activity文档。
    • 空进程:不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

根据进程中当前活动组件的重要程度,Android 会将进程评定为它可能达到的最高级别。例如,如果某进程托管着服务和可见 Activity,则会将此进程评定为可见进程,而不是服务进程。 此外,一个进程的级别可能会因其他进程对它的依赖而有所提高,即服务于另一进程的进程其级别永远不会低于其所服务的进程。 例如,如果进程 A 中的内容提供程序为进程 B 中的客户端提供服务,或者如果进程 A 中的服务绑定到进程 B 中的组件,则进程 A 始终被视为至少与进程 B 同样重要。

由于运行服务的进程其级别高于托管后台 Activity 的进程,因此启动长时间运行操作的 Activity 最好为该操作启动服务,而不是简单地创建工作线程,当操作有可能比 Activity 更加持久时尤要如此。例如,正在将图片上传到网站的 Activity 应该启动服务来执行上传,这样一来,即使用户退出 Activity,仍可在后台继续执行上传操作。使用服务可以保证,无论 Activity 发生什么情况,该操作至少具备“服务进程”优先级。 同理,广播接收器也应使用服务,而不是简单地将耗时冗长的操作放入线程中。 这里一般适用于断点续传的场景,下载或上传数据,中间过程如果Activity异常退出有可能导致文件上传未完成,不启动服务就会导致数据丢失,下次还得重新开始,这对于大文件的上传下载是难以接受的。 对于网络请求之类的耗时操作,通常的做法是在activity中新启一个子线程也称工作线程来处理,但是存在内存泄露的风险,一种处理方式是在activity退出时将子线程关掉,但是这同样会带来一个问题就是如果activity异常退出时子线程任务并未完成,这时就需要采用断点续传等处理方式来解决;还有一种方式就是按照上面所说将工作线程的任务放到service中去,这样可以保证该操作具备服务进程的优先级

Android进程创建的流程

  • Android系统分层架构
  • Android进程从大类来分,主要包括两类:内核进程和用户进程
  • 进程:每个app在启动前都必须先创建一个进程,该进程是由Zygote fork出来的,进程具有独立的资源空间,用于承载App上运行的各种Activity/Service等组件。
  • 线程:线程对应用开发者来说非常熟悉,比如每次new Thread().start()都会创建一个新的线程,该线程并没有自己独立的地址空间,而是与其所在进程之间资源共享。

  • 系统启动架构图
  • kthreadd进程是所有内核进程的鼻祖(父进程)。
  • init进程是所有用户进程的鼻祖(父进程)
  • Zygote是所有Java进程的父进程,Zygote进程本身是由init进程孵化而来的
  • System Server进程,是由Zygote进程fork而来,System Server是Zygote孵化的第一个进程,System Server负责启动和管理整个Java framework,包含ActivityManager,WindowManager,PackageManager,PowerManager等服务。
  • Media Server进程,是由init进程fork而来,负责启动和管理整个C++ framework,包含AudioFlinger,Camera Service等服务。
  • ServiceManager是由init进程孵化出来的,是整个Binder架构(IPC)的大管家,所有大大小小的service都需要先请示servicemanager。

  • Zygote进程孵化出的第一个App进程是Launcher,这是用户看到的桌面App。
  • Zygote进程还会创建Browser,Phone,Email等App进程,每个App至少运行在一个进程上。所有的App进程都是由Zygote进程fork生成的。

  • 通信方式:
    • 无论是Android系统,还是各种Linux衍生系统,各个组件、模块往往运行在各种不同的进程和线程内,这里就必然涉及进程/线程之间的通信。对于IPC(Inter-Process Communication, 进程间通信),Linux现有管道、消息队列、共享内存、套接字、信号量、信号这些IPC机制,Android额外还有Binder IPC机制,Android OS中的Zygote进程的IPC采用的是Socket机制,在上层system server、media server以及上层App之间更多的是采用Binder IPC方式来完成跨进程间的通信。对于Android上层架构中,很多时候是在同一个进程的线程之间需要相互通信,例如同一个进程的主线程与工作线程之间的通信,往往采用的Handler消息机制。
    • Android中为何采用Binder的IPC方式?(https://www.zhihu.com/question/39440766/answer/89210950)
    • Handler只能用于共享内存地址空间的两个线程间通信,即同进程的两个线程间通信。很多时候,Handler是工作线程向UI主线程发送消息,即App应用中只有主线程能更新UI,其他工作线程往往是完成相应工作后,通过Handler告知主线程需要做出相应地UI更新操作,Handler分发相应的消息给UI主线程去完成。
  • Zygote fork进程(android.os.ZygoteProcess#zygoteSendArgsAndGetResult)
  • 这里是通过socket通道向Zygote进程发送一个参数列表,然后进入阻塞等待状态,直到远端的socket服务端发送回来新创建的进程pid才返回。google此处考虑是否加个超时,目前是没有的。