C++20 协程(coroutine)入门
文章目录
- C++20 协程(coroutine)入门
- 什么是协程
- 无栈协程和有栈协程
- 有栈协程的例子
- 例 1
- 例 2
- 对称协程与非对称协程
- 无栈协程的模型
- 无栈协程的调度器
- 朴素的单线程调度器
- 让协程学会等待
- Python 中的异步函数
- 可等待对象
- M:N 调度器——C# 中的异步函数
- 小结
- C++20 中的协程对象
- (未完待续)
在阅读下面的内容之前,建议先入门至少三门除 C++ 外的其他编程语言,最好还支持协程。
可以参考:渡劫 C++ 协程(0):前言 | Benny Huo
C++20 协程(coroutine)入门
什么是协程
可以参考:初识协程 | 楚权的世界 (chuquan.me)
老生常谈,协程的核心思想是允许放弃执行当前函数,转而执行其他函数,但之后还能恢复之前函数的执行状态。学过 Python 的人很快就能想到,这不就是生成器吗?
def my_range(to): # 是一个生成器。for i in range(1, to + 1):yield i # 1. 放弃执行当前函数。if __name__ == "__main__":for i in my_range(3): # 3. 恢复之前函数的执行状态。print(i) # 2. 转而执行其他函数。
但这个和什么所谓的“亿级别流量洪峰”有什么关系,怎么做到让数亿协程“宏观并行”(即表现出并发特征)?如果没有新的线程被创建,网络调用仍然只能在主线程执行,这个矛盾怎么解决?我相信即使你不会 Python,看不懂上面的代码,在看别人对协程的介绍时也能想到这些问题。
我们一步一步来,先巩固协程相关的基本概念,再来回答以上刁钻的问题。
无栈协程和有栈协程
可以参考:浅谈有栈协程与无栈协程 - 知乎 (zhihu.com)。
可以参考:协程和纤程的区别是什么? - tearshark的回答 - 知乎。
可以参考:有栈协程与无栈协程 (mthli.xyz)
协程(coroutine),也就是协作(co-)的过程(routine),离不开过程二字,也就是说协程也是一个函数(function, method, routine, etc.)。同时可以顾名思义,互相“协作”的“过程”生来就是用于解决并发问题的。
我们都知道,一般的线程一定存在一个函数调用栈,记录着函数之间的调用关系、局部变量、返回地址等等。那对于协程来说,它和我们熟知的那个栈有什么关系呢?
有什么关系,其实取决于“协作”的具体实现。不同的实现会与我们熟知的那个栈产生不同的联系。大体上可以分为两类:
-
有栈协程(stackful)。
创建一个有栈协程时,运行时(runtime)会申请一片内存空间,作为协程的栈空间。之后,该协程都将这片空间视为自己的栈空间。如果已经开始执行该协程的代码,这个协程就好像在一个新的线程上运行一样。
但创建一个有栈协程并不会创建一个内核态的线程,如何使得协程具有并发特征?其实关键还是在于让协程自己放弃当前的执行权。
-
无栈协程(stackless)。
创建一个无栈协程时,运行时会申请一片内存空间,保存协程的栈帧。之后,该协程仍然在某个线程的栈空间上运行,只不过协程可以选择保存当前栈帧后放弃执行权,再之后还可以恢复到此前的状态继续执行。
简单地说,这两类协程可以描述为(不一定准确,主要是为了方便理解):
- 有栈协程就是不由操作系统内核调度的“线程”。取决于具体实现,可能没有线程本地存储(Thread Local Storage, TLS),等等,总而言之只是长得像线程。
- 无栈协程就是一个可以断断续续执行的函数。
Python 的生成器可以看作是无栈协程,C++20 提供的协程也是无栈协程。
有栈协程的例子
介绍以上分类,其实对理解协程提供并发能力并没有任何帮助。一方面,一开始提到的 Python 的生成器也是无栈协程,但我们(可能)并没有见过用生成器解决并发问题的场景,所以之前的提问一个也没有被解答。
为了更直观地看到协程如何解决并发问题,我们来看几个有栈协程的例子。
例 1
#include <stdbool.h>
#include <stdio.h>#include <Windows.h>PVOID fiber_main;
PVOID fiber_anothers[2];void inner(int id) {printf("Task %d\n", id);// Note:放弃当前纤程执行权,转换到其他纤程。SwitchToFiber(fiber_main);
}void WINAPI another(LPVOID param) {while (true) {inner((int)param);}
}int main() {// 将当前的线程转换为纤程,允许参与纤程的调度。fiber_main = ConvertThreadToFiber(NULL);// 创建纤程,但不执行。for (unsigned i = 0; i < 2; i++) {// 参数 1 是栈空间,0 表示取默认值。fiber_anothers[i] = CreateFiber(0, another, (LPVOID)(i + 1));}printf("Fiber demo started\n");for (unsigned i = 0; i < 3; i++) {for (unsigned j = 0; j < 2; j++) {// Note:放弃当前纤程执行权,转换到其他纤程。SwitchToFiber(fiber_anothers[j]);}}printf("Done!\n");// 回收资源。for (unsigned i = 0; i < 2; i++) {// 即使两个任务是死循环,也因为放弃执行权而没有运行。// 由于纤程是我们自己调度,所以可以安全地删除它们。DeleteFiber(fiber_anothers[i]);}ConvertFiberToThread();
}
运行结果:
Fiber demo started
Task 1
Task 2
Task 1
Task 2
Task 1
Task 2
Done!
程序 1 的 another
函数是一个典型的协程。它运行时可以表现出并发的特征,前提是我们需要自己放弃当前协程的执行权(SwitchToFiber
函数)。即使是在协程调用的子函数中(inner
函数),也可以主动放弃当前协程的执行权,所以 Windows 中的纤程是有栈协程的一种实现。
例 2
package mainimport ("fmt""time"
)func inner(id int) {fmt.Println("Task", id)// Note: 放弃当前 goroutine 执行权,转换到其他 goroutine。time.Sleep(100 * time.Millisecond)// Note: 运行时会帮助我们尽可能在 100 毫秒后重新取得执行权。
}func another(id int) {for true {inner(id)}
}func main() {fmt.Println("goroutine demo started")for i := 0; i < 2; i++ {// 创建 goroutine,是否立即开始在其他线程中执行取决于运行时。go another(i)}// Note: 放弃当前 goroutine 执行权,转换到其他 goroutine。time.Sleep(300 * time.Millisecond)fmt.Println("Done!")// Note: 主 goroutine 被销毁后,其他 goroutine 也被销毁。
}
可能的运行结果:
goroutine demo started
Task 0
Task 1
Task 0
Task 1
Task 1
Task 0
Done!
通过这两个例子,我们大致看到了有栈协程在实现并发时不可或缺的东西:调度(schedule)。例 1 中,调度完全由手工实现(SwitchToFiber
函数),费时费力;而例 2 中,调度由 Go 语言的**调度器(scheduler)**实现,写程序时只用自然地让当前 goroutine 睡眠即可(time.Sleep
函数)。
为了实现 M:N 模型,Go 语言运行时提供的调度器颇为复杂,但使用 Go 语言时就不用考虑这么多了,就程序 3 而言,把 goroutine 看作一个线程也无妨。Go 语言的调度器让有栈协程具有了很多类似线程的功能,从而可以像线程一样使用 goroutine,同时让创建 goroutine 的代价很低,也就实现了高并发。
从 Go 语言可以看出,如果一个语言支持有栈协程,那么把原有的线程函数迁移为协程函数并不会太复杂,因为它们长得挺像。但对于无栈协程来说,没有长得像一说,所以代码迁移可能会花更多时间。但无栈协程所占空间明显小于有栈协程,这是无栈协程特有的优势。
对称协程与非对称协程
可以参考:协程学习(对称和非对称) - 知乎 (zhihu.com)
可以参考:一文彻底弄懂C++开源协程库libco——原理及应用 - 知乎 (zhihu.com)
程序 3 中,goroutine 通过 go
语句被创建后,就好像一个单独的线程一样,被创建的协程只能自己选择放弃(yield)执行权,至于放弃之后谁执行,只由调度器决定,不由协程的创建者决定,这种就是对称协程(symmetric coroutine)。对称协程之间不存在明显的从属关系,大家都是平等的。
程序 2 中,我们完全自己调度纤程。如果规定在放弃执行权时只能回到纤程的创建者,则可以形成纤程的调用关系链。这种具有明显调用关系的协程就是非对称协程(asymmetric coroutine)。
由此可以注意到,无栈与有栈、对称与非对称是两个不同的概念。Python 的生成器可以看作是非对称协程,C++20 提供的协程也是非对称协程。
应该没有无栈对称协程……
无栈协程的模型
我们终于来到与 C++20 有关的东西了:无栈非对称协程。如果它不能表现得类似于一个线程,又有什么用,该怎么用?

图 1 中,main()
表示主流程,是一个普通的函数(不妨把 Python 的主过程看成一个函数),my_range()
是生成器,也就是一个协程。图中,黑点表示可以进入的点,普通函数只有开头一个,而无栈非对称协程则可以有任意多个,每个对应 Python 中的 yield
语句。
因此,可以把协程看作一个状态机,图 1 中,协程内的黑点就对应一个状态,协程内的箭头就对应状态的转移。需要注意的是,这个状态机还有大量隐藏的状态以局部变量的形式存在于协程中,随图中可见状态的转移而转移。
无栈协程在逻辑上总是可以用闭包的形式实现,但实际上很难写,甚至可能会写不出来。尽管如此,尝试将无栈协程和闭包相互转换,对理解无栈协程的工作原理会很有帮助。
#include <iostream>auto my_range() {// 每一个 lambda 表达式都对应图 1 协程中的一个黑点。int value = 0;// 通过按值捕获变量,将局部变量作为状态保存在闭包中。return [=]() mutable {std::cout << ++value << std::endl;// 通过按引用捕获变量,模拟局部变量的状态转移。return [&]() {std::cout << ++value << std::endl;return [&]() {std::cout << ++value << std::endl;return [&]() -> void {// 没有返回值。};};};};
}int main() {// 类似于 Python 中的生成器对象,状态均保存在名为 coroutine 的对象中。auto coroutine = my_range();// resume_point_* 不保存变量状态,只保存执行位置。const auto resume_point_1 = coroutine();const auto resume_point_2 = resume_point_1();resume_point_2();
}
运行结果:
1
2
3
程序 4 对应程序 1 和图 1,是使用 C++ 中的闭包模拟无栈协程的结果。从中可以感受到,如果编译器不支持协程相关的语法,只用闭包模拟无栈协程会有相当多的困难:
-
协程中的状态点越多,闭包的层数就越深。
如果尝试将闭包作为回调函数,复杂逻辑就会导致很深的闭包,称为回调地狱(callback hell)。如果能把程序 4 转换成程序 1 那样,回掉地狱问题就解决了。
# 更接近程序 4 模拟无栈协程的 Python 生成器。 def my_range():value = 0# 没有回调地狱!yield (value := value + 1)yield (value := value + 1)yield (value := value + 1)
可以参考:Java如何实现一个回调地狱(Callback Hell)? - 掘金 (juejin.cn)
通过诉诸协程解决回调地狱,靠的是扩展处理器的日常使用方法:过去我们只想到函数调用、中断,现在还可以通过自己保存栈帧来实现协程。除了向计算机底层寻求方法,还可以向更抽象的
-
协程中的局部变量作为内部状态,很难正确地处理。
比如,程序 4 中一会儿按值捕获,一会儿按引用捕获,很难弄清楚,特别是有更多零散的局部变量时。
-
如果有复杂的结构,例如循环结构,很难、甚至不能用闭包实现。
比如程序 4 就没有写出程序 1 中的循环结构。
-
闭包无法实现协程中的数据传递。
现在,我们大致明白了使用协程实现并发的方法(关键在于存在一个调度器),也知道了无栈协程的状态机模型。但我们仍然不知道如何用无栈协程实现并发,这是因为我们不知道无栈协程应该有怎样的调度器。
无栈协程的调度器
可以参考:万字好文:从无栈协程到C++异步框架! - 腾讯云技术社区 - SegmentFault 思否
可以参考:python中的yield、yield from、async/await中的区别与联系 - 简书 (jianshu.com)
可以参考:await 运算符 - 异步等待任务完成 | Microsoft Learn
可以参考:【译】图与例解读Async/Await - 知乎 (zhihu.com)
作为入门教程,我们当然不讨论无栈协程的调度器具体该怎么写,但是我们必须至少弄清楚无栈协程的调度器长什么样,不然怎么知道如何用它实现并发,怎么发挥协程的优势?
朴素的单线程调度器
很容易想到,可以让调度器变成一个死循环,不断轮流执行尚未完成的所有协程就可以了。
def my_range(to):for i in range(1, to + 1):yield iif __name__ == "__main__":coroutines = [my_range(3) for _ in range(4)]# 如果不是所有协程都已经结束,就继续执行。while not all(coroutine.gi_frame is None for coroutine in coroutines):# 轮流执行每个协程。for coroutine in coroutines:try:print(next(coroutine))except StopIteration:pass
运行结果:
1
1
1
1
2
2
2
2
3
3
3
3

