多线程基本原理
线程和进程
-
线程
- 线程是
进程的基本执行单元
,一个进程的所有任务都在线程中执行 - 进程想要执行任务,必须至少有一条线程
- 程序启动会默认开启一条线程,也就是我们常说的
主线程
也称为UI线程
- 线程是
-
进程
- 进程是指在系统中
正在运⾏的⼀个应⽤程序
,就是一段程序的执行过程,可以理解为手机上的一个app
- 每个进程之间是独立的,每个进程均运行在其
专用且受保护的内存空间内
- 通过
活动监视器
可以查看MAC
系统中所有开启的进程
- 进程是指在系统中
多线程的意义
-
优点:
- 能适当
提高程序的执行效率
- 能适当
提高资源的利用率(CPU,内容)
- 线程上的
任务执行完成后
,线程会自动销毁
- 能适当
-
缺点:
- 开启线程需要
占用一定的内存空间
(默认情况下
,每一个线程都占512KB
) - 如果开启
大量的线程
,会占用大量的内存空间
,降低程序的性能
线程越多
,CPU在调用线程上的开销就越大
- 多线程使
程序设计更加复杂
,比如线程间的通信、多线程的数据共享
- 开启线程需要
多线程的原理
多线程是CPU
在单位时间的时间片内
来回调度
和切换
,构成了很多任务同时进行的假象
- 单线程:
- (单核
CPU
)同一时间,CPU
只能处理一个线程
,换言之就是同一时间只有一个线程在执行
- (单核
- 多线程:
CPU
快速的在多个线程之间的切换,CPU
调度线程的时间足够快,就造成了多线程的同时
执行效果
- 如果线程数非常多:
CPU
会在N
个线程之间切换,消耗大量的CPU
资源,每个线程被调度的次数就降低
线程的生命周期
多线程时CPU
在多个线程之间来回切换,也就是有状态之分,当一个线程在被调度时,其他线程是的状态怎样的呢?于是就引出了线程的生命周期
线程的生命周期
分为5步:新建
->就绪
->运行
->阻塞
->死亡
,如下图:
- 新建:
- 使用
new
实例化一个线程对象,但该线程对象还未
使用start()
方法启动线程这个阶段,该阶段只在内存的堆
中为该对象的实例变量分配了内存空间
,但线程还无法
参与抢夺CPU的使用权
。
- 使用
- 就绪:
- 是指一个线程对象使用
start()
方法将线程加入可调度线程池
后就进入就绪阶段,等待CPU来调度
后才会执行
- 是指一个线程对象使用
- 运行:
- 当
CPU
开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。线程要想进入运行状态执行,首先必须先处于处于就绪状态中。运行状态可能和就绪状态来回切换,这个和CPU调度
有关。
- 当
- 阻塞:
- 处于运行状态中的线程由于某种原因例如(调用sleep、等待同步锁、从可调度线程池移出等),此时进入阻塞状态。
- 例如:
sleepForTimeInterval
,sleepUntilDate
函数,同步锁synchronized
等可使线程进入阻塞状态
- 死亡:
- 正常死亡:线程执行完毕
- 非正常死亡:当线程因异常而退出,或者调用
exit
可调度线程池
-
作原理是:
-
- 有新任务来时,会先判断线程池是否都在执行任务,如果
没到就会创建线程执行任务
- 有新任务来时,会先判断线程池是否都在执行任务,如果
-
- 如果都在执行,就会检查工作队列是否饱满,如果未饱满,就会将任务存储在工作队列
-
- 如果饱满,则会判断线程是否都处于执行状态,如果没有,则安排非核心线程去执行
-
- 如果都在执行状态,则交给
饱和策略处理
。
- 如果都在执行状态,则交给
-
-
饱和策略
- 饱和策略主要有4种:
-
AbortPolicy
:直接抛出RejectedExecutionExeception
异常来阻止系统正常运行
-
CallerRunsPolicy
:将任务回退到调用者
-
DisOldestPolicy
:丢掉等待最久的任务
-
DisCardPolicy
:直接丢弃任务
-
这四种拒绝策略均由
RejectedExecutionHandler
接口实现
任务执行的影响因素
任务执行的影响因素通常有4种:
CPU
- 任务的复杂度
- 优先级
- 线程状态
优先级
IO密集型
的线程特点是频繁等待,而CPU密集型
则很少等待,所以CPU密集型
的优先级要高,但优先级提高后也不一定执行,只是比低优先级的线程更可能运行,- 可以通过NSThread
中的setThreadPriority:
,或者POSIX
的pthread_setschedparam
方法来设置优先级
线程和runloop关系
-
runloop
与线程是⼀⼀对应的,⼀个runloop
对应⼀个核⼼的线程,为什么说是核⼼的,是因为runloop
是可以嵌套的,但是核⼼的只能有⼀个,他们的关系保存在⼀个全局的字典⾥
-
runloop
是来管理线程的,当线程的runloop
被开启后,线程回在执行完任务后进入休状态,有了任务就会被唤醒去执行任务
-
runloop
在第一次获取时被创建,在线程结束时被销毁
-
- 对于
主线程
来说,runloop
在程序一启动就默认创建好了
- 对于
-
- 对于子线程来说,
runloop
是懒加载的
,只有当我们使用的时候才会创建,所以在子线程时用定时器要注意:确保子线程的runloop
被创建,不然定时器就不会回调
- 对于子线程来说,
多线程的实现方案
GCD
-
GCD
全称是Grand Central Dispatch
,纯C
语言Api
,提供了非常多的强大函数
-
-
GCD
优势:
-
GCD
是苹果公司为多核的并行运算
提出的解决方案
-
GCD
会自动利用更多的CPU
内核(如:双核、四核)
-
GCD
会自动管理
线程的生命周期:创建线程
、调度任务
、销毁线程
,程序员只需要告诉GCD
想要执行什么任务,不需要编写任何线程管理代码
-
函数
- 任务:
GCD
的任务使用block
封装,block
中没有参数
也没有返回值
执行任务的函数: - 异步
dispatch_async
:不用等待当前语句执行完毕,就可以执行下一条语句- 会开启线程执行
block
的任务 - 异步是多线程的代名词
- 会开启线程执行
- 同步
dispatch_sync
:必须等待当前语句执行完毕,才会执行下一条语句- 不会开启线程
- 在当前线程执行
block
任务
队列
队列分为串行队列
和并行队列
,他们是一个数据结构
,都遵循FIFO(先进先出)
原则
串行队列
- 串行队列在同一时间只能执行一个任务,如图所示
- 在根据
FIFO
原则先进先出,所以后面的任务必须等前面的任务执行完毕
才能执行,就导致串行队列是顺序执行
的
并行队列
- 并行队列是一次可以调度多个任务,但并不一定都能执行,线程的状态必须是
runable
时才能执行,所以先调度不一定先执行:
函数与关系
函数与队列可以分为四种组合异步函数串行队列
、并发队列异步函数
、同步函数并发队列
、同步函数串行队列
-
- 异步函数串行队列:
开启
线程,任务一个接着一个
- 异步函数串行队列:
-
- 异步函数并发队列:
开启
线程,在当前线程执行任务,任务执行没有顺序
,和cpu
调度有关
- 异步函数并发队列:
-
- 同步函数并发队列:
不会开启
线程,在当前线程执行任务,任务一个接着一个
- 同步函数并发队列:
-
- 同步函数串行队列:
不会开启
线程,在当前线程执行任务,任务一个接着一个执行,会产生阻塞
- 同步函数串行队列:
主队列和全局队列
- 主队列:专门在
主线程
上调度任务的串行队列
,不会开启线程
,如果当前主线程正在执行任务,那么无论主队列中当前被添加了什么任务,都不会被调度dispatch_get_main_queue()
- 全局队列:为了方便程序员的使用,苹果提供了全局队列
dispatch_get_global_queue(0,0)
,全局队列是并发队列
,在使用多线程时,如果对队列没有特殊要求,在执行异步任务时,可以直接使用全局队列
队列的源码探究
主队列
dispatch_get_main_queue
/*
The main queue is meant to be used in application context to interact with the main thread and the main runloop.
1. 主队列与程序的`主线程`和`runloop`进行交互
2. 主队列在`main()`之前程序自动创建的
Returns the main queue. This queue is created automatically on behalf of the main thread before main() is called.
*/
dispatch_queue_main_t
dispatch_get_main_queue(void)
{
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}
DISPATCH_GLOBAL_OBJECT
函数,其中的有两个参数,第一个是类型,再来看看第二个参数_dispatch_main_q
struct dispatch_queue_static_s _dispatch_main_q = {
DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),
#if !DISPATCH_USE_RESOLVERS
.do_targetq = _dispatch_get_default_queue(true),
#endif
.dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
DISPATCH_QUEUE_ROLE_BASE_ANON,
.dq_label = "com.apple.main-thread",
.dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
.dq_serialnum = 1,
// 队列的区分可能与`dq_atomic_flags`和`dq_serialnum`两个参数有关,
// 值分别为`DQF_THREAD_BOUND | DQF_WIDTH(1)`和`1`
};
全局队列
dispatch_get_global_queue
dispatch_queue_global_t
dispatch_get_global_queue(intptr_t identifier, uintptr_t flags);
// `identifier`可以设置一些`优先级`
// - `flags`是留给将来使用,任务非`0`值都可能导致`NULL`,通常传`0`
dispatch_queue_global_t
dispatch_get_global_queue(intptr_t priority, uintptr_t flags)
{
dispatch_assert(countof(_dispatch_root_queues) ==
DISPATCH_ROOT_QUEUE_COUNT);
if (flags & ~(unsigned long)DISPATCH_QUEUE_OVERCOMMIT) {
return DISPATCH_BAD_INPUT;
}
dispatch_qos_t qos = _dispatch_qos_from_queue_priority(priority);
#if !HAVE_PTHREAD_WORKQUEUE_QOS
if (qos == QOS_CLASS_MAINTENANCE) {
qos = DISPATCH_QOS_BACKGROUND;
} else if (qos == QOS_CLASS_USER_INTERACTIVE) {
qos = DISPATCH_QOS_USER_INITIATED;
}
#endif
if (qos == DISPATCH_QOS_UNSPECIFIED) {
return DISPATCH_BAD_INPUT;
}
return _dispatch_get_root_queue(qos, flags & DISPATCH_QUEUE_OVERCOMMIT);
}
优先级
,有四种:-
DISPATCH_QUEUE_PRIORITY_HIGH
-
DISPATCH_QUEUE_PRIORITY_DEFAULT
-
DISPATCH_QUEUE_PRIORITY_LOW
-
DISPATCH_QUEUE_PRIORITY_BACKGROUND
-
// `_dispatch_get_root_queue`
static inline dispatch_queue_global_t
_dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
{
if (unlikely(qos < DISPATCH_QOS_MIN || qos > DISPATCH_QOS_MAX)) {
DISPATCH_CLIENT_CRASH(qos, "Corrupted priority");
}
return &_dispatch_root_queues[2 * (qos - 1) + overcommit];
}
_dispatch_root_queues
此时找到了与
dq_atomic_flags
中相关参数DQF_WIDTH
,传入的值为DISPATCH_QUEUE_WIDTH_POOL
,也就是DQF_WIDTH(DISPATCH_QUEUE_WIDTH_FULL - 1)
,但不能确定dq_serialnum
,它的值跟label
有关
自定义队列
队列的创建dispatch_queue_create
dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
return _dispatch_lane_create_with_target(label, attr,
DISPATCH_TARGET_QUEUE_DEFAULT, true);
}
_dispatch_lane_create_with_target
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
dispatch_queue_t tq, bool legacy) // tq NULL, legacy true
{
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa); // 面向对象封装
...
dispatch_lane_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_lane_s)); // 开辟内存
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0)); // 初始化
dq->dq_label = label; //label赋值
dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,
dqai.dqai_relpri);
if (overcommit == _dispatch_queue_attr_overcommit_enabled) {
dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
}
if (!dqai.dqai_inactive) {
dispatch_queue_priority_inherit_from_target(dq, tq);
dispatch_lane_inherit_wlh_from_target(dq, tq);
}
_dispatch_retain(tq);
dq->do_targetq = tq;
_dispatch_object_debug(dq, "%s", __func__);
return _dispatch_trace_queue_create(dq)._dq; // 创建痕迹标识,方便查找
}
- 该函数第一个参数
label
我们比较熟悉,就是创建的线程的名字 _dispatch_queue_attr_to_info
传入的第二参数:
dispatch_queue_attr_info_t
_dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa)
{
dispatch_queue_attr_info_t dqai = { }; // 初始为NULL,
if (!dqa) return dqai;
...
}
-
这里先初始一个
dispatch_queue_attr_info_t
类型对象,然后在根据dpa
类型进行相关赋值,如果dqa
为不存在则直接返回,这就是串行可以传NULL
的原因 -
做好相关的准备工作后,接着在调用
_dispatch_object_alloc
方法对线程开辟内存 -
再调用初始化函数
_dispatch_queue_init
,此处第三个参数有判断是否并判断,如果是并发传入为DISPATCH_QUEUE_WIDTH_MAX
,串行则传入1
,继续查看方法的实现:
-
此处可以看出又出现了
DQF_WIDTH()
函数和dq_serialnum
,并发DQF_WIDTH(DISPATCH_QUEUE_WIDTH_FULL - 2)
,串行为DQF_WIDTH(1)
,但根据队列类型传入的参数只和DQF_WIDTH
有关,那么dq_serialnum
是什么呢? -
搜索
_dispatch_queue_serial_numbers
:unsigned long volatile _dispatch_queue_serial_numbers = DISPATCH_QUEUE_SERIAL_NUMBER_INIT; //
- 区分队列吗,还得看
os_atomic_inc_orig
函数的实现:
- 最终得到
C++
方法atomic_fetch_add_explicit
是原子相关操作 ** 总结** -
- 串行队列:
DQF_WIDTH(1)
- 串行队列:
-
- 全局队列:
DQF_WIDTH(DISPATCH_QUEUE_WIDTH_FULL - 1)
- 全局队列:
-
- 创建的并发队列:
DQF_WIDTH(DISPATCH_QUEUE_WIDTH_FULL - 2)
- 创建的并发队列:
队列的继承
队列的继承关系:dispatch_queue_t
: dispatch_queue_s
: dispatch_object_s