关于协程的文章集合

https://learningos.cn/os-lectures/lec11/p3-labs.html#8icon-default.png?t=N7T8https://learningos.cn/os-lectures/lec11/p3-labs.html#8

有栈协程与无栈协程icon-default.png?t=N7T8https://mthli.xyz/stackful-stackless/

使用 C 语言实现协程icon-default.png?t=N7T8https://mthli.xyz/coroutines-in-c/

深入理解达夫设备icon-default.png?t=N7T8https://mthli.xyz/duff-device/

这是篇好文章

使用 C 语言实现协程一种基于达夫设备的思想实现的协程。icon-default.png?t=N7T8https://mthli.xyz/coroutines-in-c/有栈协程与无栈协程 - 知乎转载自笔者的同名博客 有栈协程与无栈协程。如今协程已经成为大多数语言的标配,例如 Golang 里的 goroutine,JavaScript 里的 async/await。尽管名称可能不同,但它们都可以被划分为两大类,一类是有栈(stackful…icon-default.png?t=N7T8https://zhuanlan.zhihu.com/p/330606651blog/content/blog/stackful-stackless/index.md at master · mthli/blog · GitHubYet another blog of myself 👀. Contribute to mthli/blog development by creating an account on GitHub.icon-default.png?t=N7T8https://github.com/mthli/blog/blob/master/content/blog/stackful-stackless/index.md

保存上下文即是保存从这个函数及其嵌套函数的(连续的)栈帧存储的值,以及此时寄存器存储的值;恢复上下文即是将这些值分别重新写入对应的栈帧和寄存器;而切换上下文无非是保存当前正在运行的函数的上下文,恢复下一个将要运行的函数的上下文。

我们需要申请一段能存储上下文的内存空间。在保存上下文时,我们可以选择把上下文都拷贝到这段内存;亦或者直接将这段内存作为协程运行时的栈帧空间,这样就能避免拷贝带来的性能损失了。注意,如果申请的内存空间小了,协程在运行时会爆栈;如果大了,则浪费内存;


完全地抛开调用栈的概念。请停止这种一个程序是 caller 而另一个程序是 callee 的思想,并开始把他们想象为【平等的协作者】,这就是【协程名字的来源】

对于 callee 而言,我们需要一种【返回且继续】(return and continue)的操作:从一个函数中返回,且当这个函数下次被 继续调用时,从它上一次返回之后的地方开始执行

async/await 无栈协程关键字具有传染性问题


GitHub - jimblandy/context-switch: Comparison of Rust async and Linux thread context switch time.Comparison of Rust async and Linux thread context switch time. - jimblandy/context-switchicon-default.png?t=N7T8https://github.com/jimblandy/context-switch