虽然程序 5 似乎没啥用,但是我们得知了:
-
调度器一定是一个普通函数,而不是协程。因为我们讨论的是非对称协程,所以这些协程放弃执行权后会自动回到调度器上次执行的位置,对调度器而言执行协程就好比执行函数一样。
这意味着当我们希望协程表现出并发的特征时,首先需要调用一个调度器函数。
-
这种最朴素的调度器并不调度协程内创建的协程。比如程序 5 中,
my_range
里面创建了range
,它也是一个协程,但main
调度器看不见也管不着它。这意味着要想有栈协程那样允许在任意子调用中放弃执行权会很困难。
-
这种最朴素的调度器没有办法处理协程之间的依赖关系。比如程序 5 中,各个
my_range
产生的结果都是无关的。这意味着想要使用另一个协程的运行结果会很困难。
对于后两个问题,如果像程序 5 中 my_range
使用 range
那样,让协程 my_range
自己调度另一个协程 range
,并且又希望使用另一个协程的最后运行结果(因为我们通常更关心函数的返回值),代码就会变得很繁琐。请看下面的 Python 程序。
def my_complex_task(id):for i in range(3):print(f"Task {id}")yield# 需要拿到这个结果。yield id + 1def my_print(id):inner_coroutine = my_complex_task(id)# 繁琐:怎么拿到协程的返回值?last_yield = Nonefor result in inner_coroutine:last_yield = result# 繁琐:我自己调度,怎么知道什么时候自己该 yield?yield# 繁琐:如果这个协程也只是返回结果,然后在 main 里才进行输出,是不是以上繁琐还要再来一次?print(f"Result of {id}: {last_yield}")if __name__ == "__main__":coroutines = [my_print(i + 1) for i in range(2)]# 如果不是所有协程都已经结束,就继续执行。while not all(coroutine.gi_frame is None for coroutine in coroutines):# 轮流执行每个协程。for coroutine in coroutines:try:next(coroutine)except StopIteration:pass
运行结果:
Task 1
Task 2
Task 1
Task 2
Task 1
Task 2
Result of 1: 2
Result of 2: 3
程序 6 确实让 my_print
协程用到了 my_complex_task
协程的结果,并且成功表现出了并发的特征,但写出来实在是太繁琐了。如果 my_print
要用到 my_complex_task
的结果,怎么做更优美?
让协程学会等待
既然 my_print
要用到 my_complex_task
的结果,那就等 my_complex_task
结束吧。

