iOS Foundationsswift@IT·互联网

GCD 细细的读

2017-02-23  本文已影响498人  灯泡虫

目录

<a name="前言"></a>前言

在日常app开发中多线程的使用是难免的,既然躲不过,干嘛不好好的享受? 本文将对GCD进行全面的解析。

<a name="为什么选择GCD?"></a>为什么选择GCD?

它是苹果在 iOS 4 时推出的,为多核的并行运算提出的, 以c语言编写的解决方案。高效,自动管理是它的优点。

<a name="串行队列、并行队列、同步、异步"></a>串行队列、并行队列、同步、异步

串行队列:放到串行队列的任务,GCD 会FIFO(先进先出)地取出来一个,执行一个,然后取下一个,这样一个一个的执行。

并行队列

同步:会先阻塞当前线程,直到任务完成。

异步:会另外开线程处理,不会阻塞当前线程。

串行情况下的 同步和异步

  func testFive() {
        //串行队列
        let qe =  DispatchQueue.init(label: "concurrentQueue")
       
        print("当前线程",Thread.current)
        //同步
        qe.sync {
            for i in 0..<5 {
                print("🐩 - \(i)",Thread.current)
            }
        }

        qe.sync {
            for i in 0..<5 {
                print("🐒 - \(i)",Thread.current)
            }
        }
        
        //异步
        qe.async {
            for i in 0..<5 {
                print("🇭🇰 - \(i)",Thread.current)
            }
        }
        
        qe.async {
            for i in 0..<5 {
                print("🇲🇴 - \(i)",Thread.current)
            }
        }
    }

当前线程 <NSThread: 0x60800006d540>{number = 1, name = main}
🐩 - 0 <NSThread: 0x60800006d540>{number = 1, name = main}
🐩 - 1 <NSThread: 0x60800006d540>{number = 1, name = main}
🐩 - 2 <NSThread: 0x60800006d540>{number = 1, name = main}
🐩 - 3 <NSThread: 0x60800006d540>{number = 1, name = main}
🐩 - 4 <NSThread: 0x60800006d540>{number = 1, name = main}
🐒 - 0 <NSThread: 0x60800006d540>{number = 1, name = main}
🐒 - 1 <NSThread: 0x60800006d540>{number = 1, name = main}
🐒 - 2 <NSThread: 0x60800006d540>{number = 1, name = main}
🐒 - 3 <NSThread: 0x60800006d540>{number = 1, name = main}
🐒 - 4 <NSThread: 0x60800006d540>{number = 1, name = main}
🇭🇰 - 0 <NSThread: 0x600000078f40>{number = 3, name = (null)}
🇭🇰 - 1 <NSThread: 0x600000078f40>{number = 3, name = (null)}
🇭🇰 - 2 <NSThread: 0x600000078f40>{number = 3, name = (null)}
🇭🇰 - 3 <NSThread: 0x600000078f40>{number = 3, name = (null)}
🇭🇰 - 4 <NSThread: 0x600000078f40>{number = 3, name = (null)}
🇲🇴 - 0 <NSThread: 0x600000078f40>{number = 3, name = (null)}
🇲🇴 - 1 <NSThread: 0x600000078f40>{number = 3, name = (null)}
🇲🇴 - 2 <NSThread: 0x600000078f40>{number = 3, name = (null)}
🇲🇴 - 3 <NSThread: 0x600000078f40>{number = 3, name = (null)}
🇲🇴 - 4 <NSThread: 0x600000078f40>{number = 3, name = (null)}

结论:可以看出,在串行情况下,不管是异步还是同步都是一个个按照顺序执行。唯一的区别就是异步单独开了个线程

并行情况下的 同步和异步

  func testFive() {
        //其实就是初始化函数里,多了个concurrent 的参数
        let qe =  DispatchQueue.init(label: "concurrentQueue", attributes:.concurrent)
       
        print("当前线程",Thread.current)
        
        //同步
        qe.sync {
            for i in 0..<5 {
                print("🐩 - \(i)",Thread.current)
            }
        }

        qe.sync {
            for i in 0..<5 {
                print("🐒 - \(i)",Thread.current)
            }
        }
        
        //异步
        qe.async {
            for i in 0..<5 {
                print("🇭🇰 - \(i)",Thread.current)
            }
        }
        
        qe.async {
            for i in 0..<5 {
                print("🇲🇴 - \(i)",Thread.current)
            }
        }
    }
    
    当前线程 <NSThread: 0x60000006b380>{number = 1, name = main}
