OC底层探索 – 多线程 & GCD

线程与进程

线程

线程是进程的基本执行单元,一个进程的所有任务都在线程中执行

  • 进程要想执行任务,就必须有线程,一个进程至少要有一条线程
  • 程序启线程撕裂者动会默认开启一条线程,这条线程被称为主线程或UI线程

进程

进程是指系统中正在运行的一个应用程序

  • 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内

关系线程数是什么

  • 一个线程可以创建和撤销另一个线程;同一Apple进程中的多个线程之间可以并发执行
  • 环路复杂度对进程而言,线程是一个更加接近执行体的概念,它可以与进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
  • 在操作系统中能同时运行多线程安全个进程,而同一个进程中可以有多个线程同时执行(CPU调度)
  • 同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间
  • 同一进程内的线程共享本进程的资源,比如内存、I/O、CPU环形复杂度等;但是进程之接口文档间的资源是独立的。
  • 每一个独立的进程都有一个程序运行的入口、顺序执行线程的几种状态序列和程序入口。而线程不能独立运行,必须依存在进程中,由进程提供多个线程的执行控制。
  • 进程是操作系统进行资源分配的基本单位,线程是操作系统进行任务调度和执行的最小单位。

线程操作系统的基本特征调度与时间片

[NSProcessInfo processInfo]可以查看CPU信息,[NSProcessInfo processInfo].activePro线程和进程的区别是什么cessorCount可用核心数量

一个CPU核线程是什么意思心同一时刻只能执行一个线程。当线程数量超过CPU核心数量时,一个CPU核心往往就处理多个线程,这个行为就叫做 线程调度。就是一个CPU核心轮流让各个线程分别执行一段时间。CPU在多个线程任务中进行快速的切apple id换,这个时间间隔就是 时间片

通过线程调度,我们可以知道问什么不推荐我们开操作系统是一种辟过多的线程了

  1. CPU核心有限,而线程调度时的线程切换是耗时的,每次切换耗时90微秒,开辟过多的线程会增加线程切换次数,产生额外的时间消耗,不但会消耗大量CPU资源,也会降低执时间复杂度行效率。 (主要原因)
  2. 主线程大小 1M,其他线程大小 512KB,开辟线程越多,消耗内存越多。 (这个倒是不太Apple重要)

线程生命周期 与 线程池

OC底层探索 - 多线程 & GCD

OC底层探索 - 多线程 & GCD

GCD 的线程池有 64 条线程

饱和策略

  • AbortPolicy 直接抛出RejectedExecutionExeception异常来阻止系统正常运行
  • CallerRunsPolicy 将任务回退操作系统的主要功能是到调用者
  • DisOldestPolicy 丢掉等待最久的任务
  • DisCardPolicy 直接丢弃任务

这四线程池种拒绝策略均实现的RejectedExecutionHandler接口

GCD

GCD 全称是 G操作系统当前的配置不能运行此应用程序rand Central Dispatch, 它由纯 C 语言编写,提供了非常多强大的软件复杂度函数。

GCD 是苹果公司复杂度怎么计算的线程的几种状态多核的并行运算提出的解决方案,它会自动利用更多的CPUapple watch内核(比如双接口crc错误计数核、四核)。

GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程),程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。

GCD的源码在libdispatchapple官网。 Apple Open Source

串行/并发队列 同步接口类型/异步函数

队列只是用来存储任务的,它自身没有能力来调度任务。调度任务只能依赖于线程。

同步函数特点:时间复杂度

  • 阻塞当前线程进行等待,直到当前添加到队列的任务执行完apple store
  • 只能在当前线程执行任务,不具备开启新线程的能力

异步函数特点:

  • 不会阻塞线程,不需要等待,任务可以继续执行
  • 可以在新的线程执行任务,具备开启新线程的能力。(并发队列可以开启多条子线程,串行队 列只能开启操作系统是一种一条子线程)
串行队列
DISPATCH_QUEUE_S接口crc错误计数ERIAL
并发队列
DISPATCH_QUEUE_C接口测试ON线程安全CURRENT
同步函数
dispatch_sync
不会开启线程,在当前线程执行任务
任务按添加顺序串行执行
有产生阻塞的可能
不会开启线程,在当前线线程数是什么程执行任务
任务按添加顺序串行执行
异步函数
dispatch_async
开启一条新线程,在新线程执行任务
任务按添apple id加顺序串行执行
同一队列多次调用异步函数也只会开辟一条新线程
开启一条新线程,在新线程执行任务
任务异步执行,没有顺序,与CPU调度有关
同一队列多次调用异步函数会开辟多条新线程