事实上,“学会等待”是无栈协程的基本操作,因为这样就可以实现栈式的函数调用,同时保留了并发能力。在编程语言中,等待(await)就会导致协程被挂起(suspend),直到通知恢复(assume),协程才能继续被调度。用于并发操作的无栈协程本身常被称为异步(async)函数。
Python 中的异步函数
Python 的生成器虽然是无栈协程,但实际上不会用于并发场景,原因可以见程序 6。用于并发场景的无栈协程,也就是异步函数,在 Python 中的基本使用方法如下所示。
import asyncio# async 关键字表示这是一个协程。
async def my_complex_task(id):for i in range(3):print(f"Task {id}")# 主动放弃执行权。await asyncio.sleep(0)# 需要拿到这个结果。return id + 1# 结束,通知调用方(my_print),使其恢复。async def my_print(id):# 声称自己要等。等到结果后才会被继续调度。result = await my_complex_task(id)print(f"Result of {id}: {result}")if __name__ == "__main__":# 直接“调用”协程将会得到一个协程对象,并没有开始执行。tasks = [my_print(i) for i in range(3)]# 创建调度器。loop = asyncio.new_event_loop()# 调用调度器函数。loop.run_until_complete(asyncio.wait(tasks))# 回收调度器。loop.close()
运行结果:
Task 2
Task 1
Task 0
Task 2
Task 1
Task 0
Task 2
Task 1
Task 0
Result of 2: 3
Result of 1: 2
Result of 0: 1
程序 7 和程序 6 的功能一样,在单个线程中具有并发能力。但程序 7 的编写比程序 6 简单许多,正是“等待”使得无栈协程可以在调用其他协程的同时保持并发能力。缺点是,所有被调用的协程都需要用 async
关键字修饰,称这种现象为 async 传染。
图 3 说,await
会使新的协程被加入调度器,但这一点似乎从程序 7 中看不明白。事实上,要看透这一点,必须深入协程调度器的具体实现,所以这个问题需要留到讲解 C++20 的协程库时才能解决。
可等待对象

