移动开发中的多线程与并发:什么是它,原理及如何工作

作者: IT Sectr 发布日期: 2026-03-12 阅读时间: 13 分钟

每个移动应用同时执行许多任务:从网络加载数据、处理用户触摸、动画化界面和保存文件。如果所有这些代码都在一个线程中运行,那么任何网络延迟都会导致应用冻结。多线程(multithreading)和并发是关键概念,使应用保持响应和高效。本文将涵盖所有主要工具:从 Main Thread 和 RunLoop 到 Kotlin 协程和 iOS 上的 Combine。内容基于 Apple 官方 GCD 文档

关键要点

  • Main Thread — 唯一用于处理 UI 的线程;所有其他任务都移到 Background
  • GCD 和 OperationQueue — iOS 中多线程的主要机制
  • Coroutines 和 Flow — Kotlin/Android 中异步的现代标准
  • RxJava、RxSwift 和 Combine — 用于处理数据流的响应式框架
  • Race Condition、Deadlock 和 Livelock — 需要同步的经典多线程问题
  • 工具的选择取决于平台和任务的复杂性:简单调用用 Async/Await 就足够了,复杂流用 Rx 或 Combine

什么是多线程?

多线程是应用同时执行多个代码片段的能力。每个片段在单独的线程(Thread)中运行 — 一个轻量级进程,拥有自己的调用栈。在移动开发中,线程分为两类:Main Thread(UI 线程)和 Background Threads(后台线程)。

操作系统本身管理线程在处理器核心上的分配。现代设备有 6–8 个核心,因此并行执行确实可以加速工作。然而,创建线程是一项昂贵的操作,因此不建议直接使用 Thread。相反,使用更高级别的抽象:DispatchQueue、OperationQueue、CoroutineDispatcher。

并发(Concurrency)是一个比多线程更广泛的概念。并发意味着任务甚至可以在单核上通过上下文切换「同时」执行。异步(Async/Await)是一种编程模型,其中任务不阻塞线程,而是在等待结果时返回控制权。现代语言(Kotlin、Swift、Dart)内置了对 Async/Await 的支持。

在 IT Sectr,我们特别关注项目启动时正确的多线程架构。早期阶段犯的错误会导致难以发现的 bug:数据竞争、死锁和负载下的应用不稳定。我们的每个项目在规划阶段都会进行并发架构审查。

主线程(Main/Background)

Main Thread(主线程)— 移动应用中唯一可以访问 UI 的线程。在 Android 上称为UI Thread,在 iOS 上称为 Main Thread。所有界面操作 — 更改文本、动画、触摸处理 — 只在 Main Thread 上执行。如果在主线程上执行繁重操作(文件加载、JSON 解析),界面将停止响应。在 Android 上这会导致 ANR(Application Not Responding),在 iOS 上则会导致屏幕「冻结」。

Background Threads(后台线程)用于所有与 UI 无关的事情:网络请求、数据库操作、图像处理、加密。完成后,结果传递给 Main Thread 进行显示。每个平台都提供自己的工具在线程之间切换:iOS 上的 DispatchQueue.main.async,Android 上的 runOnUiThreadwithContext(Dispatchers.Main)

RunLoop — iOS 主线程上的事件处理循环。RunLoop 等待事件(触摸、定时器、通知)并将它们分派给适当的处理程序。在 Android 上,等效的是Looper,它与每个 Main Thread 相关联。Main Looper 无限地从队列中提取消息并将其传递给 Handler 进行处理。了解 RunLoop 和 Looper 有助于避免内存泄漏和界面「卡顿」。

GCD 和 OperationQueue(iOS)

Grand Central Dispatch(GCD)— Apple 用于在 C 语言级别管理多线程的库。GCD 使用 DispatchQueue(任务队列)工作。开发者不手动创建线程;GCD 管理一个线程池,将任务分配到可用的处理器核心上。DispatchQueue 有两种类型:Serial Queue(串行队列 — 任务一个接一个执行)和 Concurrent Queue(并发队列 — 任务可以同时执行)。

Main DispatchQueue — 绑定到主线程的串行队列。Global Queues — 具有不同优先级(QoS — Quality of Service)的并发队列:userInteractive、userInitiated、utility、background。选择正确的 QoS 对性能至关重要:.userInteractive — 用于影响 UI 的任务(动画、渲染);.background — 用于时间不敏感的任务(同步、缓存清理)。

