GCD同步异步及队列的探索(上)

393 阅读13分钟

0a000581fca74098bc53b876f5e957ae.jpeg 持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

GCD概念:

1、GCD是:Grand Central Dispatch 、纯C语言,提供了非常多的强大函数。

2、GCD的优势: GCD是苹果公司为多核的并行运算提出的解决方案、 GCD会自动利用更多的CPU内核(双核、四核) GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)、程序员只需要高度GCD想要执行什么任务,不需要编写任何线程管理代码

3、GCD是将任务添加到队列、并且指定执行任务的函数 任务使用block封装 任务的block没有参数也没有返回值 执行任务的函数 异步 dispatch_async 不用等待当前语句执行完毕,就可以执行下一条语句 会开启线程执行block任务 异步是多线程的代名词 同步dispatch_sync 必须等待当前语句执行完毕,才会执行下一条语句 不会开启线程 在当前执行block的任务

4、串行队列:依次执行、先进先出:FIFO

5、并发队列:并发执行

6、队列与函数搭配

同步函数 dispatch_sync串行队列DISPATCH_QUEUE_SERIAL 不会开启线程,在当前线程执行任务 任务串行执行,任务一个接着一个 会产生堵塞 同步函数dispatch_sync并发队列DISPATCH_QUEUE_CONCURRENT 不会开启线程,在当前线程执行任务 任务一个接着一个 异步函数dispatch_async串行队列DISPATCH_QUEUE_SERIAL 开启一条新线程 任务一个接着一个 异步函数dispatch_async并发队列DISPATCH_QUEUE_CONCURRENT 开启线程,在当前线程执行任务 任务异步执行,没有顺序,CPU调度有关。

7、队列 dispatch_queue

主队列dispatch_get_main_queue() 专门用来在主线程上调度任务的队列 不会开启线程 如果当前主线程正在有任务执行,那么无论主队列中当前被添加来什么任务,都不会被调度 全局队列dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0) 为了方便程序员的使用,苹果提供了全局队列 全局队列是一个并发队列 在使用多线程开发时,如果队列没有特殊需求,在执行异步任务时,可以直接使用全局队列

8、死锁

主线程因为同步函数的原因等着先执行任务.
主队列等着主线程的任务执行完毕再执行自己的任务.
主队列和主线程相互等待会造成死锁.

9.进程和线程的意义

线程数进程的基本执行单元,一个进程的所有任务都在线程中执行
进程要想执行任务,必须得有线程,进程至少要有一条线程
程序启动会默认开启一条线程,这条线程被称为主线程或UI线程\

一. GCD串行和并发队列

1、时间片及线程概念

CPU在多个任务直接进行快速的切换,这个时间间隔就是时间片
(1)(单核CPU)同一时间,CPU只能处理1个线程

  • 换言之,同一时间只有1个线程在执行 (2)多线程同时执行:
  • 是CPU快速的在多个线程之间的切换
  • CPU调度线程的时间足够快,就造成了多线程的“同时”执行的效果 (3)如果线程数非常多
  • CPU会在N个线程之间切换,消化大量的CPU资源
  • 每个线程被调度的次数会降低,线程的执行效率降低
    NSLog(@"%@",[NSThread currentThread]);

    //设备能够支撑线程的最大并发数量

    NSLog(@"%ld",[NSProcessInfo processInfo].activeProcessorCount);
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    for (int i = 0; i < 200; i ++) {

        dispatch_async(dispatch_queue_create("lg", DISPATCH_QUEUE_SERIAL), ^{

            NSLog(@"%@",[NSThread currentThread]);

        });

    }
}

运行代码看下打印: image.png 我们看到最大核数是4,也就是在同一时间可以运行4个线程。 主线程占用1M,子线程占用512kb ,开启一个子线程需要90us(微秒)。 GCD的线程池里,缓存了64条线程。

为什么打印的number数是变化的呢:这个和线程池的概念有关

