介绍GCD

可以先看看这个

“并发”指的是程序的结构,“并行”指的是程序运行时的状态
https://blog.csdn.net/sinat_35512245/article/details/53836580
并发是能力

并行是状态

并行指物理上同时执行,并发指能够让多个任务在逻辑上交织执行的程序设计(cpu时间片轮转优先级调度)

Grand Central Dispatch (GCD) 是 Apple 开发的一个多核编程的解决方法。该方法在 Mac OS X 10.6 雪豹中首次推出,并随后被引入到了 iOS4.0 中。GCD 是一个替代诸如 NSThread, NSOperationQueue, NSInvocationOperation 等技术的很高效和强大的技术。

任务和队列

看下最简单的GCD异步把任务加入全局并发队列的代码
dispatch_async(dispatch_get_global_queue(0, 0), ^{NSLog(@"任务");
});
  • 任务
    任务其实就是一段想要执行的代码,在GCD中就是Block,就是C代码的闭包实现,需要详细了解Block的请戳2分钟明白Block,因此,做法非常简单,例如reloadTableView 就可以加到这里去,问题在于任务的执行方式同步执行异步执行 ,这两者最简单可以概括为 是否具有开线程的能力
    展开来说就是是否会阻塞当前线程,如果和上面示例代码所示,是async,他不会阻塞当前线程,block里面的任务会在另一个线程执行,当前线程会继续往下走,如果是sync,那么Block里面的任务就会阻塞当前线程,该线程之后的任务都会等待block的任务执行完,如果比较耗时,线程就会处于假死状态
  • 队列
    上面讲的是任务的同步执行或者异步执行,那么队列就是用于任务存放,分别有串行队列并行队列
    队列都遵循FIFO(first in first out),串行队列根据先进先出的顺序取出来放到当前线程中,二并行队列会把任务取出来放到开辟的非当前线程,也就异步线程中,任务无限多的时候不会开无限个线程,会根据系统的最大并发数进行开线程

简单概括如下:

项目 同步(sync) 异步(async)
串行 当前线程,顺序执行 另一个线程,顺序执行
并发 当前线程,顺序执行 另一个线程,同时执行

可以看出同步和异步就是开线程的能力,同步执行必然一个个顺序执行在当前线程,而异步执行可以根据队列不同来确定顺序还是同步并发执行