OperationQueue — 在 GCD 之上的抽象,具有额外功能:取消任务、设置操作之间的依赖关系、控制最大并发操作数。操作是 Operation 类(或 BlockOperation)的对象。示例:如果需要加载图像,然后应用滤镜,然后才显示它 — 具有依赖关系的 OperationQueue 可以完美处理。在 GCD 中,您必须使用 DispatchGroup 或信号量手动同步这些步骤。

Swift 5.5+ 中的 Async/Await — 是 GCD 的现代替代方案。asyncawait 关键字使异步代码线性且可读。函数被标记为 async,调用通过 await 等待。系统本身管理上下文切换:默认情况下,async 函数在后台线程上执行,而 UI 更新在 MainActor 上执行。@MainActor — 保证代码在主线程上执行的属性。

Coroutines 和 Flow(Kotlin)

Coroutines(协程)— JetBrains 开发的用于 Kotlin 的轻量级线程。与普通线程不同,协程不绑定到特定的 Thread。数千个协程可以在多个线程上运行而不会产生显著开销。CoroutineScope 管理协程的生命周期:viewModelScope 绑定到 ViewModel,lifecycleScope 绑定到 Activity/Fragment。当作用域被销毁时,所有子协程都会自动取消。

Dispatchers 确定协程在哪个线程池上运行:Dispatchers.Main — UI 线程;Dispatchers.IO — 用于网络请求和磁盘操作;Dispatchers.Default — 用于 CPU 密集型计算。要切换调度器,使用 withContext。协程支持结构化并发:每个协程都有一个父级,当父级被取消时,所有子协程都会被取消。这可以防止内存泄漏和挂起的任务。

Flow — 来自协程库的冷异步数据流。Flow 顺序发出值:(1)生产者生成数据,(2)操作符转换流,(3)收集器消费结果。与 LiveData 不同,Flow 支持复杂的操作符链(map、filter、flatMapConcat、catch),并且是完全线程安全的。StateFlowSharedFlow — Flow 的热变体,非常适合 UI 状态和一次性事件(Snackbar、导航)。

Channel — 另一种用于在协程之间传递数据的协程抽象。Channel 像一个队列一样工作:一个发送者(send)和一个或多个接收者(receive)。缓冲通道(Channel(UNLIMITED)、Channel(BUFFERED))允许配置溢出时的行为。Channel 经常与 Flow 一起用于将基于回调的 API 桥接到协程:callbackFlow { … }。

在 IT Sectr,我们在所有 Android 项目中积极使用协程和 Flow。这允许编写看起来同步的异步代码,易于测试(runTest、TestDispatcher)且不需要手动线程管理。一个带有数据加载的简单协程示例:

kotlin
class UserRepository(
    private val api: UserApi,
    private val dao: UserDao
) {
    suspend fun getUsers(): List<User> = withContext(Dispatchers.IO) {
        return@withContext try {
            val users = api.fetchUsers()
            dao.insertAll(users)
            users
        } catch (e: Exception) {
            dao.getAll()
        }
    }
}

Rx 和 Combine

响应式编程 — 一种数据以异步流(Observable、Publisher)形式传播的范式。RxJava/RxKotlin — Android 上最流行的实现,从 .NET Rx 移植而来。RxSwift — 用于 iOS 的类似库。主要组件:Observable(事件源)、Observer(订阅者)、Scheduler(线程管理)、Operators(流转换)。

Combine — Apple 在 iOS 13 中引入的响应式编程框架。Combine 使用 Publisher(发布者)和 Subscriber(订阅者)协议。与 RxSwift 不同,Combine 内置于 SDK 中,并与 SwiftUI 紧密集成。Combine 中的操作符:map、filter、combineLatest、zip、debounce、throttle — 涵盖了大多数场景:从数据绑定到 UI 到搜索查询去抖。

Future 和 Promise — 用于处理单个异步结果的模式。Future 表示以后可用的值。Promise 是提供值的承诺。在 Rx 中这是 Single(一个成功响应或错误),在 Combine 中是 Future Publisher。在实践中,Future/Promise 对于单个 API 请求很方便,而 Observable/Publisher 则适用于连续流(地理位置、文本输入)。

Callback 和 Delegate — 用于异步操作的经典模式。Callback — 作为参数传递并在操作完成时调用的函数。Delegate — 实现具有事件处理程序方法的协议的对象。缺点:「回调地狱」(嵌套回调)和错误处理的复杂性。NotificationCenter(iOS)和 EventBus(Android)— 广播事件机制,适用于松散耦合的通信,但会导致隐式依赖。

多线程问题(Race Condition, Deadlock)

