V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
James369
V2EX  ›  程序员

当运行到协程 await 语句时,当前线程处于一种什么样的状态?

  •  
  •   James369 · 310 天前 · 3869 次点击
    这是一个创建于 310 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近学习协程很困惑。以下是 swift 代码,还是没大明白协程的工作过程:

    func fetchData() async -> String {
        print("Start fetching data")
        
        // 模拟异步操作,使用 Task.sleep 等待 2 秒钟
        await Task.sleep(2_000_000_000)
        
        print("Data fetching complete")
        return "Data"
    }
    
    func processData() async {
        print("Start processing data")
        
        let data = await fetchData() // 等待 fetchData() 异步操作的完成
        
        print("Data processing complete. Received: \(data)")
    }
    
    print("Before calling processData")
    
    Task {
        await processData() // 调用异步函数 processData()
    }
    
    print("After calling processData")
    

    疑问:

    1. 协程本质是实现了串行还是并行运行?
    2. 当运行到协程 await 语句时,当前线程是挂起状态吗。(按照资料上说,协程不会阻塞当前线程)
    3. 这个 Task 是否就是开启一个新的线程执行?
    第 1 条附言  ·  309 天前
    信息量有点大,我先研究研究,就不一一回复了。
    Thanks.
    30 条回复    2023-05-25 09:54:38 +08:00
    MakHoCheung
        1
    MakHoCheung  
       310 天前
    1. 问得有点泛,答不了
    2. 不是挂起状态,不会阻塞当前线程,该线程会被分配去运行其它代码
    3. 不一定,这个很复杂,可以去搜下 Task.init 和 Task.detach
    ysc3839
        2
    ysc3839  
       310 天前 via Android
    要看语言和框架的,理论上 async await 只是编译器帮你把函数拆成了回调函数。比如这样的代码:
    ```
    async func() {
    func1();
    await func2();
    func3();
    }
    ```
    经过处理后会变成:
    ```
    func() {
    func1();
    func2(function() {
    func3();
    });
    }
    ```
    但是实际上 func2 里面怎么实现的是不知道的,完全可以再另一个线程恢复执行,不过有一点是能确定的,就是当前函数遇到 await 实际上是“返回”了,会继续执行调用方后面的代码。
    duke807
        3
    duke807  
       310 天前
    类似:

    你在一个多核 CPU 上的操作系统上,注册申请了一个进程或线程

    接下来,你把这个线程当作一个虚拟 cpu ,在其上再跑一个 子操作系统

    子操作系统 再次切分你原本申请的 线程 对应的 cpu 资源,用来调度 子操作系统 上面的 小线程

    子操作系统 用的是一个非抢占的调度器
    nikenidage
        4
    nikenidage  
       310 天前   ❤️ 1
    由于各大语言的 async/await 都起源于 c#,所以你可以去找一找 c#的相关文章
    例如
    https://zzk.cnblogs.com/s?w=async+await
    youngPacce
        5
    youngPacce  
       310 天前 via Android
    @MakHoCheung 你的第二点好像很容易测试,await 前后打印下线程地址就行了。
    FaiChou
        6
    FaiChou  
       310 天前   ❤️ 1
    在 swift 中叫 concurrency.
    有同步和异步, 也有串行和并行.
    同步是两个任务, 后面的任务需要等待前面的任务完成才会被执行, 如果前面的任务卡住, 也会影响后面的任务不被执行; 如果在主线程(UI 线程)则表现出页面卡住.
    相反, 异步的话, 需要两个任务在不同线程执行, 互相不影响.

    而串行是一个任务接另一个任务, 比如同步任务就是串行执行, 但异步任务也可以串行执行.
    不同线程的任务同时执行叫作并行.

    回到你的例子中. 代码执行先打印 "Before calling processData"
    接着来到 Task 中执行 processData() 函数打印 "Start processing data", 接着又到了 fetchData() 函数打印 "Start fetching data"; 到目前为止都是串行的, 然后遇到了 await, 这时候是在新的线程中起了一个异步任务. 原来线程(主线程)继续执行, 于是打印 "After calling processData".
    等待 Task.sleep 两秒结束后, 继续回到主线程打印 "Data fetching complete", 然后再返回给 processData(), 接着打印 "Data processing complete. Received: \(data)".

    协程是另一种东西, 比如 js 中的协程是比线程更小的执行单元, 具体可以学习 https://github.com/tj/co 这个.
    Huelse
        8
    Huelse  
       310 天前
    大部分与语言的异步本质上是回调,多个异步任务很可能是在同一个 native 线程上跑的。

    1 、实际从一个线程的角度来说,这几个异步任务是串行,但对整个操作可以说是并行
    2 、不是,如果等待时间很长,这个线程很可能去运行其他任务了,详见 cpu 调度
    3 、有可能,例如上个线程等待时间太长,然后一直在执行其他函数,这时候你的 Task 返回了就会放到一个新的线程里

    可见这能最大程度利用好 cpu 资源而不需要手动操作线程
    daokedao
        9
    daokedao  
       310 天前
    协程在一个线程中,当然是串行
    Donahue
        10
    Donahue  
       310 天前
    我的理解是这样的:
    Donahue
        11
    Donahue  
       310 天前
    我的理解是这样的:
    对于单个协程的情况,它的表现就是一个串行的程序,跟普通的函数没有区别。
    但是对于多个协程的情况,它们之间的表现是并行运行的,类似于多线程。
    所以可以把每次创建一个协程 Task 理解为创建一个新的线程(但实际是协程),去运行这个函数
    也就是说你可以简单地理解为创建一个协程就是创建一个线程执行某个函数

    但是实际底层并不是真的创建了一个线程,而是使用协程的调度器去进行调度。

    Task 跟调度器的关系就跟线程与操作系统线程调度器的关系一样,
    协程是应用程序内部的调度,由应用程序控制当前需要运行哪部分代码;
    线程是操作系统级别的调度, 由操作系统决定当前需要运行哪部分程序
    hxysnail
        12
    hxysnail  
       310 天前
    1. 线程为处理多个任务而开了多个协程,各个协程轮流执行,所以本质上是串行执行;
    2. await 语句在当前协程阻塞时,可以把执行权让给其他协程;线程继续执行其他线程,不会阻塞;如果线程内所以协程都处于阻塞状态,那么线程也会阻塞;
    3. 对 swift 不熟,不会。
    sunny1688
        13
    sunny1688  
       310 天前
    从协程的名字上来看就能看出是协作,可以把协程理解为可以 “让出和恢复的函数”,让遇到 await 关键字后,就会让出当前函数,swift 在语言层面有一个调度器(事件循环),语言层面会调度给其它函数执行,当 await 后面的代码执行完毕后,会恢复函数执行,从而继续往下执行,依此类推,直到当前函数全部流程全部执行完毕。

    1 、单线程下同一时间只有一个协程在执行
    2 、当前线程不会挂起,会执行其它代码,当前 await 函数会让出( yield )
    3 、不会 swift ,但协程的概念是一样的,Task 是一个协程(轻量级线程),类似 js 的 generator
    NessajCN
        14
    NessajCN  
       310 天前
    其实不用想太多,协程不阻塞当前线程就是字面意思
    你定义了三个 async 函数
    你可以安排它们执行完了一个再执行下一个,也就跟 sync 一样的执行方式
    也可以安排它们遇到 await 的时候就先去执行其他的,也就是所谓的不阻塞线程
    协程是单线程的,不会起新线程,它实现多任务一起跑的方式类似单核 cpu 调度。本质是就一个核在跑,只是遇到没活的时候(也就是 await 的时候)立刻去跑别的任务。由于执行速度飞快给你一种几个任务同时在跑的错觉。
    lisxour
        15
    lisxour  
       310 天前
    你首先要知道异步和多线程是不同的东西,然后你才能更好的了解。
    githmb
        16
    githmb  
       310 天前
    Rust 选手前来觐见。Future 代表可能在未来完成的值,对一个 Future 进行 await ,代表你需要等待该 Future 取得进展,为什么需要等待呢?当然是要执行一些非阻塞式的 IO 操作了,这依赖一些系统底层的东西,比如 epoll 。异步运行时会帮你调度这些 Future ,如果 IO 取得进展,会继续推进 Futre ,直到达到 Poll::Ready 状态,返回 await
    lolizeppelin
        17
    lolizeppelin  
       310 天前
    对于协程实现框架来说, 在一个以时间为排序 key 的队列里排序

    对于系统和底层来说, 一般是通过保存上下文实现
    就本质来说,协程解决的是遇到 io 时切换到其他其他代码片段、等 io 完成后切换回来

    要实现上述代码,如果不用类似线程的语法、那么就你的代码就是一但开始 io, 就得 goto 来 goto 去,这样的代码根本没法写

    协程的框架、或者说协程的语法,就是把上述 goto 来 goto 去的实现到框架内部,让业务代码可以常规语法差不多

    把一个协程框架代码读懂来就不会有那么多疑问了,那种带 c 或者汇编的不好读,可以读 python 的 eventlet,这套代码除了上下文保存部分用了 c,其他都是纯 python 代码
    CodingIran
        18
    CodingIran  
       310 天前
    推荐仔细阅读 onecat 的 https://objccn.io/products/async-swift
    zmcity
        19
    zmcity  
       309 天前
    协程本质是实现了串行还是并行运行?
    协程的本质是将异步编程写成同步编程的形式,原来是串行还是串行,原来是并行还是并行。

    当运行到协程 await 语句时,当前线程是挂起状态吗。(按照资料上说,协程不会阻塞当前线程)
    和 Executor 实现有关,可以挂起也可以不挂。如果不是 io 的协程一般不挂,

    这个 Task 是否就是开启一个新的线程执行?
    你这种写法如果没有其他代码了,在 swift 里面是的
    hez2010
        20
    hez2010  
       309 天前
    当当前任务执行到 await 的时候,其当前所在的线程控制权被让出,因此当前线程处于空闲状态。
    然后这个时候任务队列中的其他任务就可以被调度到当前线程上来执行了。
    J1sen
        21
    J1sen  
       309 天前
    可以学习 C++20 协程,比较底层,自己实现一下 task 这些类型就明白了。swift 这种是无栈协程,无栈协程就相当于同一个函数调用多次,但是执行的代码不一样了。一般 await 表示让出执行权,一般就是两种选择,把执行权交给调度器,让调度器去选择协程执行,或者是直接把执行权给另外一个协程。而让出执行权有两种方式,一种是函数返回,一种是函数调用。一般来说回到调度器是通过函数返回,而 await 另外一个协程是通过直接调用对应的协程函数实现的,await 的协程调用结束的时候又会把执行权交给原来的协程。 https://lewissbaker.github.io 可以看看这里的文章,讲的非常清晰
    hankai17
        22
    hankai17  
       309 天前
    1. 串行运行
    2. 协程调度设计应该类似于 有个全局协程队列 不断地循环执行 如果所有协程都运行完 或者 协程睡眠 /等待 那么线程就处于 sleep 态 否则仍然是 running 态
    Leviathann
        23
    Leviathann  
       309 天前
    无栈协程等价于给线程池 /eventloop 提交 task
    不相关的两个协程之间,你的线程池 /eventloop 是多线程就可以是并行,是单线程就是串行
    await 之前与之后部分则保证则是串行
    xausky
        24
    xausky  
       309 天前
    await 还好 yield 才是神奇的操作
    chtcrack
        25
    chtcrack  
       309 天前
    异步和多线程都是用来处理并发任务的技术,但它们的实现方式和目的略有不同。

    多线程是指将一个进程中的工作分成多个独立的线程,每个线程可以并行执行不同的任务,从而实现并发处理。在多线程中,每个线程都有自己的执行上下文和堆栈,可以在不同的时间、不同的 CPU 核心上执行任务,从而提高程序的并发性能。

    异步是指在执行任务时,不需要等待当前任务完成,而是可以在等待期间继续执行其他任务。异步通常使用回调、协程、事件或者消息等机制来实现,它的目的是提高程序的响应性能和吞吐量。

    因此,多线程和异步的区别在于它们的实现方式和目的:

    多线程是通过将任务分配到多个线程中并行执行,来提高程序的并发性能。
    异步是通过在等待期间继续执行其他任务,来提高程序的响应性能和吞吐量。
    需要注意的是,多线程和异步并不是互斥的,它们可以同时使用,以达到更好的性能优化效果。例如,在一个多线程的程序中,可以使用异步方式来处理某些任务,以避免线程阻塞和资源浪费。又或者,在一个异步的程序中,可以使用多线程来加速某些任务的执行,以提高程序的并发性能。

    总之,多线程和异步都是并发编程中非常重要的技术,程序员需要根据具体的需求和场景,来选择合适的技术来处理并发任务。 现在都有 chatgpt 了,它比大多数人都懂得多,没事可以多问问它..
    dearmymy
        26
    dearmymy  
       309 天前
    如果会汇编就比较好理解。得稍微了解下 call 函数过程
    实际上协程核心代码就那几行汇编。
    heroin80s
        27
    heroin80s  
       309 天前
    分清楚 并行和并发
    await 的时候程序指针改变了要执行的下一条指令位置
    irytu
        28
    irytu  
       309 天前 via iPhone
    不被调度的状态
    irytu
        29
    irytu  
       309 天前 via iPhone
    @xausky yield 就是让出 CPU 时间 给其他任务
    thezhang98
        30
    thezhang98  
       309 天前 via Android
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3558 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 04:55 · PVG 12:55 · LAX 21:55 · JFK 00:55
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.