串行 + 同步 阻塞示例:

OC底层探索 - 多线程 & GCD

队列

队列的作操作系统是一种什么软件用是用来存储任务。队列分类串行队列和并发队列。串行队列和并发队列都是FIFO,也就是先进先出的数据结构。

串行队列:它的DQF_WIDTH等于1,相当以它只有一条通道。所以队列中的任务要串行执行,也就是一个一个的执行,必须等上一个任务执行完成之后才能开始下一个,而且一定是按照先进先出的顺序执行的,比如串行队列里面有4个任务,进入队列的顺序是a、b环路复杂度、c、d,那么一定是先执行 a,并且等任务 a 完成之后,再执行 b …。

并发队列:它的DQF_WIDTH大于1,相当于空间复杂度有多条通道。队列中的任务可以并发执行,也就任务可以同时执行,比如并发队列里面有4个任务,进入队圈复杂度列的顺序是a、b、c、d,那么一定是先执行a,再执行b…,也是按照先进先出的原则调接口是什么用的,但是执行 b 的时候 a不一定执行完成环路复杂度,而且 a 和 b 具体哪个先执行完操作系统的五大功能成是接口文档不确定的。通道有很多,哪个任务先执行完得看任务的复杂度,以及CPU的调度情况。

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);
}
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);
    /*
        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
        // 当是并发队列时,dqai_concurrent = true, 这个下面会用到
        if (dqa == &_dispatch_queue_attr_concurrent) {
        dqai.dqai_concurrent = true;
        return dqai;
        }
    */
    // Step 1: Normalize arguments (qos, overcommit, tq)
    // 我们略过 Step 1 部分代码
    //
    // 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_WIDTH_MAX ,如果是串行队列传1
    _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;
}
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_WIDTH(width) 为 DQF_WIDTH(1), 
    // 所以DQF_WIDTH(1)可以看做串行队列的一个标志
    // DQF_WIDTH(n), 可以理解为队列出口宽度
    dqf |= DQF_WIDTH(width); 
    os_atomic_store2o(dq, dq_atomic_flags, dqf, relaxed);
    dq->dq_state = dq_state;
    dq->dq_serialnum =
    // 我们全局搜索 _dispatch_queue_serial_numbers, 可以找到下面代码
    // 说明这是队列的一个标志
    os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);
    return dqu;
}
// skip zero
// 1 - main_q 主队列
// 2 - mgr_q
// 3 - mgr_root_q
// 4 - 21 - global queues 全局队列
// 22 - workloop_fallback_q 用户自己创建的队列
// we use 'xadd' on Intel, so the initial value == next assigned
#define DISPATCH_QUEUE_SERIAL_NUMBER_INIT 22
extern unsigned long volatile _dispatch_queue_serial_numbers;

主队列

dispatch_get_main_queue()

/// 打印主队列名字
NSLog(@"%@", dispatch_get_main_queue());// <OS_dispatch_queue_main: com.apple.main-thread>
dispatch_get_main_queue(void)
{
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}
/*
关于 DISPATCH_GLOBAL_OBJECT 这是一个宏定义,
在源码中存在多份,我们通过打印主队列的名字,反向来确认
*/
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,
};
// DQF_WIDTH(1) 上面确认这是串行队列的一个标记, 所以主队列是串行队列

死锁

OC底层探索 - 多线程 & GCD

我们知道主线程加同步函数会造成死锁,那么什么情况会造成死锁呢?

当前串行环路复杂度队列 任务执行中 同步当前串行队列 添加任务 会引发死锁,原因是同一队列中的2个任务相互等待

OC底层探索 - 多线程 & GCD

OC底层探索 - 多线程 & GCD

OC底层探索 - 多线程 & GCD

OC底层探索 - 多线程 & GCD

GCD 单例

GCD单例的使apple watch

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});

源码分析

void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
    dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
    // 把 dispatch_once_t 强转成 dispatch_once_gate_t 类型
    dispatch_once_gate_t l = (dispatch_once_gate_t)val;
#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
    // 如果状态为DLOCK_ONCE_DONE,表示block已经执行过,所以直接返回
    if (likely(v == DLOCK_ONCE_DONE)) {
        return;
    }
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    if (likely(DISPATCH_ONCE_IS_GEN(v))) {
        return _dispatch_once_mark_done_if_quiesced(l, v);
    }