image.png 饱和策略:

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

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

附上线程的生命周期图:

image.png

2、通过代码例子分析线程

我们之间上代码:

- (void)test1 {
    dispatch_queue_t queue = dispatch_queue_create("ny", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

test1做了什么,通过dispatch_queue_create创建了一个queue的DISPATCH_QUEUE_CONCURRENT并发队列。然后打印1,异步add任务到queue,然后打印5,异步任务中又打印了2,同步任务add到queue中,打印3,然后打印4.我们的推测是:15234,运行test1验证一下:

**2022-05-28 18:51:43.743506+0800 002--GCD面试题[76036:2211317] 1**

**2022-05-28 18:51:43.743710+0800 002--GCD面试题[76036:2211317] 5**

**2022-05-28 18:51:43.743749+0800 002--GCD面试题[76036:2212465] 2**

**2022-05-28 18:51:43.743883+0800 002--GCD面试题[76036:2212465] 3**

**2022-05-28 18:51:43.743990+0800 002--GCD面试题[76036:2212465] 4**

结果和我们预想的一样。
我们来看下test2代码:

- (void)test2 {
    dispatch_queue_t queue = dispatch_queue_create("ny", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

从代码上看,创建了一个串型队列queue,打印了1,添加了异步任务,打印了5(这个5可以在2前,也可以在2后),执行异步任务,打印2,执行添加同步任务,发生死锁。线程卡死。串型队列 一个一个的执行。异步任务未执行完就要马上执行下一个同步任务。发生循环等待,也就是死锁。 image.png 运行验证,与猜想相同。 继续看test3代码:

- (void)test3 {
    self.num = 0;
    while (self.num < 100) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.num ++;
        });
    }
    NSLog(@"self.num = %d",self.num);
}

看代码,一个while判断num开启一个全局并发队列执行异步线程任务num++;然后打印self.num。会打印什么呢,由于while判断会在这里等待num>100,在执行打印所以打印应该是大于等于100的数,根据cpu的性能无限接近100。

**2022-05-28 20:57:59.676954+0800 002--GCD面试题[78606:2283878] self.num = 100**

**2022-05-28 20:58:04.431975+0800 002--GCD面试题[78606:2283878] Writing analzed variants.**

看test4代码:

- (void)test4 {
    self.num = 0;
    for (int i = 0; i < 100; i ++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.num ++;
        });
    }
    NSLog(@"self.num = %d",self.num);
}

和test3没什么区别,唯一不同的是while循环变成了for循环100次。执行了异步全局并发队列,打印这个num应该是一个小于100的数。根据cpu性能不同无限接近100。

**2022-05-28 21:06:59.739533+0800 002--GCD面试题[78803:2289824] self.num = 97**

运行结果97,和我们预想的一样。

3、探索libdispatch队列

查看原代码libdispatch-1271

DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_CONST DISPATCH_NOTHROW
dispatch_queue_main_t
dispatch_get_main_queue(void)
{
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}

image.png DISPATCH_GLOBAL_OBJECT是一个宏定义,在代码中大量使用。 那么我们打印一下dispatch_get_main_queue是什么:

**2022-05-28 21:50:50.535439+0800 002--GCD面试题[80354:2319526] <OS_dispatch_queue_main: com.apple.main-thread>**

我们发现这个命名"com.apple.main-thread"是系统命名的。 image.png 在看看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);
}


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;

}

#endif // !HAVE_PTHREAD_WORKQUEUE_QOS

....省略.....
if (!tq) {

tq = _dispatch_get_root_queue(

qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos,

overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq;

if (unlikely(!tq)) {

DISPATCH_CLIENT_CRASH(qos, "Invalid queue attribute");

}

}


// 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));
//dqai.dqai_concurrent 是并发设置宽度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;

}

image.png 继续跟踪发现_dispatch_queue_init函数初始化队列并设置队列的宽度。 image.png 然后继续探索发现: image.png 1-主队列,2,gcd内部管理队列 3,管理队列的目标队列 4-15,全局并发队列 。之后是手动创建的队列

小结: 队列的作⽤是⽤来存储任务。队列分类串⾏队列和并发队列。串⾏队列和并发队列都是 FIFO ,也 就是先进先出的数据结构。

串⾏队列 :它的DQF_WIDTH等于1,相当以它只有⼀条通道。所以队列中的任务要串⾏执⾏,也就 是⼀个⼀个的执⾏,必须等上⼀个任务执⾏完成之后才能开始下⼀个,⽽且⼀定是按照先进先出的 顺序执⾏的,⽐如串⾏队列⾥⾯有4个任务,进⼊队列的顺序是a、b、c、d,那么⼀定是先执⾏ a,并且等任务a完成之后,再执⾏b... 。

并发队列 :它的DQF_WIDTH⼤于1,相当于有多条通道。队列中的任务可以并发执⾏,也就任务可 以同时执⾏,⽐如并发队列⾥⾯有4个任务,进⼊队列的顺序是a、b、c、d,那么⼀定是先执⾏ a,再执⾏b...,也是按照先进先出(FIFO, First-In-First-Out)的原则调⽤的,但是执⾏b的时候a 不⼀定执⾏完成,⽽且a和b具体哪个先执⾏完成是不确定的。通道有很多,哪个任务先执⾏完得看 任务的复杂度,以及cpu的调度情况。

二. GCD死锁及同步,异步函数

1、死锁的理解

我们来看一段代码:

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_sync(dispatch_get_main_queue(), ^{

        
    });
}

这段代码很明显产生了死锁。死锁的原因是什么?dispatch_get_main_queue是串型队列,出入口只有一个,dispatch_sync需要等待viewDidLoad执行完,viewDidLoad又需要等待dispatch_sync同步函数执行完,相互等待产生死锁image.png 运行看到报错在__DISPATCH_WAIT_FOR_QUEUE__的函数中,我们通过源码libdispatch-1271进行跟踪分析。 image.png 我们看到_dq_state_drain_locked_by 这个函数为真时才会进入CRASH。看看_dq_state_drain_locked_by源码做了什么。

static inline bool
_dq_state_drain_locked_by(uint64_t dq_state, dispatch_tid tid)
{
return _dispatch_lock_is_locked_by((dispatch_lock)dq_state, tid);
}

static inline bool
_dispatch_lock_is_locked_by(dispatch_lock lock_value, dispatch_tid tid)
{
//lock_value 代表队列,tid 当前线程ID
// equivalent to _dispatch_lock_owner(lock_value) == tid
// 0 & DLOCK_OWNER_MASK ((dispatch_lock)0xfffffffc) 很大数字  ==0 才会成立
// 当前队列==当前线程 ^  = 0
return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}

总结死锁结论:在当前线程(和当前队列相关的)同步的向串型队列里面添加任务,就会死锁

我们在来看第二个例子(会死锁?):

dispatch_queue_t t = dispatch_queue_create("ny", DISPATCH_QUEUE_SERIAL);


    dispatch_sync(t,^{

        dispatch_sync(dispatch_get_main_queue(), ^{

            

        });
    });

答应是会死锁,为什么呢?用我们的结论来表达,就是当前线程向主队列(串型)同步添加任务,就产生死锁。dispatch_sync(t)也是在主线程中,所以向主队列添加任务产生死锁。

2、同步函数

进入libdispatch-1271源码分析:

void
dispatch_sync(dispatch_queue_t dq, dispatch_block_t work)
{
//任务的执行时机 -- 关于线程相关的操作
uintptr_t dc_flags = DC_FLAG_BLOCK;

if (unlikely(_dispatch_block_has_private_data(work))) {

return _dispatch_sync_block_with_privdata(dq, work, dc_flags);

}

_dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}

#define _dispatch_Block_invoke(bb) \

((dispatch_function_t)((struct Block_layout *)bb)->invoke)

struct Block_layout {

    void *isa;

    volatile int32_t flags; // contains ref count

    int32_t reserved; 

    void (*invoke)(void *, ...);

    struct Block_descriptor_1 *descriptor;

    // imported variables

};

感觉探索到了block,代码很多我们后面在研究。

继续看_dispatch_sync_f->_dispatch_sync_f_inline代码

static void
_dispatch_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func,
uintptr_t dc_flags)
{

_dispatch_sync_f_inline(dq, ctxt, func, dc_flags);

}

static inline void
_dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func, uintptr_t dc_flags)
{

if (likely(dq->dq_width == 1)) {

return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);

}


if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {

DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");

}


dispatch_lane_t dl = upcast(dq)._dl;

// Global concurrent queues and queues bound to non-dispatch threads

// always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE

if (unlikely(!_dispatch_queue_try_reserve_sync_width(dl))) {

return _dispatch_sync_f_slow(dl, ctxt, func, 0, dl, dc_flags);

}

if (unlikely(dq->do_targetq->do_targetq)) {

return _dispatch_sync_recurse(dl, ctxt, func, dc_flags);

}

_dispatch_introspection_sync_begin(dl);

_dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(

_dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));

}

我进行符号断点来分析,从源码中知道了 _dispatch_barrier_sync_f,_dispatch_sync_f_slow _dispatch_sync_recurse,_dispatch_sync_invoke_and_complete 运行demo来看看,是走那个函数。

添加测试代码:dispatch_sync(dispatch_get_global_queue(0, 0), ^{}); image.png_dispatch_sync_f_slow->_dispatch_sync_function_invoke,_dispatch_sync_complete_recurse,_dispatch_sync_invoke_and_complete_recurse继续跟踪。 image.png 进入_dispatch_sync_function_invoke源码:

static inline void
_dispatch_sync_function_invoke_inline(dispatch_queue_class_t dq, void *ctxt,
dispatch_function_t func)
{

dispatch_thread_frame_s dtf;

_dispatch_thread_frame_push(&dtf, dq);

_dispatch_client_callout(ctxt, func);//立即执行

_dispatch_perfmon_workitem_inc();

_dispatch_thread_frame_pop(&dtf);

}

DISPATCH_NOINLINE
static void
_dispatch_sync_function_invoke(dispatch_queue_class_t dq, void *ctxt,
dispatch_function_t func)
{
_dispatch_sync_function_invoke_inline(dq, ctxt, func);
}

void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
_dispatch_get_tsd_base();
void *u = _dispatch_get_unwind_tsd();
if (likely(!u)) return f(ctxt);
_dispatch_set_unwind_tsd(NULL);
f(ctxt);//立即执行
_dispatch_free_unwind_tsd();
_dispatch_set_unwind_tsd(u);
}

用bt来看堆栈信息: image.png 同步函数的特点

  1. 阻塞当前线程进⾏等待,直到当前添加到队列的任务执⾏完成。

  2. 只能在当前线程执⾏任务,不具备开启新线程的能⼒。

3、异步函数

同样的我们进入源码分析:

void
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
{
dispatch_continuation_t dc = _dispatch_continuation_alloc();

uintptr_t dc_flags = DC_FLAG_CONSUME;

dispatch_qos_t qos;

qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);

_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}

/////////////////////////////////////////////////////////////////////

static inline dispatch_qos_t
_dispatch_continuation_init(dispatch_continuation_t dc,
dispatch_queue_class_t dqu, dispatch_block_t work,
dispatch_block_flags_t flags, uintptr_t dc_flags)
{

void *ctxt = _dispatch_Block_copy(work);

.........省略.........

dispatch_function_t func = _dispatch_Block_invoke(work);//一个block任务

if (dc_flags & DC_FLAG_CONSUME) {
func = _dispatch_call_block_and_release;
}
return _dispatch_continuation_init_f(dc, dqu, ctxt, func, flags, dc_flags);
}

