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

2023-05-23 20:09:58 +08:00
 James369

最近学习协程很困惑。以下是 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 是否就是开启一个新的线程执行?
4245 次点击
所在节点    程序员
30 条回复
MakHoCheung
2023-05-23 20:24:22 +08:00
1. 问得有点泛,答不了
2. 不是挂起状态,不会阻塞当前线程,该线程会被分配去运行其它代码
3. 不一定,这个很复杂,可以去搜下 Task.init 和 Task.detach
ysc3839
2023-05-23 20:24:34 +08:00
要看语言和框架的,理论上 async await 只是编译器帮你把函数拆成了回调函数。比如这样的代码:
```
async func() {
func1();
await func2();
func3();
}
```
经过处理后会变成:
```
func() {
func1();
func2(function() {
func3();
});
}
```
但是实际上 func2 里面怎么实现的是不知道的,完全可以再另一个线程恢复执行,不过有一点是能确定的,就是当前函数遇到 await 实际上是“返回”了,会继续执行调用方后面的代码。
duke807
2023-05-23 20:37:20 +08:00
类似:

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

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

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

子操作系统 用的是一个非抢占的调度器
nikenidage
2023-05-23 20:40:09 +08:00
由于各大语言的 async/await 都起源于 c#,所以你可以去找一找 c#的相关文章
例如
https://zzk.cnblogs.com/s?w=async+await
youngPacce
2023-05-23 20:49:06 +08:00
@MakHoCheung 你的第二点好像很容易测试,await 前后打印下线程地址就行了。
FaiChou
2023-05-23 21:07:08 +08:00
在 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 这个.
lightjiao
2023-05-23 21:17:02 +08:00
Huelse
2023-05-23 21:29:39 +08:00
大部分与语言的异步本质上是回调,多个异步任务很可能是在同一个 native 线程上跑的。

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

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

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

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

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

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

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

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

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

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

这个 Task 是否就是开启一个新的线程执行?
你这种写法如果没有其他代码了,在 swift 里面是的
hez2010
2023-05-24 14:45:33 +08:00
当当前任务执行到 await 的时候,其当前所在的线程控制权被让出,因此当前线程处于空闲状态。
然后这个时候任务队列中的其他任务就可以被调度到当前线程上来执行了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/942365

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX