持续创作,加速成长!这是我参与「掘金日新计划 · 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]);
});
}
}
运行代码看下打印:
我们看到最大核数是4,也就是在同一时间可以运行4个线程。
主线程占用1M,
子线程占用512kb ,开启一个子线程需要90us(微秒)。
GCD的线程池里,缓存了64条线程。
为什么打印的number数是变化的呢:这个和线程池的概念有关
饱和策略:
- AbortPolicy 直接抛出RejectedExecutionExeception异常来阻止系统正常运行
- CallerRunsPolicy 将任务回退到调用者
- DisOldestPolicy 丢掉等待最久的任务
- DisCardPolicy 直接丢弃任务
这四种拒绝策略均实现的RejectedExecutionHandler接口
附上线程的生命周期图:
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,执行添加同步任务,发生死锁。线程卡死。串型队列 一个一个的执行。异步任务未执行完就要马上执行下一个同步任务。发生循环等待,也就是死锁。
运行验证,与猜想相同。
继续看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);
}
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"是系统命名的。
在看看
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;
}
继续跟踪发现
_dispatch_queue_init函数初始化队列并设置队列的宽度。
然后继续探索发现:
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同步函数执行完,相互等待产生死锁。
运行看到报错在
__DISPATCH_WAIT_FOR_QUEUE__的函数中,我们通过源码libdispatch-1271进行跟踪分析。
我们看到
_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), ^{});
在
_dispatch_sync_f_slow->_dispatch_sync_function_invoke,_dispatch_sync_complete_recurse,_dispatch_sync_invoke_and_complete_recurse继续跟踪。
进入
_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来看堆栈信息:
同步函数的特点
-
阻塞当前线程进⾏等待,直到当前添加到队列的任务执⾏完成。
-
只能在当前线程执⾏任务,不具备开启新线程的能⼒。
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);
}
进入_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方法进行搜索时发现有很多。
看到了,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);
}
_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,看堆栈信息:
在
_dispatch_client_callout前会执行一些开辟线程的操作_dispatch_worker_thread2->_dispatch_root_queue_drain->_dispatch_queue_override_invoke
异步函数的特点
-
不会阻塞线程,不需要等待,
任务可以继续执⾏。 -
可以在新的线程执⾏任务,
具备开启新线程的能⼒。(并发队列可以开启多条⼦线程,串⾏队列只能开启⼀条⼦线程)
在看几个例子来验证,我们的分析:
-(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之后。
打印结果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的调度情况。
同步函数的特点
- 阻塞当前线程进⾏等待,直到当前添加到队列的任务执⾏完成。
- 只能在当前线程执⾏任务,不具备开启新线程的能⼒。
异步函数的特点
- 不会阻塞线程,不需要等待,
任务可以继续执⾏。 - 可以在新的线程执⾏任务,
具备开启新线程的能⼒。(并发队列可以开启多条⼦线程,串⾏队列只能开启⼀条⼦线程)