image.png 进入_dispatch_continuation_async源码:

_dispatch_continuation_async(dispatch_queue_class_t dqu,
dispatch_continuation_t dc, dispatch_qos_t qos, uintptr_t dc_flags)
{
#if DISPATCH_INTROSPECTION

if (!(dc_flags & DC_FLAG_NO_INTROSPECTION)) {

_dispatch_trace_item_push(dqu, dc);

}
#else

(void)dc_flags;

#endif

return dx_push(dqu._dq, dc, qos);
}

#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)

我们看到dx_push最后会执行dq_push方法,而dq_push方法进行搜索时发现有很多image.png 看到了,queue_serial,queue_concurrent,queue_global等。dx_vtable(x)用来判断当前队列的类型。

进入_dispatch_root_queue_push源码继续跟踪:

void
_dispatch_root_queue_push(dispatch_queue_global_t rq, dispatch_object_t dou,
dispatch_qos_t qos)
{

#if DISPATCH_USE_KEVENT_WORKQUEUE

.........省略.........

if (likely(!old_dou._do || rq_overcommit)) {

dispatch_queue_global_t old_rq = ddi->ddi_stashed_rq;

dispatch_qos_t old_qos = ddi->ddi_stashed_qos;

ddi->ddi_stashed_rq = rq;

ddi->ddi_stashed_dou = dou;

ddi->ddi_stashed_qos = qos;

_dispatch_debug("deferring item %p, rq %p, qos %d",

dou._do, rq, qos);

if (rq_overcommit) {

ddi->ddi_can_stash = false;

}

if (likely(!old_dou._do)) {

return;

}

// push the previously stashed item

qos = old_qos;

rq = old_rq;

dou = old_dou;

}

}

#endif

#if HAVE_PTHREAD_WORKQUEUE_QOS

if (_dispatch_root_queue_push_needs_override(rq, qos)) {

return _dispatch_root_queue_push_override(rq, dou, qos);

}

#else

(void)qos;

#endif
_dispatch_root_queue_push_inline(rq, dou, dou, 1);
}

image.png _dispatch_root_queue_push_inline->_dispatch_root_queue_poke->_dispatch_root_queue_poke_slow 由于源码的层级和代码量大,我们换一个分析方法,使用bt来看异步dispatch_async

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

    });

运行打印bt,看堆栈信息: image.png_dispatch_client_callout前会执行一些开辟线程的操作_dispatch_worker_thread2->_dispatch_root_queue_drain->_dispatch_queue_override_invoke

异步函数的特点

  1. 不会阻塞线程,不需要等待,任务可以继续执⾏

  2. 可以在新的线程执⾏任务,具备开启新线程的能⼒。(并发队列可以开启多条⼦线程,串⾏队列只能开启⼀条⼦线程)

在看几个例子来验证,我们的分析:

-(void)test1 {
    dispatch_queue_t queue = dispatch_queue_create("ny", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"1");
    });
    dispatch_async(queue, ^{
        NSLog(@"2");
    });
    dispatch_sync(queue, ^{
        NSLog(@"3");
    });
    NSLog(@"0");
    dispatch_async(queue, ^{
        NSLog(@"7");
    });
    dispatch_async(queue, ^{
        NSLog(@"8");
    });
    dispatch_async(queue, ^{
        NSLog(@"9");
    });
}

创建了一个ny的并发队列,然后天际异步打印1,打印2,同步打印3,在异步打印7,8,9。可以肯定 3在0前面,12位置不确定是在3前还是后。789顺序位置不确定,但是一定在0之后。 image.png image.png 打印结果1:3120789 打印结果2:2130789 和我们分析的结果不谋而合。

继续看第二个例子:

-(void)test2 {
    dispatch_queue_t t = dispatch_queue_create("ny", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_sync(t, ^{
        NSLog(@"2");
        dispatch_async(t, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

同样是一个并发队列ny然后打印1,添加同步任务打印2,执行异步任务打印3,打印4,在打印5,12顺序可以确认, 3的位置不确定,4在5前可以确定。345,435,453.这几种情况。

**2022-05-29 21:03:37.962193+0800 001--GCD[90888:2639749] 1**
**2022-05-29 21:03:37.962430+0800 001--GCD[90888:2639749] 2**
**2022-05-29 21:03:37.962630+0800 001--GCD[90888:2639749] 4**
**2022-05-29 21:03:37.962649+0800 001--GCD[90888:2639881] 3**
**2022-05-29 21:03:37.962780+0800 001--GCD[90888:2639749] 5**

**2022-05-29 21:06:02.854080+0800 001--GCD[90941:2641815] 1**
**2022-05-29 21:06:02.854800+0800 001--GCD[90941:2641815] 2**
**2022-05-29 21:06:02.855273+0800 001--GCD[90941:2641858] 3**
**2022-05-29 21:06:02.855241+0800 001--GCD[90941:2641815] 4**
**2022-05-29 21:06:02.860407+0800 001--GCD[90941:2641815] 5**

打印两次的情况和我们分析的大体一致。

第三个例子:

dispatch_queue_t t = dispatch_queue_create("ny", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    dispatch_sync(t, ^{
        NSLog(@"2");
        dispatch_async(t, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");

创建了ny串型队列,然后打印1,添加同步任务打印2,添加异步任务打印3,打印4,打印5,因为是串型队列,任务先进先出并且一次只执行一个任务,所以 124顺序打印完才进入打印3,在因为异步线程,所以可以是35,53。

**2022-05-29 21:12:41.679479+0800 001--GCD[91086:2646879] 1**
**2022-05-29 21:12:41.680455+0800 001--GCD[91086:2646879] 2**
**2022-05-29 21:12:41.680869+0800 001--GCD[91086:2646879] 4**
**2022-05-29 21:12:41.681439+0800 001--GCD[91086:2646879] 5**
**2022-05-29 21:12:41.682761+0800 001--GCD[91086:2646955] 3**

**2022-05-29 21:13:36.988230+0800 001--GCD[91111:2648085] 1**
**2022-05-29 21:13:36.988514+0800 001--GCD[91111:2648085] 2**
**2022-05-29 21:13:36.989263+0800 001--GCD[91111:2648085] 4**
**2022-05-29 21:13:36.989480+0800 001--GCD[91111:2648227] 3**
**2022-05-29 21:13:36.989501+0800 001--GCD[91111:2648085] 5**

打印结果和我们分析的一样。

总结

队列的作⽤是⽤来存储任务。队列分类串⾏队列和并发队列。串⾏队列和并发队列都是 FIFO ,也 就是先进先出的数据结构。

串⾏队列 :它的DQF_WIDTH等于1,相当以它只有⼀条通道。所以队列中的任务要串⾏执⾏,也就是⼀个⼀个的执⾏,必须等上⼀个任务执⾏完成之后才能开始下⼀个,⽽且⼀定是按照先进先出的顺序执⾏的。

并发队列 :它的DQF_WIDTH⼤于1,相当于有多条通道。队列中的任务可以并发执⾏,也就任务可以同时执⾏,通道有很多,哪个任务先执⾏完得看任务的复杂度,以及cpu的调度情况。

同步函数的特点

  1. 阻塞当前线程进⾏等待,直到当前添加到队列的任务执⾏完成。
  2. 只能在当前线程执⾏任务,不具备开启新线程的能⼒。

异步函数的特点

  1. 不会阻塞线程,不需要等待,任务可以继续执⾏
  2. 可以在新的线程执⾏任务,具备开启新线程的能⼒。(并发队列可以开启多条⼦线程,串⾏队列只能开启⼀条⼦线程)