iOS并發(fā)之協(xié)程

1.簡單介紹一下協(xié)程的前世今生
協(xié)程(英語:coroutine)馬爾文·康威于1958年發(fā)明了術(shù)語“coroutine”并用于構(gòu)建匯編程序 ,關(guān)于協(xié)程最初的出版解說在1963年發(fā)表。
協(xié)程的概念在60年代就已經(jīng)提出,目前在服務(wù)端中應(yīng)用比較廣泛,在高并發(fā)場景下使用極其合適,可以極大降低單機(jī)的線程數(shù),提升單機(jī)的連接和處理能力,直到近些年隨著前端扮演的角色越來越重要,業(yè)務(wù)越來越復(fù)雜,越來越多的回調(diào)地獄,讓前端代碼的可讀性越來越差,所以協(xié)程才逐漸的展露頭腳,比如在WWDC21 推出了Swift 5.5 支持異步編程 \ kotlin\ 以及ES7引入了 async/await。

2、協(xié)程的基本概念
協(xié)程是一種用戶態(tài)的輕量級線程,協(xié)程的調(diào)度完全由用戶控制。從技術(shù)的角度來說,“協(xié)程就是你可以暫停執(zhí)行的函數(shù)”。協(xié)程擁有自己的寄存器上下文和棧。協(xié)程調(diào)度切換時(shí),將寄存器上下文和棧保存到其他地方,在切回來的時(shí)候,恢復(fù)先前保存的寄存器上下文和棧,直接操作棧則基本沒有內(nèi)核切換的開銷,可以不加鎖的訪問全局變量,所以上下文的切換非???。
協(xié)程非常類似于線程。但是協(xié)程是協(xié)作式多任務(wù)的,而線程典型是搶占式多任務(wù)的。這意味著協(xié)程提供并發(fā)性而非并行性。


132fe937-0939-4b92-8db3-c665f04ce531.png

3、演示進(jìn)程和線程的買票代碼,進(jìn)行性能差異對比。帶著問題去思考什么是線程的并發(fā),什么是協(xié)程的并發(fā)?

非搶占式:
無需系統(tǒng)調(diào)用
作為對比,多線程執(zhí)行一般需要通過系統(tǒng)調(diào)用去分配線程進(jìn)行執(zhí)行
協(xié)程是線程安全的,無需鎖
掛起執(zhí)行時(shí):
保存寄存器和棧
不影響其他協(xié)程執(zhí)行
恢復(fù)執(zhí)行:
恢復(fù)之前的寄存器和棧
無縫切換回之前的執(zhí)行邏輯

協(xié)程的優(yōu)點(diǎn):
(1)無需線程上下文切換的開銷,協(xié)程避免了無意義的調(diào)度,由此可以提高性能(但也因此,程序員必須自己承擔(dān)調(diào)度的責(zé)
任,同時(shí),協(xié)程也失去了標(biāo)準(zhǔn)線程使用多CPU的能力)
(2)無需原子操作鎖定及同步的開銷
(3)方便切換控制流,簡化編程模型
(4)高并發(fā)+高擴(kuò)展性+低成本:一個(gè)CPU支持上萬的協(xié)程都不是問題。所以很適合用于高并發(fā)處理。

協(xié)程的缺點(diǎn):
(進(jìn)行阻塞(Blocking)操作(如IO時(shí))會(huì)阻塞掉整個(gè)程序。

一、GCD
a、線程之間的切換消耗

什么是線程上下文的切換?多任務(wù)系統(tǒng)往往需要同時(shí)執(zhí)行多道作業(yè)。作業(yè)數(shù)往往大于機(jī)器的CPU數(shù),然而一顆CPU同時(shí)只能執(zhí)行一項(xiàng)任務(wù),為了讓用戶感覺這些任務(wù)正在同時(shí)進(jìn)行,操作系統(tǒng)的設(shè)計(jì)者巧妙地利用了時(shí)間片輪轉(zhuǎn)的方式,CPU給每個(gè)任務(wù)都服務(wù)一定的時(shí)間,然后把當(dāng)前任務(wù)的狀態(tài)保存下來,在加載下一任務(wù)的狀態(tài)后,繼續(xù)服務(wù)下一任務(wù)。任務(wù)的狀態(tài)保存及再加載,這段過程就叫做上下文切換。時(shí)間片輪轉(zhuǎn)的方式使多個(gè)任務(wù)在同一顆CPU上執(zhí)行變成了可能,但同時(shí)也帶來了保存現(xiàn)場和加載現(xiàn)場的直接消耗。
040005361425252.png

真正干活的不是線程,而是CPU。線程越多,干活不一定越快。
b、鎖的消耗

線程的管理,線程的管理也是資源的管理,因?yàn)槎鄠€(gè)線程會(huì)共享一個(gè)進(jìn)程內(nèi)的資源。


381412-20151210224200715-688806242.jpeg