图 4 的意思是,协程必须等待的是另一个协程吗?只要等待的对象能够恢复(resume)调用方协程、能提供运行的结果,那就可以拿来等!这种对象就称为可等待对象(awaitable object)。
虽然可等待对象可以不是协程,但一般都是协程。图 4 中的 my_task
也有可能是协程吗?事实上是可能的,只要 main_task
在首次恢复时不被调度器指派到主线程上即可。
M:N 调度器——C# 中的异步函数
可以参考:await 运算符 - 异步等待任务完成 | Microsoft Learn
至此为止,我们只实现了并发,还没有实现并行。很容易想到,要让协程拥有并行的能力,只需要让调度器支持创建多个内核态线程就好了。
实现并行的关键是在恢复协程时为它分配到另一个线程上。我们直接看看 C# 的一个例子。
public class AwaitOperator
{public static async Task Main(){Task<int> downloading = DownloadDocsMainPageAsync(); // 立即开始执行,直到 await。返回值是 Task。Console.WriteLine($"{nameof(Main)}: Launched downloading. (on {Thread.CurrentThread.ManagedThreadId})");int bytesLoaded = await downloading;Console.WriteLine($"{nameof(Main)}: Downloaded {bytesLoaded} bytes. (on {Thread.CurrentThread.ManagedThreadId})");}private static async Task<int> DownloadDocsMainPageAsync(){Console.WriteLine($"{nameof(DownloadDocsMainPageAsync)}: About to start downloading. (on {Thread.CurrentThread.ManagedThreadId})");var client = new HttpClient();byte[] content = await client.GetByteArrayAsync("https://learn.microsoft.com/en-us/");Console.WriteLine($"{nameof(DownloadDocsMainPageAsync)}: Finished downloading. (on {Thread.CurrentThread.ManagedThreadId})");return content.Length;}
}
可能的运行结果:
DownloadDocsMainPageAsync: About to start downloading. (on 1)
Main: Launched downloading. (on 1)
DownloadDocsMainPageAsync: Finished downloading. (on 7)
Main: Downloaded 39995 bytes. (on 7)
程序 8 告诉我们:
- C# 可以在后台自动运行一个调度器,并且是 M:N 调度器。
- 调度器的调度工作在
await
语句处发生。协程挂起后,再次恢复时在哪个线程上由调度器决定。
小结
C++20 的协程是无栈非对称协程。无栈协程可以用于生成器,也可以用于并发场景。用于并发场景的协程也被称为异步函数。并发场景下,协程的调度器不可或缺。
无栈协程可以抽象为一个状态机,也可以用闭包模拟简单的无栈协程。使无栈协程并发的关键是 await 语句,可以等待对象返回结果后再接受调度。使无栈协程并行的关键是调度器,调度器可以在协程恢复运行时指派线程。不同编程语言实现的调度器各不相同,不同场景下所需的调度器也不相同,使用前需要充分调研所用调度器的特征。
C++20 中的协程对象
前面举了这么多例子,只是为了说明协程的功能。C++20 中的协程具体是怎样的?很遗憾,C++20 根本没提供协程的调度器,一切都需要自己写,所以大家才说 C++20 的协程是为库开发者准备的。
但如果学习了 C++20 中的协程,便可以说了解了协程的底层原理,处理其他语言中的协程也就游刃有余了。
(未完待续)
on 7)
程序 8 告诉我们:1. C# 可以在后台自动运行一个调度器,并且是 M:N 调度器。
2. 调度器的调度工作在 `await` 语句处发生。协程挂起后,再次恢复时在哪个线程上由调度器决定。### 小结C++20 的协程是无栈非对称协程。无栈协程可以用于生成器,也可以用于并发场景。用于并发场景的协程也被称为异步函数。并发场景下,协程的调度器不可或缺。无栈协程可以抽象为一个状态机,也可以用闭包模拟简单的无栈协程。使无栈协程并发的关键是 await 语句,可以等待对象返回结果后再接受调度。使无栈协程并行的关键是调度器,调度器可以在协程恢复运行时指派线程。不同编程语言实现的调度器各不相同,不同场景下所需的调度器也不相同,使用前需要充分调研所用调度器的特征。## C++20 中的协程对象前面举了这么多例子,只是为了说明协程的功能。C++20 中的协程具体是怎样的?很遗憾,C++20 根本没提供协程的调度器,一切都需要自己写,所以大家才说 C++20 的协程是为库开发者准备的。但如果学习了 C++20 中的协程,便可以说了解了协程的底层原理,处理其他语言中的协程也就游刃有余了。### (未完待续)
相关文章:

C++20 协程(coroutine)入门
文章目录 C20 协程(coroutine)入门什么是协程无栈协程和有栈协程有栈协程的例子例 1例 2 对称协程与非对称协程无栈协程的模型无栈协程的调度器朴素的单线程调度器让协程学会等待Python 中的异步函数可等待对象M:N 调度器——C# 中的异步函数 小结 C20 中…...
2023.8.6
2022河南萌新联赛第(三)场:河南大学\区间操作.cpp //题意:定义一个f[x]函数表示一个数分解质因数后各个质因子的幂次和,给定一个长度为n的数组, //有m个操作,第一种操作是输出[l, r]范围内的a…...
kubernetes网络之网络策略-----Network Policies - Default
默认情况下,如果名称空间中没有配置 NetworkPolicy,则该名称空间中,所有Pod的所有入方向流量和所有出方向流量都是被允许的。 那么如果我们想改变名称空间中默认的网络策略,又该怎么做呢? 默认拒绝所有的入方向流量 …...

奥威BI系统|秒分析,更适合分析大数据
根据以往的经验,当数据量多到一定程度就容易导致系统卡顿、崩溃。这种现象给企业级数据分析造成了极大的困扰。随着业务发展扩大和分析需求精细化,企业需要一套能秒分析大数据的系统。而奥威BI系统就是这样一款可以秒分析大数据的商业智能系统。 奥威BI…...