🐩 - 0 <NSThread: 0x60000006b380>{number = 1, name = main}
🐩 - 1 <NSThread: 0x60000006b380>{number = 1, name = main}
🐩 - 2 <NSThread: 0x60000006b380>{number = 1, name = main}
🐩 - 3 <NSThread: 0x60000006b380>{number = 1, name = main}
🐩 - 4 <NSThread: 0x60000006b380>{number = 1, name = main}
🐒 - 0 <NSThread: 0x60000006b380>{number = 1, name = main}
🐒 - 1 <NSThread: 0x60000006b380>{number = 1, name = main}
🐒 - 2 <NSThread: 0x60000006b380>{number = 1, name = main}
🐒 - 3 <NSThread: 0x60000006b380>{number = 1, name = main}
🐒 - 4 <NSThread: 0x60000006b380>{number = 1, name = main}
🇭🇰 - 0 <NSThread: 0x608000078640>{number = 3, name = (null)}
🇲🇴 - 0 <NSThread: 0x6000000767c0>{number = 4, name = (null)}
🇭🇰 - 1 <NSThread: 0x608000078640>{number = 3, name = (null)}
🇲🇴 - 1 <NSThread: 0x6000000767c0>{number = 4, name = (null)}
🇭🇰 - 2 <NSThread: 0x608000078640>{number = 3, name = (null)}
🇲🇴 - 2 <NSThread: 0x6000000767c0>{number = 4, name = (null)}
🇭🇰 - 3 <NSThread: 0x608000078640>{number = 3, name = (null)}
🇲🇴 - 3 <NSThread: 0x6000000767c0>{number = 4, name = (null)}
🇭🇰 - 4 <NSThread: 0x608000078640>{number = 3, name = (null)}
🇲🇴 - 4 <NSThread: 0x6000000767c0>{number = 4, name = (null)}

结论:可以看出,在并行情况下,同步时和串行一致。异步则会开多个线程并行执行。

当然,如果你还是有点晕的话可以看下面这个表格,一般情况都可以用下面的表格解释(例外看下面的注意点!)

同步执行 异步执行
串行队列 当前线程,一个一个执行 其他线程,一个一个执行
并行队列 当前线程,一个一个执行 开很多线程,一起执行

注意点:经过测试,得到的经验:(1)主线程中的其他队列异步都能分出线程(系统线程资源没用完前),(2)分线程中的其他队列异步不一定分出线程;(3)主线程中的主队列异步不能分出线程。

<a name="线程死锁解析"></a>线程死锁解析

一、串行队列

    func syncFirst()  {
        print("MainQueue-Task1",Thread.current)
        DispatchQueue.main.sync {
            print("MainQueue-Task2-sync",Thread.current)
        }
        print("MainQueue-Task3",Thread.current)
    }
        
    输出:1
    MainQueue-Task1 <NSThread: 0x60000006e640>{number = 1, name = main}

解析:

在主队列存在3个任务(任务1,同步任务2,任务3),按照正常的队列的顺序执行顺序应该为
任务1-->同步任务2-->任务3;但是因为同步任务2的存在,导致主线程阻塞,且同步任务2被抽出重新入队列,于是同步任务2,在主队列中排在了任务3的后面。那么问题来了,现在有两条线 1: 任务1-->同步任务2-->任务3 // 2: 任务1-->任务3-->同步任务2 于是主线程就会因为互相等待不知道先执行哪个而完全阻塞。

如下图所示

gcd-deadlock-1.png
    func syncSec() {
        let queue = DispatchQueue.init(label: "MyQueue")
        print("MainQueue-Task1",Thread.current) 
        queue.async {
            print("MyQueue-Task2",Thread.current)
            queue.sync(execute: {
                print("MyQueue-Task3",Thread.current)
            })
            print("MyQueue-Task4",Thread.current)
        }
        print("MainQueue-Task5",Thread.current)
    }
    输出:1 -(2/5)
    MainQueue-Task1 <NSThread: 0x608000071400>{number = 1, name = main}
    MainQueue-Task5 <NSThread: 0x608000071400>{number = 1, name = main}
    MyQueue-Task2 <NSThread: 0x60000007d200>{number = 3, name = (null)}

解析:
在主队列存在3个任务(任务1,异步任务,任务5),那么毫无疑问任务1最先执行,因中间隔着是异步线程,故任务5可以和异步线程同时开始执行;MyQueue串行队列中存在3个任务(同步任务2,同步任务,同步任务),任务2先执行,然后遇到了同步操作,那么还是和上个问题一样,同步任务3和任务4之间相互等待,造成 线程<NSThread: 0x60000007d200>的完全阻塞,程序停止;