These are a few programs that try to measure context switch time and task memory use in various ways. In summary:
这些是一些尝试以各种方式测量上下文切换时间和任务内存使用情况的程序。总之:

  • A context switch takes around 0.2µs between async tasks, versus 1.7µs between kernel threads. But this advantage goes away if the context switch is due to I/O readiness: both converge to 1.7µs. The async advantage also goes away in our microbenchmark if the program is pinned to a single core. So inter-core communication is something to watch out for.
    异步任务之间的上下文切换大约需要 0.2μs,而内核线程之间的上下文切换需要 1.7μs。但如果上下文切换是由于 I/O 准备就绪而导致的,那么这种优势就会消失:两者都会收敛到 1.7μs。如果程序固定到单个核心,异步优势也会在我们的微基准测试中消失。因此,核心间的通信是需要注意的。

  • Creating a new task takes ~0.3µs for an async task, versus ~17µs for a new kernel thread.
    对于异步任务,创建新任务大约需要 0.3μs,而新内核线程则需要大约 17μs。

  • Memory consumption per task (i.e. for a task that doesn't do much) starts at around a few hundred bytes for an async task, versus around 20KiB (9.5KiB user, 10KiB kernel) for a kernel thread. This is a minimum: more demanding tasks will naturally use more.
    对于异步任务,每个任务(即不执行太多操作的任务)的内存消耗约为几百字节,而内核线程的内存消耗约为 20KiB(9.5KiB 用户,10KiB 内核)。这是最小值:要求更高的任务自然会使用更多。

  • It's no problem to create 250,000 async tasks, but I was only able to get my laptop to run 80,000 threads (4 core, two way HT, 32GiB).
    创建 250,000 个异步任务没有问题,但我只能让我的笔记本电脑运行 80,000 个线程(4 核、双向 HT、32GiB)。

一个cpp协程库的前世今生(一)缘起_协程_SkyFire_InfoQ写作社区背景撸一个协程库是我的一个夙愿,从大学知道有协程这么个东西之后就想自己写一个,所以并不是说现在开源的协程不满足我的需求,而是纯粹想体验一下从头开始设计开发会遇到什么困难,而我又能从中学到什么。我想做成icon-default.png?t=N7T8https://xie.infoq.cn/article/06650f12b79d865fb1bddf491

一个cpp协程库的前世今生(二)协程切换的原理_c++_SkyFire_InfoQ写作社区再说协程框架之前,我们先了解一下协程切换的原理是什么。下面的描述主要围绕x86_64体系架构、linux操作系统进行。阅读本节需要的基础知识阅读本节需要了解以下基础知识:各种寄存器的用途栈的结构calicon-default.png?t=N7T8https://xie.infoq.cn/article/a9ba6f5fd4ac855de9f9be308

浅谈C++20 协程那点事儿​本文是 C++20 的协程入门文章,作者围绕协程的概念到协程的实现思路全方位进行讲解,努力让本文成为全网最好理解的「C++20 协程」原理解析文章。icon-default.png?t=N7T8https://mp.weixin.qq.com/s/0njDHtz_SGPkrr4ndAWHaA

Measuring context switching and memory overheads for Linux threads - Eli Bendersky's websiteicon-default.png?t=N7T8https://eli.thegreenplace.net/2018/measuring-context-switching-and-memory-overheads-for-linux-threads/


 从操作系统层面而言,进程是分配资源的基本单位,线程在很长时间被称为轻量级的进程,是程序执行的基本单位。

比如说一个游戏,启动后为一个进程,但是一个游戏场面的呈现需要图形的渲染,联网,这些操作不能相互的阻塞,如果阻塞了,卡起就很难受,总觉得这游戏怎么这么 low,我们希望它们能同时的运行,所以将其各个部分设计为线程,这就出现了一个进程有多个线程

用户态线程

它完全是在用户空间创建,对于操作系统而言是不知情的,用户级线程的优势如下:

  • 切换成本低:用户空间自己维护,不用走操作系统的调度

  • 管理开销小:创建和销毁不用系统调用,系统调用所造成的上下文切换下文会讲解

用户态线程有什么缺点

  • 与内核沟通成本大:因为这种线程大部分时间在用户空间,如果进行 IO 操作,很难利用内核的优势,且需要频繁的用户态和内核态的切换

  • 线程之间的协作麻烦:想象两个线程 A 和 B需要通信,通信通常会涉及到 IO 操作,IO 操作涉及到系统调用,系统调用又要发生用户态和内核套的切换成本,难

  • 操作系统无法针对线程的调度进行优化:如果一个进程的用户态线程阻塞了操作系统无法及时的发现和处理阻塞问题,它不会切换其他线程从而造成浪费

内核态线程

内核态线程执行在内核态,一般通过系统调用创造一个内核级线程,那么有哪些优点?

  • 操作系统级优化:内核中的线程即使执行 IO 操作也不需要进行系统调用,一个内核阻塞可以让其他立即执行

  • 充分利用多核优势:内核权限足够高,可以在多个 CPU 核心执行内核线程

内核级线程有什么缺点?

  • 创建成本比较高:创建的时候需要使用系统调用即切换到内核态

  • 切换成本高:切换的时候需要进行内核操作

  • 扩展性差:因为一个内核管理,坑位有限,不可能数量太多

线程的上下文切换将分为两个部分

  • 两个线程不属于同一个进程,那么资源不共享,所以切换过程就会涉及到进程的上下文切换,需要切换虚拟页表

  • 第二种情况即两个线程属于同一个进程。因为共享虚拟内存,所以切换的时候这些资源保持不动,只需要切换线程的私有数据等不共享的数据

这也从侧面表明了,进程内的线程切换比多进程间的切换会节省不少资源,这也是多线程逐渐替代多进程的一个优势

怎么处理在协程中调用阻塞IO的操作呢

  • 比较简答的思路是当调用阻塞 IO 的时候,重新启动一个线程去执行这个操作,等执行完成后,协程再去读取结果,这是不是和多线程很像

  • 将系统 IO 进行封装,改为异步调用的方式,此时需要大量的工作,所以需要寄生于编程语言的原生支持

打开线程 | 进程 | 协程的大门icon-default.png?t=N7T8https://mp.weixin.qq.com/s/2rVYPeKBnTrFoSxmUEM08g


在切换上下文时和构造协程环境的时候需要额外注意一下,除此以外 300行也就搞定了:https://github.com/skywind3000/collection/tree/master/vintage/context

用 ctx_makecontext / ctx_swapcontext 分分钟可以包装出一个 yield,具体包装方法可以见云风的 cloudwu/coroutine 项目。

在 Windows 下, 我们可以通过 fiber 来实现 coroutine , 在 posix 下, 有更简单的选择就是 setcontext 。

纤程

Widnows 是提供了用户级线程的,类似 coroutine 需要用户主动是切换。这在单线程程序中非常有用。线程调度模块只负责提供堆栈,环境的保存。不负责分配时间片等。

自己实现 coroutine 并不难,但能用操作系统提供的可以得到更多的便利。Windows 中把这种用户级线程叫做 Fiber,纤维的意思。比较通用的译名是纤程。

我们可以把一个 thread 转换成一个 fiber ,用到的 API 是 ConvertThreadToFiber。其实用的更多的是CreateFiber,它可以创建一个纤程,但并不切换过去运行。

被创建出来的 Fiber 会有一个上下文的地址被返回,用于以后的切换操作。我们可以用 SwitchToFiber 来切换。这是唯一用于 Fiber 释放操作权的途径。SwitchToFiber 必须显式的指定切换的目标,所以 Fiber 调度的工作需要我们自己写代码来实现。

GetCurrentFiber 和 GetFiberData 这两个函数都很有用,一个用来取到运行环境,一个用来取得创建参数,这两个函数都是用 inline 函数的形式提供在 .h 文件中的。

最近都流行实现 Coroutine 么? - 知乎这两天看着大家都在实现无栈的 coroutine 都挺好玩的,但无栈协程限制太多,工程实践上很少用,所以昨天手痒写了个有栈的 coroutine ,接口反照 ucontext 的接口,不比无栈的复杂多少: int main(void) { ctx_con…icon-default.png?t=N7T8https://zhuanlan.zhihu.com/p/32431200


假设磁盘上有10个文件,你需要读取的内存,那么你该怎么用代码实现呢?

最简单的方法——串行

最简单方法,那就是一个一个的读取,读完一个接着读下一个。用代码表示是这样的:

for file in files:
  result = file.read()
  process(result)

我们假设每个文件读取需要1分钟,那么10个文件总共需要10分钟才能读取完成。这种方法有什么问题呢?实际上这种方法只有一个问题,那就是。除此之外,其它都是优点:

  1. 代码简单,容易理解
  2. 可维护性好,这代码交给谁都能维护的了(论程序员的核心竞争力在哪里)

那么慢的问题该怎么解决呢?为啥要一个一个读取呢?并行读取不就可以加快速度了吗。

稍好的方法,并行

那么,该怎么并行读取文件呢?我们可以同时开启10个线程,每个线程中读取一个文件。

def read_and_process(file):
  result = file.read()
  process(result)

def main():
  files = [fileA,fileB,fileC......]
  for file in files:
     create_thread(read_and_process,
                   file).run()
  # 等待这些线程执行完成

那么这种方法有什么问题吗?在开启10个线程这种问题规模下没有问题。现在我们把问题难度加大,假设有10000个文件,需要处理该怎么办呢?直接创建10000个线程去读不可以吗?虽然线程号称“轻量级进程”,虽然是轻量级但当数量足够可观时依然会有性能问题。这里的问题主要有这样几个方面:

  1. 创建线程需要消耗系统资源,像内存等(想一想为什么?)
  2. 调度开销,尤其是当线程数量较多且都比较繁忙时(同样想一想为什么?)
  3. 创建多个线程不一定能加快I/O(如果此时设备处理能力已经饱和)

事件驱动 + 异步

即使在单个线程中,使用事件驱动+异步也可以实现IO并行处理,Node.js就是非常典型的例子。为什么单线程也可以做到(IO)并行呢?这是基于这样两个事实:

  1. 相对于CPU的处理速度来说,IO是非常慢的
  2. IO不怎么需要计算资源

因此,当我们发起IO操作后为什么要一直等着IO执行完成呢?在IO执行完之前的这段时间处理其它IO难道不香吗?这就是为什么单线程也可以并行处理多个IO的本质所在。回到我们的例子,该怎样用事件驱动+异步来改造上述程序呢?实际上非常简单。首先我们需要创建一个event loop,这个非常简单:

event_loop = EventLoop()

然后,我们需要往event loop中加入原材料,也就是需要监控的event,就像这样:

def add_to_event_loop(event_loop, file):
   file.asyn_read() # 文件异步读取
   event_loop.add(file)

注意当执行file.asyn_read这行代码时会立即返回,不会阻塞线程,当这行代码返回时可能文件还没有真正开始读取,这就是所谓的异步。file.asyn_read这行代码的真正目的仅仅是发起IO,而不是等待IO执行完成。此后我们将该IO放到event loop中进行监控,也就是event_loop.add(file)这行代码的作用。一切准备就绪,接下来就可以等待event的到来了:

while event_loop:
   file = event_loop.wait_one_IO_ready()
   process(file.result)

我们可以看到,event_loop会一直等待直到有文件读取完成(event_loop.wait_one_IO_ready()),这时我们就能得到读完的文件了,接下来处理即可。全部代码如下所示:

 def add_to_event_loop(event_loop, file):
   file.asyn_read() # 文件异步读取
   event_loop.add(file)

def main():
  files = [fileA,fileB,fileC ...]
  event_loop = EventLoop()
  for file in files:
      add_to_event_loop(event_loop, file)
      
  while event_loop:
     file = event_loop.wait_one_IO_ready()
     process(file.result)

[多线程] VS [单线程 + event loop]

接下来我们看下程序执行的效果。在多线程情况下】,假设有10个文件,每个文件读取需要1秒,那么同时并行读取10个文件需要1秒(可能多一点点)。那么对于单线程+event loop】呢?我们再次看下event loop + 异步版本的代码:

def add_to_event_loop(event_loop, file):
   file.asyn_read() # 文件异步读取
   event_loop.add(file)

def main():
  files = [fileA,fileB,fileC......]
  event_loop = EventLoop()
  for file in files:
      add_to_event_loop(event_loop, file)
      
  while event_loop:
     file = event_loop.wait_one_IO_ready()
     process(file.result)

对于add_to_event_loop,由于文件异步读取,因此该函数可以瞬间执行完成,真正耗时的函数其实就是event loop的等待函数,也就是这样

file = event_loop.wait_one_IO_ready()

我们知道,一个文件的读取耗时是1秒,因此该函数在1s后才能返回,但是,但是,接下来是重点。但是虽然该函数wait_one_IO_ready会等待1s,不要忘了,我们利用这两行代码同时发起了10个IO操作请求

for file in files:  
    add_to_event_loop(event_loop, file)

因此在event_loop.wait_one_IO_ready等待的1s期间,剩下的9个IO也完成了,也就是说event_loop.wait_one_IO_ready函数只是在第一次循环时会等待1s,但是此后的9次循环会直接返回,原因就在于剩下的9个IO也完成了因此整个程序的执行耗时也是1秒。是不是很神奇,我们只用一个线程就达到了10个线程的效果。这就是event loop + 异步的威力所在

一个好听的名字:Reactors模式

本质上,我们上述给出的event loop简单代码片段做的事情本质上和生物一样:给出刺激,做出反应。我们这里的给出event,然后处理event。这本质上就是所谓的Reactors模式。现在你应该明白所谓的Reactors模式是怎么一回事了吧。所谓的一些看上去复杂的异步框架其核心不过就是这里给出的代码片段,只是这些框架可以支持更加复杂的多阶段任务处理以及各种类型的IO。而我们这里给出的代码片段只能处理文件读取这一类IO。

把回调也加进来

如果我们需要处理各种类型的IO上述代码片段会有什么问题吗?问题就在于上述代码片段就不会这么简单了,针对不同类型会有不同的处理方法,因此上述process方法需要判断IO类型然后有针对性的处理,这会使得代码越来越复杂,越来越难以维护

幸好我们也有应对策略,这就是回调。类似于模板方法,把变化的部分和不同的部分提取到回调中。否则就需要用 if 判断各种IO类型进行分支处理,这样的话代码就不能统一了。 

我们可以把IO完成后的处理任务封装到回调函数中然后和IO一并注册到event loop。就像这样:

def IO_type_1(event_loop, io):
  io.start()
  
  def callback(result):
    process_IO_type_1(result)
    
  event_loop.add((io, callback))

这样,event_loop在检测到有IO完成后就可以把该IO和关联的callback处理函数一并检索出来,直接调用callback函数就可以了。

while event_loop:
   io, callback = event_loop.wait_one_IO_ready()
   callback(io.result)

看到了吧,这样event_loop内部就极其简洁了,even_loop根本就不关心该怎么处理该IO结果,这是注册的callback该关心的事情,event_loop需要做的仅仅就是拿到event以及相应的处理函数callback,然后调用该callback函数就可以了。现在我们可以用单线程来并发编程了,也使用callback对IO处理进行了抽象,使得代码更加容易维护,想想看还有没有什么问题

回调函数的问题

虽然回调函数使得event loop内部更加简洁,但依然有其它问题,让我们来仔细看看回调函数:

def start_IO_type_1(event_loop, io):
  io.start()
  
  def callback(result):
    process_IO_type_1(result)
    
  event_loop.add((io, callback))

从上述代码中你能看到什么问题吗?在上述代码中,一次IO处理过程被分为了两部分:

  1. 发起IO  io.start()
  2. IO处理  callback

其中第2部分放到了回调函数中,这样的异步处理天然不容易理解,这和我们熟悉的发起IO,等待IO完成、处理IO结果的同步模块有很大差别。这里的给的例子很简单,所以你可能不以为意,但是当处理的任务非常复杂时,可能会出现回调函数中嵌套回调函数,也就是回调地狱,这样的代码维护起来会让你怀疑为什么要称为一名苦逼的码农。

问题出在哪里

让我们再来仔细的看看问题出在了哪里?同步编程模式下很简单,但是同步模式下发起IO,线程会被阻塞,这样我们就不得不创建多个线程,但是创建过多线程又会有性能问题。这样为了发起IO后不阻塞当前线程我们就不得不采用异步编程+event loop。在这种模式下,异步发起IO不会阻塞调用线程,我们可以使用单线程加异步编程的方法来实现多线程效果,但是在这种模式下处理一个IO的流程又不得不被拆分成两部分,这样的代码违反程序员直觉,因此难以维护。那么很自然的,有没有一种方法既能有同步编程的简单理解又会有异步编程的非阻塞呢?答案是肯定的,这就是协程。

Finally!终于到了协程

利用协程我可以以同步的形式来异步编程。这是什么意思呢?我们之所以采用异步编程是为了发起IO后不阻塞当前线程,而是用协程,程序员可以自行决定在什么时刻挂起当前协程,这样也不会阻塞当前线程。而协程最棒的一点就在于挂起后可以暂存执行状态恢复运行后可以在挂起点继续运行,这样我们就不再需要像回调那样将一个IO的处理流程拆分成两部分了。因此我们可以在发起异步IO,这样不会阻塞当前线程,同时在发起异步IO后挂起当前协程,当IO完成后恢复该协程的运行,这样我们就可以实现同步的方式来异步编程了。接下来我们就用协程来改造一下回调版本的IO处理方式:

def start_IO_type_1(io):
  io.start() # IO异步请求
  yield      # 暂停当前协程 
  process_IO_type_1(result) # 处理返回结果

此后我们要把该协程放到event loop中监控起来:

def add_to_event_loop(io, event_loop):
  coroutine = start_IO_type_1(io)
  next(coroutine)
  event_loop.add(coroutine)

最后,当IO完成后event loop检索出相应的协程并恢复其运行:

while event_loop:
   coroutine = event_loop.wait_one_IO_ready()
   next(coroutine)

现在你应该看出来了吧,上述代码中没有回调也没有把处理IO的流程拆成两部分,整体的代码都是以同步的方式来编写,最棒的是依然能达到异步的效果。实际上你会看到,采用协程后我们依然需要基于事件编程的event loop,因为本质上协程并没有改变IO的异步处理本质,只要IO是异步处理的那么我们就必须依赖event loop来监控IO何时完成,只不过我们采用协程消除了对回调的依赖,整体编程方式上还是采用程序员最熟悉也最容易理解的同步方式。

总结

  1. 单线程串行 + 阻塞式IO(同步)
  2. 多线程并行 + 阻塞式IO(并行)
  3. 单线程 + 非阻塞式IO(异步) + event loop
  4. 单线程 + 非阻塞式IO(异步) + event loop + 回调
  5. Reactor模式(更好的单线程 + 非阻塞式IO+ event loop + 回调)
  6. 单线程 + 非阻塞式IO(异步) + event loop + 协程

协程到底有什么用?6种I/O模式告诉你! - 知乎今天来聊一聊协程的作用。 假设磁盘上有10个文件,你需要读取的内存,那么你该怎么用代码实现呢? 在接着往下看之前,先自己想一想这个问题,看看自己能想出几种方法,各自有什么样的优缺点。想清楚了吗(还在看吗)…icon-default.png?t=N7T8https://zhuanlan.zhihu.com/p/515753112


public class NIOServer {
    public static void main(String[] args) throws IOException {
        Selector serverSelector = Selector.open();
        Selector clientSelector = Selector.open();

        new Thread(() -> {
            try {
                // 对应IO编程中的服务端启动
                ServerSocketChannel listenerChannel = ServerSocketChannel.open();
                listenerChannel.socket().bind(new InetSocketAddress(8000));
                listenerChannel.configureBlocking(false);
                listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);

                while (true) {
                    // 监测是否有新连接,这里的1指阻塞的时间为 1ms
                    if (serverSelector.select(1) > 0) {
                        Set<SelectionKey> set = serverSelector.selectedKeys();
                        Iterator<SelectionKey> keyIterator = set.iterator();

                        while (keyIterator.hasNext()) {
                            SelectionKey key = keyIterator.next();

                            if (key.isAcceptable()) {
                                try {
                                    // (1)每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector
                                    SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
                                    clientChannel.configureBlocking(false);
                                    clientChannel.register(clientSelector, SelectionKey.OP_READ);
                                } finally {
                                    keyIterator.remove();
                                }
                            }

                        }
                    }
                }
            } catch (IOException ignored) {
            }

        }).start();


        new Thread(() -> {
            try {
                while (true) {
                    // (2)批量轮询哪些连接有数据可读,这里的1指阻塞的时间为 1ms
                    if (clientSelector.select(1) > 0) {
                        Set<SelectionKey> set = clientSelector.selectedKeys();
                        Iterator<SelectionKey> keyIterator = set.iterator();

                        while (keyIterator.hasNext()) {
                            SelectionKey key = keyIterator.next();

                            if (key.isReadable()) {
                                try {
                                    SocketChannel clientChannel = (SocketChannel) key.channel();
                                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                                    // (3)面向Buffer
                                    clientChannel.read(byteBuffer);
                                    byteBuffer.flip();
                                    System.out.println(Charset.defaultCharset().newDecoder(). decode(byteBuffer)
                                            .toString());
                                } finally {
                                    keyIterator.remove();
                                    key.interestOps(SelectionKey.OP_READ);
                                }
                            }

                        }
                    }
                }
            } catch (IOException ignored) {
            }
        }).start();


    }
}

NIO来解释一下核心思路。

  • NIO模型中通常会有两个线程,每个线程都绑定一个轮询器Selector。在这个例子中,serverSelector负责轮询是否有新连接,clientSelector负责轮询连接是否有数据可读。

  • 服务端监测到新连接之后,不再创建一个新线程,而是直接将新连接绑定到clientSelector上,这样就不用IO模型中的1万个while循环死等,参见(1)。

  • clientSelector被一个while死循环包裹着,如果在某一时刻有多个连接有数据可读,那么通过clientSelector.select(1)方法可以轮询出来,进而批量处理。

  • 数据的读写面向Buffer。

 让Netty“榨干”你的CPU使用Netty之后整个世界都变美好了icon-default.png?t=N7T8https://mp.weixin.qq.com/s/Q27R1weSBeC5j9vNar7O4w

为什么要有协程,协程是如何实现的?高并发的解决方案icon-default.png?t=N7T8https://mp.weixin.qq.com/s/UF_lkPKPkEVwzTk0ermung终于懂了:协程思想的起源与发展协程概念比线程概念更早出现icon-default.png?t=N7T8https://mp.weixin.qq.com/s/Wy-_J9TlU3aSAJt24ZT5hw为什么协程切换的代价比线程切换低? - 知乎因为这两者根本不是同一种东西。协程是一边吃饭一边说话。线程是一边听音乐一边写作业。协程其本身是串行…icon-default.png?t=N7T8https://www.zhihu.com/question/308641794/answer/571632526?utm_source=wechatMessage_undefined_bottom浅谈有栈协程与无栈协程 - 知乎协程相关视频解析: linux系统下协程的实现与原理剖析训练营(上) linux系统下协程的实现与原理剖析训练营(下)如今虽不敢说协程已经是红的发紫,但确实是越来越受到了大家的重视。Golang中的已经是只有goroutin…icon-default.png?t=N7T8https://zhuanlan.zhihu.com/p/347445164?utm_source=wechatMessage_undefined_bottom协程的实现 - 知乎协程与线程通过上文(协程之CPU切换),可以明白上下文切换进程>线程>协程,这就是进化路线。 协程的问题就是系统并不感知,所以操作系统不会帮你做切换。 那么谁来帮你做切换?让需要执行的协程更多的获得C…icon-default.png?t=N7T8https://zhuanlan.zhihu.com/p/57869129?utm_source=wechat_session&utm_medium=social&s_r=0一个cpp协程库的前世今生(二)协程切换的原理_c++_SkyFire_InfoQ写作社区再说协程框架之前,我们先了解一下协程切换的原理是什么。下面的描述主要围绕x86_64体系架构、linux操作系统进行。阅读本节需要的基础知识阅读本节需要了解以下基础知识:各种寄存器的用途栈的结构calicon-default.png?t=N7T8https://xie.infoq.cn/article/a9ba6f5fd4ac855de9f9be308

Google “战败”后,C++20 用微软的提案进入协程时代!经过多年的酝酿、争论、准备后,协程终于进入 C++20 标准。icon-default.png?t=N7T8https://mp.weixin.qq.com/s/SlTObQQeDXvLLXuoxbO1yg

工作 6 年多,我还是没有搞懂什么是协程的道与术工作 6 年了,还是没搞懂协程icon-default.png?t=N7T8https://mp.weixin.qq.com/s/SBDntpy5lE2SHV4I1F4Lfw

 Golang 并发原理分析-51CTO.COMGo实现了两种并发形式。第一种是大家普遍认知的:多线程共享内存。其实就是Java或者C++等语言中的多线程开发。另外一种是Go语言特有的,也是Go语言推荐的:CSP(communicating sequential processes)并发模型。icon-default.png?t=N7T8https://developer.51cto.com/article/706382.html

JEP 425: Virtual Threads (Preview)icon-default.png?t=N7T8https://openjdk.java.net/jeps/425 
      java 虚拟线程,java的用户级线程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值