怎么理解kotlin的挂起函数?

中断当前协程,执行挂起函数的函数体,函数体会调用恢复协程的操作。

协程都被挂起了,那么挂起函数的函数体由谁执行呢?

这是一个很好的问题,涉及到协程与挂起函数的工作机制。

当协程达到一个挂起函数时,协程的执行会被暂停(即协程被挂起),但它并不会阻塞执行该挂起函数的线程。挂起函数的函数体仍然由当前线程(或在某些情况下由其他线程)继续执行。

 

为了更清楚地解释这一点,我们可以使用以下的步骤:

协程启动: 当一个协程开始运行时,它会在某个线程上执行。

遇到挂起函数: 当协程中的代码执行到一个挂起函数时,协程的执行会被暂停。

挂起函数执行: 尽管协程被暂停了,挂起函数的函数体仍然会继续在原始线程上执行(除非该挂起函数明确地指定了其他的执行上下文)。

挂起函数完成: 一旦挂起函数完成其工作,它会通知协程库,然后协程会在适当的时间和上下文中被恢复。

例如,假设你在主线程中启动了一个协程,并在该协程中调用了一个网络请求的挂起函数。这个协程会被挂起,但实际的网络请求(挂起函数的函数体)可能会在另一个线程(如网络线程)上执行。当网络请求完成时,协程会被通知并在原始的主线程上恢复执行。

 

关键点是:挂起函数的设计目的是为了允许协程在等待某个操作完成时不阻塞线程,而这个操作(挂起函数的函数体)可能在同一个线程或另一个线程上执行,具体取决于该挂起函数的实现。

 

suspend fun foo()

   1.执行耗时的同步操作(由线程执行,协程因为挂起了,所以不会由协程执行),阻塞等待耗时操作完成,这里阻塞只阻塞当前协程。

  2.执行协程恢复

   

如果是由线程执行,那线程不就阻塞了吗?

阻塞是在另一个线程和这个协程无关的线程上。

当我们在协程中执行一些可能导致阻塞的操作(如网络I/O或文件I/O),这些操作实际上通常是在背后的其他线程(通常是线程池中的线程)上执行的,以确保不阻塞启动协程的主线程。

例如,当你在协程中使用一个特定于I/O的挂起函数,它可能在Dispatchers.IO线程池上运行该操作,这样主线程就不会被阻塞。

总结一下:在协程中使用挂起函数时,任何可能的“阻塞”操作都会转移到其他线程上执行,这样启动协程的原始线程(例如主线程)就不会被实际阻塞。这使得协程特别适合于UI线程编程,因为它可以确保UI线程保持响应。

那如果协程的线程,和阻塞请求的线程,是同一个线程,阻塞请求的时候,是不是就不用跑其他协程了

如果协程的线程和执行阻塞操作的线程是同一个线程,那么当线程被阻塞时,该线程上的其他协程也会被暂停,因为它们共享同一个线程资源。

 

这就是为什么在使用Kotlin协程时,推荐使用特定的调度器(如 Dispatchers.IO 用于I/O操作)来确保阻塞操作在专门的线程或线程池上执行,从而避免阻塞主线程或其他协程的执行。

 

但是,如果你确实在同一个线程上启动了一个协程,并在其中执行了阻塞操作,那么这个线程上的所有操作(包括其他协程)都会被阻塞,直到阻塞操作完成。这种情况应该尽量避免,因为它违背了使用协程的初衷,即提供非阻塞的并发机制。

 

耗时操作会和协程在同一个线程吗?如果是会阻塞该线程下的其他协程,所以kotlin如何避免冲突的?

当你在某个调度器上启动一个协程并在其中执行耗时操作时,该耗时操作确实会在协程的线程上执行。如果该操作是阻塞的,那么它会阻塞该线程。如果这个线程上还有其他协程,那么它们也会被暂停,直到阻塞操作完成。

但是,Kotlin协程设计得非常灵活,以避免这样的冲突。以下是Kotlin如何处理这种情况的策略:

 

