iOS并发之协程

1.简单介绍一下协程的前世今生
协程(英语:coroutine)马尔文·康威于1958年发明了术语“coroutine”并用于构建汇编程序 ,关于协程最初的出版解说在1963年发表。
协程的概念在60年代就已经提出,目前在服务端中应用比较广泛,在高并发场景下使用极其合适,可以极大降低单机的线程数,提升单机的连接和处理能力,直到近些年随着前端扮演的角色越来越重要,业务越来越复杂,越来越多的回调地狱,让前端代码的可读性越来越差,所以协程才逐渐的展露头脚,比如在WWDC21 推出了Swift 5.5 支持异步编程 \ kotlin\ 以及ES7引入了 async/await。

2、协程的基本概念
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。从技术的角度来说,“协程就是你可以暂停执行的函数”。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
协程非常类似于线程。但是协程是协作式多任务的,而线程典型是抢占式多任务的。这意味着协程提供并发性而非并行性。


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

3、演示进程和线程的买票代码,进行性能差异对比。带着问题去思考什么是线程的并发,什么是协程的并发?

非抢占式:
无需系统调用
作为对比,多线程执行一般需要通过系统调用去分配线程进行执行
协程是线程安全的,无需锁
挂起执行时:
保存寄存器和栈
不影响其他协程执行
恢复执行:
恢复之前的寄存器和栈
无缝切换回之前的执行逻辑

协程的优点:
(1)无需线程上下文切换的开销,协程避免了无意义的调度,由此可以提高性能(但也因此,程序员必须自己承担调度的责
任,同时,协程也失去了标准线程使用多CPU的能力)
(2)无需原子操作锁定及同步的开销
(3)方便切换控制流,简化编程模型
(4)高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

协程的缺点:
(进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序。

一、GCD
a、线程之间的切换消耗

什么是线程上下文的切换?多任务系统往往需要同时执行多道作业。作业数往往大于机器的CPU数,然而一颗CPU同时只能执行一项任务,为了让用户感觉这些任务正在同时进行,操作系统的设计者巧妙地利用了时间片轮转的方式,CPU给每个任务都服务一定的时间,然后把当前任务的状态保存下来,在加载下一任务的状态后,继续服务下一任务。任务的状态保存及再加载,这段过程就叫做上下文切换。时间片轮转的方式使多个任务在同一颗CPU上执行变成了可能,但同时也带来了保存现场和加载现场的直接消耗。
040005361425252.png

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

线程的管理,线程的管理也是资源的管理,因为多个线程会共享一个进程内的资源。


381412-20151210224200715-688806242.jpeg

二、swift 协程
a、await\async
在 WWDC21 中 Swift 盼来了 async/await,作为现代编程语言的标志之一,async/await 可以让我们像编写常规代码一样,轻松地编写异步代码,这样能更直观且更安全地表达我们的思路。async/await 是整个 Swift 结构化并发的基础。

   Task.init {
  // 插入await关键字之后,调用异步函数work()
            await self.work()
       }
//在异步函数work()中,出发异步函数 buy(),async 函数可以调用其他 async 函数,也可以直接调用普通的同步函数
 func work() async {
        let start = CACurrentMediaTime()
        for _ in 0 ..< ticketTotal {
            let ticket = await self.buy()
            print(ticket)
        }
        let end = CACurrentMediaTime()
        print("协程-测量时间:\(end - start)")
    }
//Continuation函数会等待异步回调结束。
 func buy() async -> Int {
        await withUnsafeContinuation { continuation in
            self.asynBuyTicket { ticket in
                continuation.resume(returning: ticket)
            }
        }
    }

b、actor
Swift 5.5 中的并发模型旨在提供一个安全的编程模型,可以静态检测数据竞争和其他常见的并发错误。结构化并发为函数和闭包提供多线程竞争安全性保障。该模型适用于许多常见的设计模式,包括并行映射和并发回调模式等,但仅限于处理由闭包捕获的状态。
为此 Swift 5.5 引入了新的并发模型 Actor , 它通过数据隔离保护内部的数据,确保在给定时间只有一个线程可以访问该数据。
状态私有:外部无法直接去访问 Actor 的内部数据
只能接收和处理消息:外部想要与 Actor 通信只能发送消息
每个 Actor一次只执行一个消息:Actor 内部在一个消息队列中处理消息,保证了线程安全

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-测量时间:\(end - start)")
        }
    }
}
Actor模式
传统线程的传递模式
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
老业务怎么平滑的过渡到async呢?
Swift 5.5 提供了 Continuations 让我们可以把已有的基于闭包的方法转换成 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()))
                }
            }

        }
    }

你可能感兴趣的:(iOS并发之协程)