iOS 多线程
为什么要学习多线程
当在主线程执行耗时操作的时候,界面会卡死,阻塞用户交互,影响用户体验。怎么解决这个问题呢?就要使用多线程技术。
多线程概念
- 同步
- 异步
- 进程
- 线程
多线程原理
- (单核 CPU)统一时间,CPU 只能处理 1 个线程,只有 1 个线程在执行
- 多线程同时执行其实是 CPU 快速的在多个线程之间切换
- CPU 调度的时间足够快,就造成了多线程的 ”同时“ 执行
- 如果线程数非常多,CPU 会在 n 个线程之间切换,消耗大量的 CPU 资源,每个线程被调度的次数降低,线程的执行效率降低
多线程的优缺点
- 优点
- 能适当提高程序的执行效率
- 能适当提高资源的利用率
- 线程上的任务执行完成后,线程会自动销毁
- 缺点
- 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占用 512 KB)
- 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
- 线程越多,CPU 在调用线程上的开销就越大
- 线程越多,在一定程度上也是很耗电的
- 程序设计更加复杂,比如线程间的通信、多线程的数据共享
主线程
- 一个程序运行后,默认会开启 1 个线程,称为 主线程 或 UI 线程
- 主线程 一般用来刷新 UI 界面,处理 UI 事件(比如:点击、滚动、拖拽等事件)
- 主线程 使用注意
- 别将耗时的操作放到主线程中
- 耗时操作会卡住主线程,严重影响用户体验
多线程的技术方案
技术方案 | 简介 | 语言 | 线程生命周期 | 使用频率 |
---|---|---|---|---|
pthread | 1. 一套通用的多线程 API 2. 适用于 Uinix\Linux\Windows 等系统 3. 跨平台\可移植 4.使用难度大 |
C | 手动管理 | 几乎不用 |
NSThread | 1. 使用更加面向对象 2. 简单易用,可直接操作线程对象 |
OC | 手动管理 | 偶尔使用 |
GCD | 1. 旨在替代 NSThread 等线程技术 2. 充分利用设备的多核 |
C | 自动管理 | 经常使用 |
NSOperation | 1. 基于 GCD (底层是 GCD) 2. 比 GCD 多了一些更简单实用的功能 3. 使用更加面向对象 |
OC | 自动管理 | 经常使用 |
pthread 详解
- pthread 概述
- pthread 是 POSIX 多线程开发框架,是跨平台的 C 语言框架,需要自己管理线程的创建销毁等操作
- pthread_t 用于标识一个线程,不能单纯看成整数,通过头文件可以看到是 _opaque_pthread_t 类型的结构体指针
- pthread_attr_t 线程的属性,通过头文件可以看到是一个 _opaque_pthread_attr_t 类型结构体
- pthread API 介绍
int pthread_create(pthread_t _Nullable * _Nonnull __restrict, const pthread_attr_t * _Nullable __restrict,void * _Nullable (* _Nonnull)(void * _Nullable),void * _Nullable __restrict);
- 创建一个线程,创建成功返回 0,失败返回对应错误码
- 第一个参数为指向线程标识符的指针
- 第二个参数用来设置线程属性,我们可以初始化一个 pthread_attr_t 变量指定优先级等属性,一般可以传入 NULL,采用缺省值
int pthread_join(pthread_t , void * _Nullable * _Nullable);
- 等待某个线程执行完毕,这个函数是一个线程阻塞的函数,将一直阻塞到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回
- 第一个参数为被等待的线程标识符
- 第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值
int pthread_detach(pthread_t);
- 将该子线程的状态设置为 detached,则该线程运行结束后会自动释放所有资源
- pthread_detach 和 pthread_join 回收线程资源的区别:
- pthread_join 会同步等待子线程任务结束后回收其资源,如果该子线程没有运行结束,父线程会被阻塞,在有些情况下我们并不希望如此,就可以用 pthread_detach
- pthread_detach 不会阻塞调用线程
- 调用方式:父线程调用:
pthread_detach(thread2)
,也可以在子线程中调用:pthread_detach(pthread_self())
,pthread_self()
获取当前线程ID
int pthread_kill(pthread_t, int);
- 该函数可以用于向指定的线程发送信号
- 如果线程内不对信号进行处理,则调用默认的处理程式,如 SIGQUIT 信号会退出终止线程,SIGKILL会杀死线程等等,可以调用
signal(SIGQUIT, sig_process_routine);
来自定义信号的处理程序 - 第一个参数表示线程的标识符
- 第二个参数表示传递的signal参数,一般都是大于0的,这时系统默认或者自定义的都是有相应的处理程序。常用信号量宏可以在这里查看**#import <signal.h>** ,signal 为 0 时,是一个被保留的信号,一般用这个保留的信号测试线程是否存在
- pthread_kill 返回值如下:
- 0:调用成功
- ESRCH:线程不存在
- EINVAL:信号不合法
- 测试线程是否存在/终止的方法
线程的状态
graph LR A[New] -->|Start| B[Runnable] B --> C{Running} C --> B C --> D[Dead] C --> E[Blocked] E -->|Sleep| B
多线程访问共享资源的问题
- 共享资源
- 1 块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
- 比如多个线程访问同一个对象、同一个变量、同一个文件
- 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题