队列的创建 和 简单API

  • 主队列:dispatch_get_main_queue(); 主线程中串行队列
  • 全局队列:dispatch_get_global_queue(0, 0); 全局并行队列
  • 自定义队列

     // 自定义串行队列
    dispatch_queue_create(@"custom name of thread", DISPATCH_QUEUE_SERIAL);
    // 自定义并发队列
    dispatch_queue_create(@"com.mkj.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    
  • 最简单的API

    dispatch_sync(<#dispatch_queue_t  _Nonnull queue#>, ^(void)block)
    dispatch_async(<#dispatch_queue_t  _Nonnull queue#>, ^(void)block)
    

线程死锁1

NSLog(@"任务1");
dispatch_sync(dispatch_get_main_queue(), ^{NSLog(@"任务2");
});
NSLog(@"任务3");

打印信息

2016-12-04 12:50:55.932 GCD[3020:116100] 任务1
(lldb)
Exc_bad_INSTRUCTION报错

分析:
首先执行任务1,然后遇到dispatch_sync 同步线程,当前线程进入等待,等待同步线程中的任务2执行完再执行任务3,这个任务2是加入到mainQueue主队列中(上面有提到这个是同步线程),FIFO原则,主队列加入任务加入到队尾,也就是加到任务3之后,那么问题就来了,任务3等待任务2执行完,而任务2加入到主队列的时候,任务2就会等待任务3执行完,这个就赵成了死锁。

根据上面的描述,可以看到,当前线程中,如果开启同步,而且把任务加入到当前线程,那么当前线程就会阻塞
看下如下的案例
案例一
我们在当前线程中,开启同步等待,然后把任务加入到自定义的串行队列中,这个时候任务一执行完之后,程序等待,任务2被放入串行队列(不是当前队列主队列中),那么另外开辟的串行队列执行任务2,然后继续执行任务3,不会有死循环

dispatch_queue_t t = dispatch_queue_create("com.mikejing", DISPATCH_QUEUE_SERIAL);NSLog(@"任务1");dispatch_sync(t, ^{NSLog(@"任务2");});NSLog(@"任务3");2018-09-07 21:52:03.290198+0800 inherit[1640:22106] 任务12018-09-07 21:52:03.290379+0800 inherit[1640:22106] 任务22018-09-07 21:52:03.290450+0800 inherit[1640:22106] 任务3

案例二
依然是同步等待,我们这个时候把任务2放在全局并发队列里面,这个时候,一样同步等待,等待并发队列任务2执行完,再执行任务3,这里等待的是并发队列,不会阻塞当前线程
主线程中 同步并发队列

NSLog(@"任务1");dispatch_sync(dispatch_get_global_queue(0, 0), ^{NSLog(@"%@",[NSThread currentThread]);NSLog(@"任务2");});NSLog(@"任务3");2018-09-07 23:12:53.943102+0800 inherit[4350:71057] 任务12018-09-07 23:12:53.943256+0800 inherit[4350:71057] <NSThread: 0x60c000079c80>{number = 1, name = main}2018-09-07 23:12:53.943345+0800 inherit[4350:71057] 任务22018-09-07 23:12:53.943433+0800 inherit[4350:71057] 任务3

子线程中同步并发队列

//    dispatch_queue_t t = dispatch_queue_create("com.mikejing", DISPATCH_QUEUE_SERIAL);
//    // 异步串行
//    dispatch_async(t, ^{//        NSLog(@"任务1%@",[NSThread currentThread]);
//        dispatch_sync(dispatch_get_global_queue(0, 0), ^{//            NSLog(@"任务2%@",[NSThread currentThread]);
//        });
//        NSLog(@"任务3%@",[NSThread currentThread]);
//    });
//    2018-09-07 22:11:50.201285+0800 inherit[2421:38097] 任务1<NSThread: 0x60400027d740>{number = 3, name = (null)}
//    2018-09-07 22:11:50.201442+0800 inherit[2421:38097] 任务2<NSThread: 0x60400027d740>{number = 3, name = (null)}
//    2018-09-07 22:11:50.201566+0800 inherit[2421:38097] 任务3<NSThread: 0x60400027d740>{number = 3, name = (null)}

案例二告诉我们,只要是同步,就不会开辟线程,无论是串行队列还是并发队列,都会等待,然后会有线程自身调度去执行串行中的任务或者并发列队中的任务,可以理解为,同步的前提下,串行队列和并发队列是一样的,因为同步,反正需要一个一个执行。

线程死锁2

dispatch_queue_t serialQueue = dispatch_queue_create("com.mkj.serialQueue", DISPATCH_QUEUE_SERIAL);NSLog(@"任务1");
dispatch_async(serialQueue, ^{NSLog(@"任务2");dispatch_sync(serialQueue, ^{NSLog(@"任务3");});NSLog(@"任务4");
});
NSLog(@"任务5");

打印日志:

2016-12-04 13:10:06.587 GCD[3322:129782] 任务1
2016-12-04 13:10:06.588 GCD[3322:129782] 任务5
2016-12-04 13:10:06.588 GCD[3322:129815] 任务2
(lldb)  同样在这里报错停止

分析
1.这里用系统create的方法创建自定义线程,按顺序先执行任务1

2.然后遇到一个异步线程,把任务2,同步线程(包含任务3),任务4这三个东西看成一体放到自定义的串行队列中,由于是异步线程,直接执行下一个任务5,因此异步线程的任务2和任务5不确定谁先谁后,但是任务1 任务2 任务5这三个东西必定会打印出来

3.看下异步线程里面,都放置在自定义的串行队列中,任务2之后遇到一个同步线程,那么线程阻塞,执行同步线程里面的任务3,由于这个队列里面放置的任务4按第二步里面的顺序率先加入进串行队列的,当同步线程执行的时候,里面的任务3是还是按照FIFO顺序加入到任务4之后,那么又造成了案例一里面的任务4等待任务3,任务3等待任务4的局面,又死锁了

线程之间的调度,安全避开死锁

NSLog(@"任务1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{NSLog(@"任务2");dispatch_sync(dispatch_get_main_queue(), ^{NSLog(@"任务3");});NSLog(@"任务4");});
NSLog(@"任务5");

打印日志:这里不会产生死锁,直接解释下如何调度

2016-12-04 13:34:56.136 GCD[3726:150720] 任务1
2016-12-04 13:34:56.137 GCD[3726:150720] 任务5
2016-12-04 13:34:56.137 GCD[3726:150765] 任务2
2016-12-04 13:34:56.142 GCD[3726:150720] 任务3
2016-12-04 13:34:56.143 GCD[3726:150765] 任务4

1最外层分析,首先执行任务1,然后遇到异步线程,不阻塞,直接任务5,由于异步线程有任务2,直接输出

2.这个异步线程是全局并发队列,但是里面又遇到了同步线程,也就是说任务2执行完之后线程阻塞,这个同步线程的任务3是加到mainQueue中的,也就是任务5之后

3.前面已经执行完了任务125或152,那么阻塞的3可以顺利执行,执行完3之后就可以顺利地执行任务4

线程死锁4

dispatch_async(dispatch_get_global_queue(0, 0), ^{NSLog(@"任务1");dispatch_sync(dispatch_get_main_queue(), ^{NSLog(@"任务2");});NSLog(@"任务3");});
NSLog(@"任务4");
while (1) {}
NSLog(@"任务5");
这里会有警告
code will never be executed 埋了隐患,编译器还是还强的,注意看就能避免很多死锁

打印日志:

2016-12-04 13:52:09.597 GCD[3976:163387] 任务1
2016-12-04 13:52:09.597 GCD[3976:163302] 任务4

分析:
1.一开始就是一个异步线程,任务4,死循环和任务5,这里注定了任务5不会被执行,如果其他线程有任务加到主线程中来,那么必定卡死

2.肯定能打印1和4,然后异步线程中遇到同步线程,同步线程的任务是加到mainQueue中的,也就是加到任务5之后,我擦,这肯定炸了,任务5是不会执行的,因此,任务3肯定不会被执行,而且异步线程里面的是同步阻塞的,那么任务3之后的代码肯定也不会执行

3.这里main里面的死循环理论上是不会影响异步线程中的任务1,2,3的,但是任务2是要被加到主队列执行的,那么忧郁FIFO的原理,导致不会执行任务2,那么就死锁了

总结

很多死锁造成的原因第一点是在主线程或者子线程中遇到了一个同步线程,如果这个同步线程把任务加到自己所在线程的同步队列里面就会死锁(mainQueue也是同步队列)

dispatch_sync(来一个同一个同步队列或者mainQueue, <#^(void)block#>)

这种情况下及其容易死锁,千万要小心

能明白就能让上面的线程死锁例子二进行解锁

死锁例子二:
解锁1 新增一个串行队列

dispatch_queue_t serialQueue = dispatch_queue_create("com.mkj.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.mkj.serialQueue1", DISPATCH_QUEUE_SERIAL);NSLog(@"任务1");
dispatch_async(serialQueue, ^{NSLog(@"任务2");dispatch_sync(serialQueue1, ^{NSLog(@"任务3");});NSLog(@"任务4");
});
NSLog(@"任务5");

解锁2 用全局并发队列

dispatch_queue_t serialQueue = dispatch_queue_create("com.mkj.serialQueue", DISPATCH_QUEUE_SERIAL);NSLog(@"任务1");
dispatch_async(serialQueue, ^{NSLog(@"任务2");dispatch_sync(dispatch_get_global_queue(0, 0), ^{NSLog(@"任务3");});NSLog(@"任务4");
});
NSLog(@"任务5");

都能正常打印出

15234 或者 12345这个异步的2和5无法确定,所有有几种可能

总结下:
1.死锁的情况,当前线程无论是主线程还是子线程,只要是串行队列,我们继续sync同步等待,然后加入block任务,这个时候就会死锁,如上图
2.同步并发队列,由于同步的限制,只会在当前线程执行,因此并发和串行队列都是一样的
3.当我们async回到getmainqueue的时候,实际上是在主线程队列最后追加任务,档主线程其他任务完成后才回去执行回调的block
4.案例死锁第一个,当主线程调用sync同步等待任务,而且继续加入到主线程中的时候,就会死锁,一种解锁办法就是把sync的队列换成自定义串行队列或者并发队列即可
5.按我现在的理解,开不开线程取决于同步还是异步,同步不开,异步开,开几条取决于队列,串行队列开一条,并发队列开多条(取决于cpu)
关于同步异步:

6.dispatch_sync是同步函数,不具备开启新线程的能力,交给它的block,只会在当前线程执行,不论你传入的是串行队列还是并发队列,并且,它一定会等待block被执行完毕才返回。
dispatch_async是异步函数,具备开启新线程的能力,但是不一定会开启新线程(例如async……get_main_queue就不会开线程,其他串行开一条,并发队列开多条),交给它的block,可能在任何线程执行,开发者无法控制,是GCD底层在控制。它会立即返回,不会等待block被执行。
注意:以上两个知识点,有例外,那就是当你传入的是主队列,那两个函数都一定会安排block在主线程执行。记住,主队列是最特殊的队列

7.以上都是最简单的理解,任务都是同步任务,那么衍生出来一个超级大问题,如果Block里面的任务是异步网络请求,如何控制先后顺序?如果Block任务里面还嵌套异步任务,因为并发队列里面的任务,只是负责打印和发送请求的操作,异步回调数据是不归队列管的。
一道阿里的面试题
使用GCD如何实现A,B,C三个任务并发,完成后执行任务D?
不是让你打印同步任务,而且网络并发任务的先后依赖如何形成?
另开一篇深入介绍

深度理解GCD线程死锁,队列,同步和异步,串行和并发相关推荐

  1. JAVA笔记14__多线程共享数据(同步)/ 线程死锁 / 生产者与消费者应用案例 / 线程池...

    /*** 多线程共享数据* 线程同步:多个线程在同一个时间段只能有一个线程执行其指定代码,其他线程要等待此线程完成之后才可以继续执行.* 多线程共享数据的安全问题,使用同步解决.* 线程同步两种方法: ...

  2. 深入理解JAVA线程池

    深入理解JAVA线程池 前言 多线程的异步执行方式,虽然能够最大限度发挥多核计算机的计算能力,但是如果不加控制,反而会对系统造成负担.线程本身也要占用内存空间,大量的线程会占用内存资源并且可能会导致O ...

  3. [转] JavaScript:彻底理解同步、异步和事件循环(Event Loop)

    一. 单线程 我们常说"JavaScript是单线程的". 所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个.不妨叫它主线程. 但是实际上还存在其他 ...

  4. JavaScript 运行机制详解(理解同步、异步和事件循环)

    1.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. Java ...

  5. linux查看java线程死锁_ccriticalsection 多线程 死锁_c++ 线程死锁_linux 线程 死锁

    qq_407283393122018-12-10 一个很蠢的造成死锁的问题 wanglt3113172018-12-12 什么是死锁,死锁的原因,如何避免 apanying902019-01-09 c ...

  6. 一文读懂并发与并行,同步与异步阻塞

    并发与并行 并发:指的是任务数多余cpu核数,通过操作系统的各种任务调度算法, 实现用多个任务"一起"执行(实际上总有一些任务不在执行,因为切换任务的速度相当快, 看上去一起执行而 ...

  7. 同步与异步,回调与协程

    目录 概念上下文: 同步的方式: 异步加回调的方式: 异步协程方式: 总结: 这里分享一个 协程原理到实现,全局分析丨协程的切换与调度视频点击查看:「链接」 正文 本文主要介绍在网络请求中的同步与异步 ...

  8. 同步和异步、阻塞和非阻塞之间的关系以及同步阻塞、同步非阻塞、异步阻塞、异步非阻塞的含义

    目录 从线程的维度来理解同步和异步.阻塞和非阻塞. 从线程维度来理解同步阻塞.同步非阻塞.异步阻塞.异步非阻塞. 从线程的维度来理解同步和异步.阻塞和非阻塞. 先假设有 线程A 和 线程B 两个线程, ...

  9. 计算机网络(6) ——同步IO/异步IO专题

    计算机网络(6) --同步IO/异步IO专题 文章目录 理解性记忆 计算机网络(6) --同步IO/异步IO专题 1.同步IO 2.异步IO 理解性记忆 计算机网络(6) --同步IO/异步IO专题 ...

最新文章

  1. Javascript框架的自定义事件(转)
  2. 乔布斯的64周年诞辰,苹果滞销的第N天
  3. 右键 Dos在这里 删除
  4. Spring PropertyPlaceholderConfigurer Usage - 使用系统变量替换spring配置文件中的变量
  5. URLConnection-URL连接
  6. qt android glsl,基于Qt的OpenGL学习(1)—— Hello Triangle
  7. Java集合转化为数组
  8. bash中的算术运算
  9. 数据结构乐智教学百度云_数据结构 百度网盘分享
  10. ckeditor 3.6一直提示“例外被抛出且未被接住”的问题的解决方法
  11. imp-00003:oracle error 959 encountered
  12. sendgrid_使用SendGrid宇宙函数发送电子邮件
  13. 如何使用 LK 字幕脚本工具
  14. ​前端VueRouter解析
  15. 一个老程序员写给换行业的朋友的信
  16. 如何彻底修复DNS污染呢?
  17. 自然语言处理(NLP)发展历程(1),什么是自然语言处理?
  18. Java虚拟机如何设置环境变量_如果classpath环境变量没有进行设置,Java虚拟机会自动将其设置为“.”,也就是当前目录。...
  19. 关于TCP中文件传输阻塞问题的原因及解决方案和相关优化。
  20. AIX5.3、AIX6.0 AIX操作系统安全加固

热门文章

  1. 10进制转8进制怎么转?
  2. 计算机怎么建立共享网络打印机共享,教你实现局域网打印机共享设置 Win7
  3. 使用真机导致Androidstudio打印不出log
  4. 正则表达式-中文姓名带·,英文名字加空格
  5. 在python -m spacy download en出现错误
  6. java中的getclass()函数_JavaScript getClass() 函数
  7. 旅游业营销数字化赋能设计思维设计工作坊
  8. php mid函数的用法,继续收藏一些PHP常用函数
  9. 使用 Flutter 模仿美团 App
  10. 高性能个人博客系统VanBlog