进程
是指在系统中正在运行的应用程序,每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内。
线程
是进程的基本执行单元,一个进程的所有任务都在线程中执行。进程执行任务,必须要有线程,进程至少要有一个线程。程序启动会默认开启一条线程,这条线程被称为主线程或者UI线程。
线程与进程间的关系
- 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
- 相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
- 所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)
- 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
- 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
- 执行过程:每个独立的进程程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
- 根本区别:进程是操作系统进行资源分配的基本单位,而线程是操作系统进行任务调度和执行的最小单位。
时间片的概念:CPU在多个任务直接进行快速的切换,这个时间间隔就是时间片
(单核CPU)同一时间,CPU 只能处理 1 个线程
换言之,同一时间只有 1 个线程在执行
-
多线程同时执行:
- 是 CPU 快速的在多个线程之间的切换
- CPU 调度线程的时间足够快,就造成了多线程的“同时”执行的效果
-
如果线程数非常多
- CPU 会在 N 个线程之间切换,消耗大量的 CPU 资源
- 每个线程被调度的次数会降低,线程的执行效率降低
-
优点
- 能适当提高程序的执行效率
- 能适当提高资源的利用率(CPU,内存)
- 线程上的任务执行完成后,线程会自动销毁
-
缺点
- 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512 KB)
- 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
- 线程越多,CPU 在调用线程上的开销就越大
- 程序设计更加复杂,比如线程间的通信、多线程的数据共享
因为线程本身也会占用内存空间,开启一条线程系统需要大约90ms,所以开启过多的线程没有意义,可以通过如下方式获取设备能够支持线程的最大的并发数量
func test(){
print("\(ProcessInfo.processInfo.activeProcessorCount)")
}
线程池
通过打印主线程,number = 1,那么理论上来讲,后面新建的number应该一次增加
<NSThread: 0x100729d80>{number = 2, name = (null)}
<NSThread: 0x100729d80>{number = 2, name = (null)}
<NSThread: 0x100729d80>{number = 2, name = (null)}
<NSThread: 0x101023a20>{number = 3, name = (null)}
<NSThread: 0x101023a20>{number = 3, name = (null)}
<NSThread: 0x100729d80>{number = 2, name = (null)}
<NSThread: 0x101023a20>{number = 3, name = (null)}
<NSThread: 0x10072a110>{number = 4, name = (null)}
<NSThread: 0x100729d80>{number = 2, name = (null)}
<NSThread: 0x101023a20>{number = 3, name = (null)}
通过for循环创建队列,打印对应的线程,发现number是有重复的,这是因为内部维护了一个线程池,线程不是随用就创建的,在核心线程池里去取空闲的线程,复用线程。
线程池的工作原理如下:
- 使用线程执行任务的时候,需要到线程池中去取线程进行任务分配
- 首先判断线程池大小是否小鱼核心线程池大小,如果小于的话,创建新的线程执行任务。
- 如果当前线程池大小大于核心线程池大小,然后开始判断工作队列是否已满,如果没满,将任务提交到工作队列
- 如果工作队列已满,判断线程池的线程是否都在工作,如果有空闲线程没有在工作,就交给它去执行任务。
- 如果线程池中的线程都在工作,那么就交给饱和策略去执行
饱和策略
AbortPolicy 直接抛出RejectedExecutionExeception异常来阻止系统正常运行
CallerRunsPolicy 将任务回退到调用者
DisOldestPolicy 丢掉等待最久的任务
DisCardPolicy 直接丢弃任务
这四种拒绝策略均实现的RejectedExecutionHandler接口
GCD
全称是 Grand Central Dispatch,纯 C 语言,提供了非常多强大的函数
- GCD的优势
*GCD 是苹果公司为多核的并行运算提出的解决方案- GCD 会自动利用更多的CPU内核(比如双核、四核)
- GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码
GCD源码探究
搜索dispatch_queue_create方法,继续调用了_dispatch_lane_create_with_target
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_NOINLINE
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
dispatch_queue_t tq, bool legacy)
{
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
//
// Step 1: Normalize arguments (qos, overcommit, tq)
//
dispatch_qos_t qos = dqai.dqai_qos;
#if !HAVE_PTHREAD_WORKQUEUE_QOS
if (qos == DISPATCH_QOS_USER_INTERACTIVE) {
dqai.dqai_qos = qos = DISPATCH_QOS_USER_INITIATED;
}
if (qos == DISPATCH_QOS_MAINTENANCE) {
dqai.dqai_qos = qos = DISPATCH_QOS_BACKGROUND;
}
........
//
// Step 2: Initialize the queue
//
if (legacy) {
// if any of these attributes is specified, use non legacy classes
if (dqai.dqai_inactive || dqai.dqai_autorelease_frequency) {
legacy = false;
}
}
........
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;
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;
}
根据注释来看第一步就是根据传过来的dqa进行设置各种参数封装为为dqai。第二步_dispatch_queue_init初始化队列,根据参数dqai的dqai_concurrent,来决定传递参数是1还是大于1,这个参数就是DQF _WIDTH,串行队列(还有主队列)就是DQF_WIDTH(1),即DQF_WIDTH(1)就是串行队列或者主队列,而_dispatch_queue_serial_numbers这个参数代表如下:
// skip zero
// 1 - main_q
// 2 - mgr_q
// 3 - mgr_root_q
// 4,5,6,7,8,9,10,11,12,13,14,15 - global queues
// 17 - workloop_fallback_q
// we use 'xadd' on Intel, so the initial value == next assigned
DQF_WIDTH(1)串行队列,相当于车道中的单行道
DQF_WIDTH(大于1)并行队列,相当于车道中的多行道
无论串行还是并行列,都是FIFO先进先出的结构。当是串行队列的时候,每个任务先放进去必定先执行。并行队列,因为多车道,就不一定了,因为每个任务执行的时间,而且有很多个通道,就不一定是按照顺序先执行。串行就像银行办理业务只有一个窗口,无论前面任务有多久,必须要等待然后才能执行。并行就是有多个窗口,比如有4个窗口,并发执行四个任务,如果2号任务先执行完毕,则5号就去2号窗口执行,执行完成的时间按照各自的任务复杂度。
一般队列配合函数一起使用
函数与队列
-
同步函数串行队列
- 不会开启线程,在当前线程执行任务
- 任务串行执行,一个接着一个
- 会产生阻塞
-
同步函数并发队列
- 不会开启线程,在当前线程执行任务
- 任务一个接着一个
-
异步函数串行队列
- 开启一条新线程
- 任务一个接着一个
-
异步函数并发队列
- 开启新线程
- 任务异步执行,没有顺序,跟CPU的调度有关