安全作业-Race竞争型漏洞、原型链污染
1.race漏洞一直卡在虚拟机安装上(待研究) 2.原型链污染 一、第一题js代码 const express require(express) var hbs require(hbs); var bodyParser require(body-parser); const md5 require(md5); var morganBody require(morgan-body); const app express(); var use…...
对微服务网关的一些总结
对微服务网关的一些总结 一. 什么是网关 网关是位于NGINX(或没有)与真实微服务间的转发服务。 用户通过HTTP接口,连接到NGINX,然后NGINX反向到M个网关。 网关根据[服务注册与发现],进行转发请求到具体的微服务上。 由于网关可编码&#…...

该选择WPF 还是 Winform?
WPF和WinForms都是.NET平台下的桌面应用程序开发框架,它们各有特点,适用于不同的场景和需求。下面是对WPF和WinForms的一些比较和优劣势:WPF(Windows Presentation Foundation):WPF具有强大的图形渲染能力&…...
概念解析 | ChatGPT技术概览
注1:本文系“概念解析”系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:ChatGPT技术概览 参考资料:Deng J, Lin Y. The benefits and challenges of ChatGPT: An overview[J]. Frontiers in Computing and Intelligent Systems, 2022, 2(2): 81-83. …...
用Rust实现23种设计模式之 代理模式
关注我,学习Rust不迷路!! 代理模式是一种结构型设计模式,它允许通过代理对象来控制对真实对象的访问。以下是代理模式的优点和使用场景: 优点: 控制访问:代理模式可以控制对真实对象的访问&a…...
【nlp pytorch】基于标注信息从句子中提取命名实体内容
基于标注信息从句子中提取实体内容 1 需求2 代码实现3 代码封装1 需求 给定一个句子和已经通过模型训练标注好的信息,从而提取出句子中的实体内容,如下 输入: (1)句子信息 每个糖尿病患者,无论是病情轻重,不论是注射胰岛素,还是口服降糖药,都必须合理地控制饮食。(2)…...

