1. 程式人生 > >Swift3.0 GCD多執行緒詳解

Swift3.0 GCD多執行緒詳解


                                                                                          GCD思維導圖

            GCD(Grand Central Dispatch)是蘋果公司實現的一套先進先出執行的任務佇列, 我們只要將程式閉包傳給GCD, mac os會在系統執行緒池裡執行該任務, 而且無法確定到底是哪個執行緒執行的。 總之, GCD是個順序或併發執行佇列, 封裝了執行緒的排程, 開發者不用直接操作執行緒了。

        DispatchQueue支援同步sync和非同步async方法,每個人物即DispatchWorkItem只執行一遍; 同步和併發方法都是按照先進先出的順序執行佇列裡的任務。

        App程序在啟動時系統會自動建立一個main queue即DispatchQueue.main, 注意該queue裡不能執行耗時操作並只能在main佇列裡重新整理介面。

1、DispatchQueue基本用法非同步async:在子執行緒執行耗時操作完成後,將結果重新整理到介面; 注意放開註釋使用例項化queue和global的效果是一樣的。

        //let queue = DispatchQueue(label: "com.brycegao.gcdtest")
        DispatchQueue.global().async {
        //queue.async {
            let url = URL(string: "http://img-arch.pconline.com.cn/images/upload/upc/tx/photoblog/1107/05/c5/8235345_8235345_1309860279806.jpg")!
            
            if let imageData = try? Data(contentsOf: url) {  //從網上取資料,屬於耗時操作
                let tmpimage = UIImage(data: imageData as Data)   //二進位制資料轉換為圖片,屬於耗時操作
                
                DispatchQueue.main.async {  //通知ui重新整理
                    self.image = tmpimage
                    self.imageView.image = self.image
                }     
            }
        }
     這段程式碼的作用從網上下載一張圖片並顯示在UIImageView, 註釋值傳遞! 根據閉包的語法特性, 閉包內可以訪問外部的變數, 例如DispatchQueue.main.async閉包內能夠使用外部變數self。
      let queue = DispatchQueue(label: "com.brycegao.gcdtest")
        queue.async {
            let date = Date()
            print("async1 \(date.description)")
            Thread.sleep(forTimeInterval: 1)   //停止1秒
        }
        queue.async {
            let date = Date()
            print("async2 \(date.description)")
            Thread.sleep(forTimeInterval: 1)
        }
        queue.async {
            let date = Date()
            print("async3 \(date.description)")
            Thread.sleep(forTimeInterval: 1)
        }  
從日誌可以看出FIFO的特點,先新增的任務肯定先執行
async1 2016-12-27 13:47:38 +0000
async2 2016-12-27 13:47:39 +0000
async3 2016-12-27 13:47:40 +0000


2、DispatchQueue基本用法同步sync, 該方法會阻塞UI佇列, 導致不顯示控制元件或無點選事件等問題; sync方法仍然按照FIFO順序執行。

        let queue = DispatchQueue(label: "com.brycegao.gcdtest")
        queue.async {  //非同步方法不阻塞UI
            let date = Date()
            print("async1 \(date.description)")
            Thread.sleep(forTimeInterval: 1)   //停止1秒
        }
        queue.sync {   //同步方法會阻塞UI,造成不顯示控制元件或無點選事件,但仍然是順序執行
            let date = Date()
            print("sync \(date.description)")
            Thread.sleep(forTimeInterval: 10)
        }
        queue.async {
            let date = Date()
            print("async3 \(date.description)")
            Thread.sleep(forTimeInterval: 1)
        }  
async1 2016-12-27 13:55:17 +0000
sync 2016-12-27 13:55:18 +0000
async3 2016-12-27 13:55:28 +0000

3、DispatchQos用於描述佇列優先順序, 從高到低分為userInteractive,userInitiated,default,utility,background, 預設是default。

4、上面介紹的是序列佇列(預設), 現在介紹並行佇列。 並行對列只能通過例項化方式得到, 區別是有.concurrent引數。將上面的示例程式碼稍作改動, 即修改DispatchQueue的例項化方法引數。

 let conqueue = DispatchQueue(label: "queuename", attributes: .concurrent)  //併發佇列
        
        conqueue.async {
            let date = Date()
            print("async1 \(date.description)")
            Thread.sleep(forTimeInterval: 1)   //停止1秒
        }
        conqueue.async {   //同步方法會阻塞UI,造成不顯示控制元件或無點選事件,但仍然是順序執行
            let date = Date()
            print("async2 \(date.description)")
            Thread.sleep(forTimeInterval: 1)
        }
        conqueue.async {
            let date = Date()
            print("async3 \(date.description)")
            Thread.sleep(forTimeInterval: 1)
        }        /*DispatchQueue.global().async 
從日誌看跟普通的多執行緒併發是一樣的, 在這裡是在mac os執行緒池內執行的。
async1 2016-12-27 14:08:21 +0000
async2 2016-12-27 14:08:21 +0000
async3 2016-12-27 14:08:21 +0000


5、設定執行時間asyncAfter函式可以設定延遲一段時間後執行閉包,功能類似於定時器。 還是在上面示例程式碼上稍作修改。
.....
conqueue.async {
            let date = Date()
            print("async1 \(date.description)")
            Thread.sleep(forTimeInterval: 1)   //停止1秒
        }
        let time = DispatchTime.now() + 3
        conqueue.asyncAfter(deadline: time, execute: {
            let date = Date()
            print("asyncAfter \(date.description)")
        })
....
輸出:
async1 2016-12-27 14:16:11 +0000
async3 2016-12-27 14:16:11 +0000
async2 2016-12-27 14:16:11 +0000
asyncAfter 2016-12-27 14:16:14 +0000

6、DispatchGroup的作用就是監聽一個或多個DispatchQueue任務結束的觸發事件, 類似於Java的wait/notifyAll。
        let group = DispatchGroup()
        let queue1 = DispatchQueue(label: "queue1")
        queue1.async(group: group) {
            Thread.sleep(forTimeInterval: 1)   //停止1秒
            let date = Date()
            print("async1 \(date.description)")
        }
        let queue2 = DispatchQueue(label: "queue2")
        queue2.async(group: group) {
            Thread.sleep(forTimeInterval: 3)
            let date = Date()
            print("asycn2 \(date.description)")
        }
        let queue3 = DispatchQueue(label: "queue3")
        queue3.async(group: group){
            Thread.sleep(forTimeInterval: 1)
            let date = Date()
            print("async3 \(date.description)")
        }
        let date1 = Date()
        print("date1: \(date1.description)")
        group.wait()    //等待group的任務都執行完成後向下執行
        let date2 = Date()
        print("date2: \(date2.description)")
   日誌:
date1: 2016-12-27 14:32:52 +0000
async3 2016-12-27 14:32:53 +0000
async1 2016-12-27 14:32:53 +0000
asycn2 2016-12-27 14:32:55 +0000
date2: 2016-12-27 14:32:55 +0000

如上面示例程式碼, 將3個DispatchQueue的任務新增到DispatchGroup中,  date1先打印出來, 等到async1、async2、async3都執行完成後才打印date2。