#endif
#endif
    // 判断 block 是否未执行过(DLOCK_ONCE_UNLOCKED),
    // 未执行则执行一次 block, 并标记状态为 DLOCK_ONCE_DONE
    if (_dispatch_once_gate_tryenter(l)) {
        return _dispatch_once_callout(l, ctxt, func);
    }
    // 如果 block 正在执行中,则会一直等待
    // 这里面有一个 for (;;) 死循环,循环中不断查询状态,直到为 DLOCK_ONCE_DONE return
    return _dispatch_once_wait(l);
}

概括来说就是通过状态操作系统的五大功能的判断使得block只被调用一次.

栅栏函数

dispatch_queue_t t = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
// 异步栅栏函数
dispatch_barrier_async(t, ^{
});
// 同步栅栏函数
dispatch_barrier_sync(t, ^{
});

栅栏函数的效果

等待栅栏函数前添加到队列里面的任务全部执行完成之后,才apple官网会执行栅栏函数里 面的任务,栅栏函数里面的任务执行完成之后才会执行栅栏函数后面的队列里面的任务。操作系统是计算机系统中的

需要注意的点

  • 栅栏函数只对同一队列起作用。
  • 栅栏函数对全局并发队列无效。(因为系统的一下操作也在使用这个队列,所以不能让这个队列能够被设置栅栏)

调度组

调度组的效果:等待调度组前面的任务执行完才会执行 dispatch_group_notify操作系统的五大功能数里面的任务。

调度组和队列没有关系,只要是同一调度组就可以。

使用示例:

// 创建调度组
dispatch_group_t g = dispatch_group_create();
dispatch_queue_t q1 = dispatch_queue_create("q1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t q2 = dispatch_queue_create("q2", DISPATCH_QUEUE_CONCURRENT);
// 加入任务到调度组
dispatch_group_async(g, q1, ^{
    NSLog(@"1");
});
/* 上面添加代码也可以这样写
    // 入组,调用此函数会增加组中未完成任务的当前计数
 dispatch_group_enter(g);
    dispatch_async(q1, ^{
        NSLog(@"1");
        // 执行完出组,调用此函数会减少组中未完成任务的当前计数
        dispatch_group_leave(g);
  );
    所以,enter 和 leave 必须成对使用,enter 多了 notify 不会执行,leave 多了会崩溃
    为了减少不成对的错误,苹果提供了上面简化的方式,其内部封装了 enter 和 leave
*/
dispatch_group_async(g, q2, ^{
    NSLog(@"2");
});
dispatch_group_async(g, dispatch_get_global_queue(0, 0), ^{
    NSLog(@"3");
});
dispatch_group_async(g, dispatch_get_main_queue(), ^{
    NSLog(@"4");
});
// 当调度组内任务都执行完后,会执行这里的 block
dispatch_group_notify(g, dispatch_get_main_queue(), ^{
    NSLog(@"5");
});

信号量

// 这个函数是创建一个dispatch_semaphore_t类型的 信号量,并且创建的时候需要指定信号量的大小
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
// 发送信号量。该函数会对信号量的值进行 +1 操作
dispatch_semaphore_signal(sem);
// 等待信 号量。如果信号量值 == 0,那么该函数就会一直等待,也就是不返回(相当于阻塞当前线程),
// 直到该函数等待的信号量的值 >= 1,该函数会对信号量的值进行 -1 操作,然后返回。
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

使用信号量可以控制并发数量

// 创建一个 信号量 = 1 的信号量
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
// 1
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"1");
    // 3
    dispatch_semaphore_signal(sem);
});
// 2
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"2");
    // 4
    dispatch_semaphore_signal(sem);
});
这里2个异步函数一定是顺序执行的,因为
在 1 位置,原始信号量 = 1,可以执行,并且信号量 - 10
所以 2 位置,信号量 = 0, 阻塞线程,等待
等 3 位置执行完后,信号量 +1 = 12 位置可以继续执行了, 所以才能走到 4 位置

需要注意的是,在信号量释放时,通过 dispa接口测试用例设计tch_semaphore_waitdispatch_semaphore_signa操作系统是什么的接口l 改变之后的信号量大小如果小于创建时的初始信号量大小就会触发崩溃。线程的几种状态

如果我们直接让 dispatch_semaphore_waitdispatch_semaphore_signal 成对出现,接口和抽象类的区别一定可以避免信号量释放崩溃的问题出现。