如下图所示

线程死锁2.png

看完第一个和第二个的例子是不是有一种错觉:只要串行队列中有同步操作,就会立马死锁?其实不然,下面的例子就证明了有串行同步操作也是可以的。

    func syncThree()  {
        let qe =  DispatchQueue.init(label: "MyQueue")
        print("MainQueue-Task1",Thread.current)
        qe.sync {
            print("MyQueue-Task2",Thread.current)
            print("MyQueue-Task3",Thread.current)
        }
        print("MainQueue-Task4",Thread.current)
    }
     输出:1-2-3 ,且没有推出程序
     MainQueue-Task1 <NSThread: 0x600000079000>{number = 1, name = main}
     MyQueue-Task2 <NSThread: 0x600000079000>{number = 1, name = main}
     MyQueue-Task3 <NSThread: 0x600000079000>{number = 1, name = main}
     MainQueue-Task4 <NSThread: 0x600000079000>{number = 1, name = main}

解析:
主队列中3个任务(任务1,同步任务,任务4),MyQueue队列中两个任务(任务2,任务3)。主线程首选执行主队列中的任务1,然后遇到了同步任务,关键来了!因同步任务是MyQueue的全部任务集合,故不会和主队列进行冲突,而是按照官方描述的,遇到同步任务要执行完任务中的事情,于是任务2和任务3相继被执行,之后主队列的任务4出队列被执行! 顺序为:1->2->3->4。

如下图所示

线程死锁3.png
    func syncFour()  {
        let qe =  DispatchQueue.init(label: "MyQueue")//, attributes:.concurrent)
        print("MainQueue-Task1",Thread.current)
        qe.sync {
            print("MyQueue-Task2",Thread.current)
            qe.sync {
                print("MyQueue-Task3",Thread.current)
            }
            print("MyQueue-Task4",Thread.current)
        }
        print("MainQueue-Task5",Thread.current)
    }
    输出 1-2
    MainQueue-Task1 <NSThread: 0x60800006f240>{number = 1, name = main}
    MyQueue-Task2 <NSThread: 0x60800006f240>{number = 1, name = main}
    

解析:
主队列中3个任务(任务1,同步任务,任务5),MyQueue队列中三个任务(任务2,同步任务3,任务4)。
主线程首先执行任务1,之后遇到MyQueue的同步任务,跳到MyQueue中执行任务2,又遇到了MyQueue的同步任务,那么同步任务中的任务3被推入队列中,导致任务3跑到任务4的后面,导致了任务3和任务4的相互等待,造成死锁!
我们回到第一个例子的代码中再思考一下,和这段代码有没有共同点??其实我们可以理解为下面这段伪代码

    //伪代码,为了说明
    MainQueue.sync{
        viewDidload{
                ....
                syncFirst();
                ....
        }
    }
    
    func syncFirst()  {
        print("MainQueue-Task1",Thread.current)
        DispatchQueue.main.sync {
            print("MainQueue-Task2-sync",Thread.current)
        }
        print("MainQueue-Task3",Thread.current)
    }

是不是明白了什么?整个主线程已经是在串行同步的条件下了,
所以我么可以总结一下串行队列的死锁情况:串行队列中有属于自身队列的同步操作,就会立马死锁!或者说 任何一个串行队列,不能添加两个本队列的同步操作!

如下图所示

线程死锁4.png

二、并行队列

    func syncFive()  {
        let qe =  DispatchQueue.init(label: "MyQueue", attributes:.concurrent)
        print("MainQueue-Task1",Thread.current)
        qe.sync {
            print("MyQueue-Task2",Thread.current)
            qe.sync {
                print("MyQueue-Task3",Thread.current)
            }
            print("MyQueue-Task4",Thread.current)
        }
        print("MainQueue-Task5",Thread.current)
    }
    输出:1-2-3-4-5
    MainQueue-Task1 <NSThread: 0x608000076480>{number = 1, name = main}
    MyQueue-Task2 <NSThread: 0x608000076480>{number = 1, name = main}
    MyQueue-Task3 <NSThread: 0x608000076480>{number = 1, name = main}
    MyQueue-Task4 <NSThread: 0x608000076480>{number = 1, name = main}
    MainQueue-Task5 <NSThread: 0x608000076480>{number = 1, name = main}

解析
主队列中3个任务(任务1,同步任务,任务5),MyQueue队列中三个任务(任务2,同步任务3,任务4)。
主线程首先执行任务1,遇到MyQueue的同步任务跳到MyQueue队列中,执行任务2,此时遇到了MyQueue自己队列的同步任务3,因是并行情况所有所以直接执行(ps:至于为什么直接过去了,我还没找到原理般的解释,先相信知乎上这篇文章上第一个回答所说的,如果大家知道原理,可以告诉我。)
执行完成后再回到主队列,执行任务5。