图为科技加入深圳市智能交通行业协会 ,打 …
图为科技加入深圳市智能交通行业协会,打造智能交通新生态! 交通是国民经济发展的“大动脉”,交通拥堵、事故频发等问题不仅影响了人们的出行体验,也对经济的发展产生了负面影响。安全、高效、便捷的出行,一直是人们的…...
大模型排行榜及相关基础技术
大模型排行榜 测试集CEval中文多个学科测试集排名MMLU大规模多任务语言理解英文排名,介绍斯坦福排行榜 强人工智能AGI相关基础技术 标题简介分类稳定扩散模型The Illustrated Stable Diffusion图示化讲解Jay讲解Stable Diffusion计算机技术资料Transformer图示化讲解…...

Python入门【try和except结构、常见异常、with上下文管理 、traceback模块和生成异常日志、自定义异常类】(十八)
👏作者简介:大家好,我是爱敲代码的小王,CSDN博客博主,Python小白 📕系列专栏:python入门到实战、Python爬虫开发、Python办公自动化、Python数据分析、Python前后端开发 📧如果文章知识点有错误…...
windows脚本获取管理员权限修改host
很多时候我们常常需要通过管理员权限执行脚本,脚本可能涉及到一些受保护信息的访问,我们写个简单的脚本来更改host文件,host文件就是需要管理员权限才能访问的启动脚本时先检查是否有管理员权限,如果没有就调用授权脚本进行管理员…...

Flask简介与基础入门
一、了解框架 Flask作为Web框架,它的作用主要是为了开发Web应用程序。那么我们首先来了解下Web应用程序。Web应用程序 (World Wide Web)诞生最初的目的,是为了利用互联网交流工作文档。 1、一切从客户端发起请求开始。 所有Flask程序都必须创建一个程序…...

Stable Diffusion 硬核生存指南:WebUI 中的 GFPGAN
本篇文章聊聊 Stable Diffusion WebUI 中的核心组件,强壮的人脸图像面部画面修复模型 GFPGAN 相关的事情。 写在前面 本篇文章的主角是开源项目 TencentARC/GFPGAN,和上一篇文章《Stable Diffusion 硬核生存指南:WebUI 中的 CodeFormer》提…...

IO模型-信号驱动IO
linux内核中存在一个信号SIGIO,这个信号就是用于实现信号驱动IO的。当应用程序中想要以信号驱动IO的模型读写硬件数据时,首先注册一个SIGIO信号的信号处理函数,当硬件数据就绪,硬件会发起一个中断,在硬件的中断处理函数中向当前进…...

每日一题——回文链表
回文链表 题目链接 回文结构即字符串正序逆序完全一致,如“1 2 3 4 3 2 1”,那么我们就要想办法同时比较链表头和链表尾的元素,看其是否相等。 下面介绍一种最常用的方法: 思路 如果我们仔细观察回文结构,就会得到一…...

OPENCV C++(一) 二进制和灰度原理 处理每个像素点值的方法
#include <opencv2/opencv.hpp> using namespace std; using namespace cv;必须包含的头文件! 才能开始编写代码 读取相片 一般来说加个保护程序 不至于出error和卡死 Mat image imread("test.webp"); //存放自己图像的路径 if (image.empty()){p…...

Python GUI编程(Tkinter)
Python GUI编程(Tkinter) Python 提供了多个图形开发界面的库,几个常用 Python GUI 库如下: Tkinter: Tkinter 模块(Tk 接口)是 Python 的标准 Tk GUI 工具包的接口 .Tk 和 Tkinter 可以在大多数的 Unix 平台下使用,同样可以应用在 Windows …...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...

(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...

云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...