二、swift 協(xié)程
a、await\async
在 WWDC21 中 Swift 盼來了 async/await,作為現(xiàn)代編程語言的標(biāo)志之一,async/await 可以讓我們像編寫常規(guī)代碼一樣,輕松地編寫異步代碼,這樣能更直觀且更安全地表達(dá)我們的思路。async/await 是整個(gè) Swift 結(jié)構(gòu)化并發(fā)的基礎(chǔ)。

   Task.init {
  // 插入await關(guān)鍵字之后,調(diào)用異步函數(shù)work()
            await self.work()
       }
//在異步函數(shù)work()中,出發(fā)異步函數(shù) buy(),async 函數(shù)可以調(diào)用其他 async 函數(shù),也可以直接調(diào)用普通的同步函數(shù)
 func work() async {
        let start = CACurrentMediaTime()
        for _ in 0 ..< ticketTotal {
            let ticket = await self.buy()
            print(ticket)
        }
        let end = CACurrentMediaTime()
        print("協(xié)程-測量時(shí)間:\(end - start)")
    }
//Continuation函數(shù)會(huì)等待異步回調(diào)結(jié)束。
 func buy() async -> Int {
        await withUnsafeContinuation { continuation in
            self.asynBuyTicket { ticket in
                continuation.resume(returning: ticket)
            }
        }
    }

b、actor
Swift 5.5 中的并發(fā)模型旨在提供一個(gè)安全的編程模型,可以靜態(tài)檢測數(shù)據(jù)競爭和其他常見的并發(fā)錯(cuò)誤。結(jié)構(gòu)化并發(fā)為函數(shù)和閉包提供多線程競爭安全性保障。該模型適用于許多常見的設(shè)計(jì)模式,包括并行映射和并發(fā)回調(diào)模式等,但僅限于處理由閉包捕獲的狀態(tài)。
為此 Swift 5.5 引入了新的并發(fā)模型 Actor , 它通過數(shù)據(jù)隔離保護(hù)內(nèi)部的數(shù)據(jù),確保在給定時(shí)間只有一個(gè)線程可以訪問該數(shù)據(jù)。
狀態(tài)私有:外部無法直接去訪問 Actor 的內(nèi)部數(shù)據(jù)
只能接收和處理消息:外部想要與 Actor 通信只能發(fā)送消息
每個(gè) Actor一次只執(zhí)行一個(gè)消息:Actor 內(nèi)部在一個(gè)消息隊(duì)列中處理消息,保證了線程安全

94d2a794-882b-4717-a6dd-e3f9bdbdd2fc.png
actor Ticket {
    var number = ticketTotal
    var start = CACurrentMediaTime()
    var end = CACurrentMediaTime()

    func buy() {
        if number == ticketTotal {
            start = CACurrentMediaTime()
        }
        if number > 0 {
            number -= 1
            print((number))
        }
        if number == 0 {
            end = CACurrentMediaTime()
            print("actor-測量時(shí)間:\(end - start)")
        }
    }
}
Actor模式
傳統(tǒng)線程的傳遞模式
actor 還支持異步屬性
struct BundleFile {
    let filename: String
    
    var contents: String {
        get async throws {
            guard let url = Bundle.main.url(forResource: filename, withExtension: nil) else {
                throw FileError.missing
            }
    
            do {
                return try String(contentsOf: url)
            } catch {
                throw FileError.unreadable
            }
        }
    }
}

c、Continuation
老業(yè)務(wù)怎么平滑的過渡到async呢?
Swift 5.5 提供了 Continuations 讓我們可以把已有的基于閉包的方法轉(zhuǎn)換成 async 方法。

 let thread1 = Thread {
            Task.init {
                let image = try await AsynsData().processImageData()
                self.imageView2.image = image
            }
        }
        thread1.start()
        
        let thread2 = Thread {
            Task.init {
                let image = try await AsynsData().processImageData()
                self.imageView3.image = image
            }
        }
        thread2.start()

 func processImageData() async throws -> UIImage? {
        let upload1 = try await uploadResource()
        let upload2 = try await uploadResource()
        let upload3 = try await uploadResource()
        let downloadImage = try await downloadImage([upload1.data,
                                                     upload2.data,
                                                     upload3.data])
        return downloadImage
    }

//圖片下載
    func downloadImage(_ data:[Data]) async throws -> UIImage {
        await withUnsafeContinuation { continuation in
            AF.download("https://httpbin.org/image/png").responseData { response in
                if let data = response.value, let image = UIImage(data: data) {
                    continuation.resume(returning: image)
                }
            }
        }
    }

    //文件上傳
    func uploadResource() async throws -> (error: APISessionError?, data: Data) {
        await withUnsafeContinuation { continuation in
            guard let fileUrl = Bundle.main.url(forResource: "symbold", withExtension: "text") else {
                continuation.resume(returning:(APISessionError.networkError(error: nil, statusCode: 503), Data()) )
                return
            }
            AF.upload(fileUrl, to: "https://httpbin.org/post").response { response in
                if let data = response.data {
                    continuation.resume(returning:(nil, data))
                } else {
                    continuation.resume(returning:(.noData, Data()))
                }
            }

        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容