如下图所示

线程死锁5.png
    func testThree()  {
        // 全局线程
        let queueGlobal = DispatchQueue.global()
        // 主线程
        let queueMain = DispatchQueue.main
        print("MainQueue-Task1",Thread.current)
        //全局异步
        queueGlobal.async {
            print("GlobalQueue-Task2 ",Thread.current)
            //主线程同步,因主线程阻塞,block内容和while循环相互等待
            queueMain.sync(execute: {
                print("GlobalQueue-Task3 ",Thread.current)
            })
            print("GlobalQueue-Task4",Thread.current)
        }
        print("MainQueue-Task5",Thread.current)
        sleep(3);
        print("MainQueue-Task6")
    }
    输出 1-(5/2)-6-3-4
    MainQueue-Task1 <NSThread: 0x60000007d4c0>{number = 1, name = main}
    MainQueue-Task5 <NSThread: 0x60000007d4c0>{number = 1, name = main}
    GlobalQueue-Task2  <NSThread: 0x608000269680>{number = 3, name = (null)}
    MainQueue-Task6
    GlobalQueue-Task3  <NSThread: 0x60000007d4c0>{number = 1, name = main}
    GlobalQueue-Task4 <NSThread: 0x608000269680>{number = 3, name = (null)}


解析
看图就知道这个比较麻烦!主队列中6个任务(任务1,异步任务,任务5,休眠任务,任务6,和后面入对的任务3),GlobleQueue队列中三个任务(任务2,同步任务,任务4)。主线程执行任务1,遇到异步则分出线程执行任务2,同时主线程继续执行任务5,所以任务2和5位置不一定。现在有两条线程,主线程上进入了3秒休眠。分线程上因遇到主线程的同步任务,主线程为串行同步队列,故需要把任务3加入到主线程的队尾,在任务6之后。休眠结束后,主线程继续任务6和任务3,任务3执行完之后,同步任务才得以完成,所以任务4才能执行。有点绕,要消化一下!

如下图所示

线程死锁6.png

<a name="DispatchQueue的使用"></a>DispatchQueue的使用

初始化

 let queue = DispatchQueue.init(label:String,qos:DispatchQoS,attributes:DispatchQueue.Attributes,autoreleaseFrequency:DispatchQueue.AutoreleaseFrequency, target: DispatchQueue?)

属性

<a name="Dispatchgroup的使用"></a>Dispatchgroup的使用

初始化

 let group = DispatchGroup.init()

实战

下面的一段代码很好的解决了多个网络请求并行的问题。

    func testGroup() {
        
        let group = DispatchGroup.init()
        let queueOne = DispatchQueue.init(label: "queueOne",attributes:.concurrent)
        
        let firstTime = NSDate.init().timeIntervalSince1970
        print("主线程",Thread.current)
        print("开始时间",firstTime)
          //这里和下面的三段模拟日常的网络请求
        group.enter()
        queueOne.async(group: group) {
            sleep(1) //模拟请求延迟
            print("1结束")
            group.leave()
        }
        
        group.enter()
        queueOne.async(group: group) {
            sleep(2)
            print("2结束")
            group.leave()
        }
        
        group.enter()
        queueOne.async(group: group) {
            sleep(3)
            print("3结束")
            group.leave()
        }
        
        group.notify(queue: DispatchQueue.main) {
            let secTime = NSDate.init().timeIntervalSince1970
            print("所有异步执行结束时间",secTime)
            print("主线程",Thread.current)
        }
    }
    输出:
    主线程 <NSThread: 0x60000006e800>{number = 1, name = main}
    开始时间 1487831647.5901
    1结束
    2结束
    3结束
    所有异步执行结束时间 1487831650.66429
    主线程 <NSThread: 0x60000006e800>{number = 1, name = main}

解析
首先先创建一个group和一个并行的queue;然后往group里添加三段 并行queue异步的模拟网络请求,分别延迟1s,2s,3s。看输出你就可以看出,整个三段请求的时间是按照 请求时间最大的一段来决定的,所以是3s的时间。等所有的请求都完成之后,就会执行group的notify回调,传人主线程,就可以刷新UI拉。

注意点
enter 和 leave实际执行的次数得是1:1的,不然就会crash。所以我们平时就可以在网络请求的成功和失败block各放一个leave。因其要么失败要么成功,比例还是1:1。