// 创建信号量
dispatch_semaphore_t
dispatch_semaphore_create(intptr_t value)
{
    dispatch_semaphore_t dsema;
    // If the internal value is negative, then the absolute of the value is
    // equal to the number of waiting threads. Therefore it is bogus to
    // initialize the semaphore with a negative value.
    // 这个判断说明,创建信号量时,初始信号量的值不能 < 0
    if (value < 0) {
        return DISPATCH_BAD_INPUT;
    }
    dsema = _dispatch_object_alloc(DISPATCH_VTABLE(semaphore),
    sizeof(struct dispatch_semaphore_s));
    dsema->do_next = DISPATCH_OBJECT_LISTLESS;
    dsema->do_targetq = _dispatch_get_default_queue(false);
    // 这个是存放信号量的当前值, 为什么能确定这个是当前值呢?
    // 因为在 dispatch_semaphore_wait 和 
    // dispatch_semaphore_signal 中操作的都是这个值
    dsema->dsema_value = value;
    _dispatch_sema4_init(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
    // 这个是信号量的初始值
    dsema->dsema_orig = value;
    return dsema;
}
// 信号量的释放方法
void
_dispatch_semaphore_dispose(dispatch_object_t dou,
DISPATCH_UNUSED bool *allow_free)
{
dispatch_semaphore_t dsema = dou._dsema;
// 这里说明了,当信号量当前值 < 信号量初始值 时 会崩溃
if (dsema->dsema_value < dsema->dsema_orig) {
    DISPATCH_CLIENT_CRASH(dsema->dsema_orig - dsema->dsema_value,
        "Semaphore object deallocated while in use");
}
_dispatch_sema4_dispose(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
}

dispat复杂度怎么计算的ch_source

dispatch_source 是用来监听事件的,可以创建不同类型的dispatch_source来监听不同的事件。

dispatch_source可以监听的事件类型:

事件作用
DI操作系统的主要功能是SPATCH_SOURCE_TYPE_DA接口文档TA_ADD自定义事件,变量add
DISPATCH_SOURCE_TYPE_DATA_OR自定义事件,变量or
DISPATCH_SOURCE_TYPE_DATA_REPLAC接口类型E自定义事件,变量replace。如果传入数据为0,将不会发出handler
DISPATCH_SOURCE_TYPE_MACH_SEND监听Mach port的deadname通知,hander是具有send权限的Mach接口文档 port,包括send和send_o接口测试用例设计nce
DISPAAppleTC操作系统是一种什么软件H_SOURCE_TYPE_MACH_RECV监听Mach port获取等待处理的消息
DISPATCH_SOURCE_TYPE_MEMOR接口测试YPRESSURE监听系统中的内存压力
DISPATCH_SOURCE_TYPE_PROC监听进程事件
DISPATCH_SOURC接口测试E_TYPE_READ监听文件描述符是否有可读数据
DISPATCH_SOURCE_TYPE_SIG劳动复杂度NAL监听当前进程的signal
DISPATCH_SOURCE_TYPE_TIMER接口英文时器监听
DISPATCH_SOURCE_TYPE_VNODE监听文件描述符事件
D圈复杂度ISPATCH_SOURCE_操作系统是一种TYP操作系统是什么的接口E_WRITE监听文件描述符使用可用apple id的buffer空间来写数据

dispatch_sour操作系统是什么的接口ce的具体用法:

在任一线程上调用它的dispatch_source_merge_data函数,会执行dispatch_source事先接口文档定义好的句柄(可以把句柄简单理解为一个block)。

dispatch_source的几个方法:

typedef unsigned long  uintptr_t;
// 创建源
dispatch_source_t dispatch_source_create(dispatch_source_type_t type,
                                        uintptr_t handle,
                                        uintptr_t mask,
                                        dispatch_queue_t _Nullable queue);
// 设置源事件回调
void dispatch_source_set_event_handler(dispatch_source_t source,
                                       dispatch_block_t _Nullable handler);
// 源事件设置数据
void dispatch_source_merge_data(dispatch_source_t source, 
                                uintptr_t value);
// 获取源事件数据
uintptr_t dispatch_source_get_data(dispatch_source_t source);
// 继续源事件
void dispatch_resume(dispatch_object_t object);
// 挂起源事件
void dispatch_suspend(dispatch_object_t object);
// 取消源事件
void dispatch_source_cancel(dispatch_source_t source);
定时器示例
// 定时器使用 --- 倒计时
__block int timeout = 60;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
    if(timeout <= 0){
        dispatch_source_cancel(_timer);
    }
    else{
        timeout--;
        NSLog(@"倒计时:%d", timeout);
    }
});
dispatch_resume(_timer);

发表评论

提供最优质的资源集合

立即查看 了解详情