多线程打开了高性能的大门,但同时也带来了难以发现的错误风险。最常见的是:Race Condition(竞态条件)、Deadlock(死锁)、Livelock(活锁)和 Starvation(线程饥饿)。理解这些问题是任何移动开发者的必备技能。

Race Condition

当两个或多个线程在没有同步的情况下同时读写相同的数据时,就会发生 Race Condition。结果取决于哪个线程先执行。经典示例:两个线程递增计数器。「读取 → 递增 → 写入」操作不是原子的,因此当同时执行时,一个递增会「丢失」。解决方案 — 使用原子操作(AtomicInteger、AtomicReference)或锁(Mutex、Semaphore、synchronized)。

Deadlock

Deadlock — 每个线程持有一个资源并等待另一个线程持有的资源的情况。没有线程可以继续。发生条件:互斥、持有并等待、无抢占、循环等待。预防:建立单一的锁获取顺序、使用带超时的 tryLock、应用无锁算法(ConcurrentHashMap、CopyOnWriteArrayList)。

Livelock 和 Starvation

Livelock — 线程未被阻塞,但不断相互「传递」资源而不做有用工作。示例:两个人在走廊相遇,都向同一方向移动让路。Starvation — 线程无法访问资源,因为其他线程不断拦截它。解决方案:公平锁(fair locks)、谨慎使用线程优先级。

同步工具

为了防止多线程问题,使用同步原语:Mutex(互斥)、Semaphore(限制并发访问数)、Lock(带 tryLock 的接口)、Synchronized(JVM 级锁)、@MainActor(Swift — 保证在主线程上执行)。在 Android 上,也可通过 Executors.newFixedThreadPool、newCachedThreadPool 使用 ThreadPool。然而,手动池管理是遗留项目的特权;在新项目中,最好使用协程。

工具 平台 类型 特性
DispatchQueue (GCD)iOS任务队列串行/并发、QoS 优先级、线程池由系统管理
OperationQueueiOS操作队列依赖关系、取消、maxConcurrentOperationCount
Coroutines + FlowAndroid协程轻量、结构化并发、StateFlow、Channel
RxJava / RxKotlinAndroid响应式流Observable、Schedulers、丰富的操作符集
CombineiOS响应式流Publisher/Subscriber、SwiftUI 集成
Async/Await + TaskiOS / Android异步模型线性代码、@MainActor、结构化并发

常见问题

Main Thread 和 Background Thread 有什么区别?

Main Thread(UI 线程)负责界面渲染和触摸处理。Background Thread 执行后台任务 — 数据加载、计算、网络工作。阻塞 Main Thread 会导致界面冻结(Android 上为 ANR,iOS 上为 frozen UI)。

什么是 Race Condition 以及如何避免它?

Race Condition — 当两个线程同时访问共享数据且结果取决于执行顺序时的竞态条件。通过同步避免:Mutex、Semaphore、Lock、Synchronized、@MainActor 或原子操作。

Coroutines 还是 RxJava:Android 该选哪个?

Coroutines 是 Android 的现代标准(JetBrains,Google 支持)。RxJava/RxKotlin 是一种具有丰富操作符集的响应式方法。Coroutines 对于异步调用更简单,RxJava 对于复杂数据流更强大。在 IT Sectr,我们对新项目使用 Coroutines + Flow。

什么是 Deadlock 和 Livelock?

Deadlock — 两个线程互相等待对方资源的相互阻塞。Livelock — 线程未被阻塞但不断传递资源而不做有用工作。两个问题都通过正确的锁顺序和超时来解决。

为什么 iOS 中需要 DispatchQueue?

DispatchQueue 是 Grand Central Dispatch(GCD)用于线程管理的抽象。Main Queue 在主线程上执行任务,Global Queues 在后台线程上执行。Serial Queue 保证顺序执行,Concurrent Queue 保证并行执行。在现代项目中,GCD 通常被 Async/Await 和 Task 取代。

总结

  • Main Thread — 仅 UI;所有其他操作在后台
  • GCD 和 OperationQueue — iOS 多线程的基础;Async/Await — 现代替代方案
  • Coroutines 和 Flow — Android 的标准;结构化并发防止泄漏
  • RxJava、RxSwift、Combine — 用于复杂数据流的响应式框架
  • Race Condition 和 Deadlock — 主要问题;通过锁和正确的资源获取顺序解决
  • Thread Pool 由系统(GCD)或框架(协程)管理;不推荐手动创建线程
  • 工具的选择取决于平台:Android 用 Coroutines,iOS 用 GCD/Combine

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目