<a name="DispatchSourceTiemr的使用"></a>DispatchSourceTiemr的使用

初始化

(评论中有朋友遇到过 定时器不执行的问题,是因为 没把 定时器全局化 。如:)
 var timer:DispatchSourceTimer? 感谢XIAODAO同学 的建议,哈哈)
 let timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags.strict, queue: DispatchQueue.main)
 当然也可以简单的初始化:
 let timer = DispatchSource.makeTimerSource()//默认了主线程

注意点

timer要全局定义,上面那样局部定义初始化是不会执行的

属性

主要方法

来看几段代码

单次执行

    func runOnce()  {
        print("主线程",Thread.current)
        //创建主线程下的定时器
        timer = DispatchSource.makeTimerSource(queue: DispatchQueue.main)
        
        //单次执行 
        timer?.scheduleOneshot(deadline: DispatchTime.now(), leeway: .milliseconds(10))
        
        //执行的代码块
        timer?.setEventHandler {
            print("单次执行",Thread.current)
        }
        //继续,用activate()也行
        timer?.resume()
    } 
    输出:
    主线程 <NSThread: 0x6080000740c0>{number = 1, name = main}
    单次执行 <NSThread: 0x6080000740c0>{number = 1, name = main}

多次执行

    func runMul()  {
        print("主线程",Thread.current)
        创建分线程下的定时器
        timer = DispatchSource.makeTimerSource()
        
        //循环执行,马上开始,间隔为1s,误差允许10微秒
        timer?.scheduleRepeating(deadline: DispatchTime.now(), interval: .seconds(1), leeway: .milliseconds(10))
        
        //执行内容
        timer?.setEventHandler {
            print("1 second interval",Thread.current)
        }
        
        //激活
        timer?.activate()
    }
    输出:
    主线程 <NSThread: 0x60000006f0c0>{number = 1, name = main}
    1 second interval <NSThread: 0x60000006f0c0>{number = 1, name = main}
    1 second interval <NSThread: 0x60000006f0c0>{number = 1, name = main}
    1 second interval <NSThread: 0x60000006f0c0>{number = 1, name = main}
    ...
    ...

取消执行

    func runCancel()  {
        print("主线程",Thread.current)
        timer = DispatchSource.makeTimerSource(queue: DispatchQueue.main)
        
        timer?.scheduleRepeating(deadline: DispatchTime.now(), interval: .seconds(1), leeway: .milliseconds(10))
        
        timer?.setEventHandler {
            print("1 second interval",Thread.current)
            sleep(2)
            print("after sleep")
        }
        
        timer?.activate()
        
        //主队列创建的情况下 如果在这里直接cancel(),handler里面的内容是不能执行的。如果是默认的分线程,则是可以的,至于为什么,我前面多线程的时候解释过了哈。
         //timer?.cancel()
         
         
        
        //最好都这样取消
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1, execute: {
            self.timer?.cancel()
        })
    }
    输出:
    主线程 <NSThread: 0x6080000644c0>{number = 1, name = main}
    1 second interval <NSThread: 0x60000006f000>{number = 3, name = (null)}
    after sleep

<a name="延迟执行"></a>延迟执行

GCD的延迟执行一般来说可以分为两大类

如下代码所示

    func testDelay()  {
    
            let queue = DispatchQueue.init(label: "myqueue")
            let delayTime = DispatchTime.now() + 2.0
            let delayTimeTimer =  DispatchWallTime.now() + 2.0
            print("before delay")
            
            //第一种
            queue.asyncAfter(deadline: delayTime) {
                print("这是 asyncAfter delay")
            }
            
            //第二种
            let workItem = DispatchWorkItem.init {
                print("这是 asyncAfter workItem delay")
            }
            queue.asyncAfter(deadline: delayTime, execute: workItem)
        
            //第三种
            timer = DispatchSource.makeTimerSource()
            timer?.scheduleOneshot(wallDeadline:delayTimeTimer)
            timer?.setEventHandler(handler: {
                print("这是 timer delay")
            })
            timer?.activate()
    }
    输出:
    before delay
    这是 timer delay
    这是 asyncAfter delay
    这是 asyncAfter workItem delay

总结

这是我第一次写这么长的博客,把自己都感动了!感觉还不错,还会继续下去。针对本文的观点,如有错误点,烦请指出!

本文引用以下文章的部分观点:五个案例让你明白GCD死锁关于iOS多线程,你看我就够了

作者:灯泡虫

邮箱:developer_yh@163.com

上一篇 下一篇

猜你喜欢

热点阅读