线程和进程的概念
线程的概念
线程是进程的基本执行单元,一个进程的所有任务都在线程中执行。
进程要想执行任务,必须得有线程,进程至少要有一条线程。
程序启动会默认开启一条线程,这条线程被称为主线程或UI线程。
进程的概念
进程是指在系统中正在运行的一个应用程序。
每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内。
通过“活动监视器”可以查看 Mac 系统中所开启的进程。
线程和进程的关系
- 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
- 相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
- 所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)
- 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
- 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
- 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
- 根本区别:进程是操作系统进行资源分配的基本单位,而线程是操作系统进行任务调度和执行的最小单位。
时间片
CPU在多个任务直接进行快速的切换,这个时间间隔就是时间片
单核CPU同一时间,CPU 只能处理 1 个线程,换言之,同一时间只有 1 个线程在执行。而多线程同时执行本质是CPU快速的在多个线程之间的切换,CPU调度线程的时间足够快,就造成了多线程的“同时”执行的效果。
但如果线程数非常多,CPU 会在 N 个线程之间切换,消耗大量的 CPU 资源,每个线程被调度的次数会降低,线程的执行效率降低。
多线程的优缺点
优点
- 能适当提高程序的执行效率
- 能适当提高资源的利用率(CPU,内存)
- 线程上的任务执行完成后,线程会自动销毁
缺点
- 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512 KB(主线程1M),耗时约90微秒)
- 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
- 线程越多,CPU 在调用线程上的开销就越大
- 程序设计更加复杂,比如线程间的通信、多线程的数据共享
线程的生命周期
当一个线程被创建时,它并不会立即执行,而是除了一个Runnable的状态,当start时,通过cpu调度当前线程,才使线程真正执行。当线程执行完任务后,它就会自己消亡,当出现一些同步锁或Sleep时,线程就会被阻塞,当cpu去调度其他线程时,当前线程又会回到Runnable状态。
线程池
线程池饱和策略
- AbortPolicy 直接抛出RejectedExecutionExeception异常来阻止系统正常运行
- CallerRunsPolicy 将任务回退到调用者
- DisOldestPolicy 丢掉等待最久的任务
- DisCardPolicy 直接丢弃任务
这四种拒绝策略均实现的RejectedExecutionHandler接口
GCD
GCD全称是 Grand Central Dispatch,纯C语言实现,提供了非常多强大的函数。GCD 会自动利用更多的CPU内核(比如双核、四核),自动管理线程的生命周期(创建线程、调度任务、销毁线程),程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。
队列和线程
队列是先进先出的(FIFO),并且有串行队列、并行队列、全局并发队列、主队列。队列是用来存储任务的,通过cpu调度将队列中的任务放到线程中去执行。
源码
dispatch_get_main_queue()
我们先查看dispatch_get_main_queue函数的实现。
dispatch_queue_main_t dispatch_get_main_queue(void)
{
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}
dispatch_get_main_queue调用了DISPATCH_GLOBAL_OBJECT,但是经过搜索发现GCD源码中有多个DISPATCH_GLOBAL_OBJECT宏定义,我们并不能单纯地通过查看源码来定位到代码的走向。
但是我们在通过NSLog("%@", dispatch_get_main_queue())时,打印了如下的内容。
我们尝试在源码中搜索
com.apple.main-thread,最终发现了有这一段代码。
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,
};
这段代码定义了了一个dispatch_queue_static_s结构体类型的值_dispatch_main_q,而_dispatch_main_q刚刚出现在了dispatch_get_main_queue源码中作为DISPATCH_GLOBAL_OBJECT的参数。
接下来我们先查看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_queue_create里调用了_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)
{
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
//
// Step 1: Normalize arguments (qos, overcommit, tq)
//
// 规范化一些参数,省略
//
// 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;
}
}
const void *vtable;
dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
if (dqai.dqai_concurrent) {
vtable = DISPATCH_VTABLE(queue_concurrent);
} else {
vtable = DISPATCH_VTABLE(queue_serial);
}
switch (dqai.dqai_autorelease_frequency) {
case DISPATCH_AUTORELEASE_FREQUENCY_NEVER:
dqf |= DQF_AUTORELEASE_NEVER;
break;
case DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM:
dqf |= DQF_AUTORELEASE_ALWAYS;
break;
}
if (label) {
const char *tmp = _dispatch_strdup_if_mutable(label);
if (tmp != label) {
dqf |= DQF_LABEL_NEEDS_FREE;
label = tmp;
}
}
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;
}
这个函数的核心就是传入的第二个函数:队列的类型,根据传入的类型来创建不同队列,所以我们重点查看第二个参数。在函数实现的首行,
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
我们就看到了该函数把传入的第二个参数dqa封装成了dqai,那我们看看找个封装做了什么事情。
dispatch_queue_attr_info_t _dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa)
{
dispatch_queue_attr_info_t dqai = { };
if (!dqa) return dqai;
#if DISPATCH_VARIANT_STATIC
if (dqa == &_dispatch_queue_attr_concurrent) {
dqai.dqai_concurrent = true;
return dqai;
}
#endif
// 省略不关注的代码
return dqai;
}
typedef struct dispatch_queue_attr_info_s {
dispatch_qos_t dqai_qos : 8;
int dqai_relpri : 8;
uint16_t dqai_overcommit:2;
uint16_t dqai_autorelease_frequency:2;
uint16_t dqai_concurrent:1;
uint16_t dqai_inactive:1;
} dispatch_queue_attr_info_t;
我们可以看到,里面先是初始化了一个dispatch_queue_attr_info_t结构体类型的变量dqai,但是并没有赋值,如果传入的参数dqa为空,则直接返回dqai,而dqai中的dqai_concurrent成员变量默认为0,所以如果不传第二个参数,默认返回的是串行队列。如果传入的参数是_dispatch_queue_attr_concurrent,则返回的是并行队列。
现在接下去看_dispatch_lane_create_with_target函数中Step 2的代码,其中有一个_dispatch_queue_init函数调用。
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));
第三个参数为是否为concurrent,如果是值为DISPATCH_QUEUE_WIDTH_MAX,否则为1,那我们看一下这个宏的定义。
#define DISPATCH_QUEUE_WIDTH_FULL 0x1000ull
#define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1)
#define DISPATCH_QUEUE_WIDTH_MAX (DISPATCH_QUEUE_WIDTH_FULL - 2)
那么,如果是串行队列的话,第三个参数就为1,如果是并行队列,第三个参数就为8。那接下来就看看_dispatch_queue_init里面做了什么事情。
static inline dispatch_queue_class_t
_dispatch_queue_init(dispatch_queue_class_t dqu, dispatch_queue_flags_t dqf,
uint16_t width, uint64_t initial_state_bits)
{
uint64_t dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(width);
dispatch_queue_t dq = dqu._dq;
dispatch_assert((initial_state_bits & ~(DISPATCH_QUEUE_ROLE_MASK |
DISPATCH_QUEUE_INACTIVE)) == 0);
if (initial_state_bits & DISPATCH_QUEUE_INACTIVE) {
dq->do_ref_cnt += 2; // rdar://8181908 see _dispatch_lane_resume
if (dx_metatype(dq) == _DISPATCH_SOURCE_TYPE) {
dq->do_ref_cnt++; // released when DSF_DELETED is set
}
}
dq_state |= initial_state_bits;
dq->do_next = DISPATCH_OBJECT_LISTLESS;
dqf |= DQF_WIDTH(width);
os_atomic_store2o(dq, dq_atomic_flags, dqf, relaxed);
dq->dq_state = dq_state;
dq->dq_serialnum = os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);
return dqu;
}
我们继续关注第三个参数width,那么理所当然就注意到这一行dqf |= DQF_WIDTH(width);。当串行队列时,这行代码就是dqf |= DQF_WIDTH(1);,这就与_dispatch_main_q里 .dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),调用的函数和参数是一样的,这也说明了主队列也是一个串行队列。
面试题
dispatch_queue_t queue = dispatch_queue_create("queue_name", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
执行顺序为1-5-2-3-4。首先1肯定是先打印的,然后在queue中异步添加任务,然后再把5放到主线程的队列中。2和5的执行顺序不一定,要看cpu的调度情况。在2之后,又往queue中同步添加了3,因为是同步的,所以4一定会等3先执行完再执行。
dispatch_queue_t queue = dispatch_queue_create("queue_name", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
执行顺序为1-5-2然后死锁。在执行到3的时候,由于是串行队列,并且在queue中将3同步添加到queue,此时3在等4执行,4也在等3执行,造成了死锁。
dispatch_queue_t queue = dispatch_queue_create("queue_name", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
dispatch_async(queue, ^{
sleep(2);
NSLog(@"5");
});
执行的顺序为2-4-5-3。第一个dispatch_asyn先加到queue中,所以2先执行,4和2在同一个线程,所以4之后执行,5和3中间,5先被加入到queue中,所以不管5之前sleep了多久,都是5先执行,然后再执行3。