线程池:像Dispatchers.IO或Dispatchers.Default这样的调度器实际上是线程池。这意味着,即使其中一个线程被阻塞,其他线程仍然可以继续执行其他任务或协程。

 

 

挂起函数并不能解决阻塞,只是转移了阻塞到另一个线程。 

nodejs的背后,也是多线程啊,和这个很像

尽管JavaScript代码在单线程上执行,但Node.js使用了多线程来处理背后的I/O操作。这是通过libuv库实现的,该库提供了一个线程池来处理那些不能异步执行的操作。例如,当你在Node.js中执行一个文件系统操作,JavaScript的调用是非阻塞的,但实际的文件系统操作可能是在libuv的工作线程上执行的。

尽管Node.js的JavaScript执行环境是单线程的,但在其背后,尤其是在进行I/O操作时,确实存在多线程的处理。这种结合单线程事件循环和多线程I/O的模型使Node.js能够高效地处理大量并发的I/O密集型任务。

背后的工作线程会发事件给主线程,从而驱动事件循环。

是不是web的架构很像啊

nodejs的主线程相当于nginx,只是承载链接,不干实际活儿,相当于分配工作的人,他不能阻塞,就可以一直接活儿,但实际干活的是工作线程。kotlin的协程模型也是一样的吧?

 

在某种程度上,您的比喻是有道理的。Node.js的主线程、Nginx的工作机制以及Kotlin的协程在某些方面确实有类似之处,特别是在非阻塞操作和事件驱动的架构方面。下面是一个简化的比较:

 

Node.js 的主线程负责处理事件循环和调度回调函数,正如您所说,它“承载链接”,但不直接执行阻塞I/O或CPU密集型任务。这些任务通常由系统或libuv线程池中的工作线程在后台处理。

Nginx 采用事件驱动的架构,主进程不直接处理客户端的请求,而是分配给工作进程。每个工作进程都是非阻塞的,并且能够处理多个并发连接,确保主进程能够继续接受新的连接。

 

Kotlin 协程 提供了一种在单个线程上以非阻塞方式执行多个任务的能力。协程调度器负责协程的调度,它可以是单线程的,也可以是多线程的。当一个协程遇到阻塞调用时(例如,等待网络响应),它可以被挂起,调度器则可以在同一线程上开始执行另一个协程,从而提高了线程的利用率。

这三个系统的共同之处在于它们都试图通过非阻塞和事件驱动的方式来提高系统的总体吞吐量和并发能力。然而,它们的具体实现方式和用途是不同的。尽管有这种相似性,但在深入研究这些技术时,了解它们的具体工作原理和最佳实践是非常重要的。

 

 

kotlin和go怎样在CPU密集型上取得优势?

也就是nodejs是传统的多线程模型。而kotlin和go是基于协程的多线程模型,协程切换占资源少,是主要原因,对吗?

nodejs不是协程,而是事件循环。async相当于提交任务给后台线程(或者等自己空闲)

 

python的async,await简单描述

async用来将函数变成协程。

await的作用:

1. 挂起协程

2. 提交后台任务给:

   * 后台线程(转交给别人)  - 委派

   * 如果没有后台线程,只有一个主线程,那就等到主线程空闲再执行这个协程-拖延

3. 在该后台任务完成时,恢复协程。

 

是的,您的描述是正确的。为了更加精确:

async: 使用 async 关键字定义的函数会返回一个协程对象。这些函数通常被称为"异步函数"。

await: 在异步函数内部,你可以使用 await 关键字来调用另一个异步函数。这会挂起当前的协程,等待被调用的异步函数完成。

提交任务给后台线程: 这取决于具体的实现细节。在纯粹的 asyncio 环境中,没有涉及到真实的线程切换。但如果你结合了其他如线程池或者其他能在后台线程执行的库,那么 await 可以等待后台线程完成任务。

恢复协程: 一旦 await 后面的异步操作完成(无论是I/O操作、后台线程完成工作或其他),控制权会返回给被挂起的协程,从而继续执行。

总之,async 和 await 关键字允许你以非阻塞的方式编写并发代码,不论背后的机制是事件循环还是真实的多线程。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李来群

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值