Coroutines Channels

Java中的多线程通信, 总会涉及到共享状态(shared mutable state)的读写, 有同步, 死锁等问题要处理.

协程中的Channel用于协程间的通信, 它的宗旨是:

Do not communicate by sharing memory; instead, share memory by communicating.

本文被收录在: https://github.com/mengdd/KotlinTutorials

Channel basics

channels用于协程间的通信, 允许我们在不同的协程间传递数据(a stream of values).

生产者-消费者模式

发送数据到channel的协程被称为producer, 从channel接受数据的协程被称为consumer.

生产: send, produce.

消费: receive, consume.

当需要的时候, 多个协程可以向同一个channel发送数据, 一个channel的数据也可以被多个协程接收.

当多个协程从同一个channel接收数据的时候, 每个元素仅被其中一个consumer消费一次. 处理元素会自动将其从channel里删除.

Channel的特点

Channel在概念上有点类似于BlockingQueue, 元素从一端被加入, 从另一端被消费. 关键的区别在于, 读写的方法不是blocking的, 而是suspending的.

在为空或为满时. channel可以suspend它的sendreceive操作.

Channel的关闭和迭代

Channel可以被关闭, 说明没有更多的元素了.

取消producer协程也会关闭channel.

在receiver端有一种方便的方式来接收: 用for迭代.

看这个例子:

fun main() = runBlocking<Unit> {
val channel = Channel<Int>()
launch {
for (x in 1..5) channel.send(x)
channel.close() // we're done sending
}
// here we print received values using `for` loop (until the channel is closed)
for (y in channel) println(y)
println("Done!")
}

运行后会输出:

1
2
3
4
5
Done! Process finished with exit code 0

如果注释掉channel.close()就会变成:

1
2
3
4
5

Done没有被输出, 程序也没有退出, 这是因为接受者协程还在一直等待.

不同的Channel类型

库中定义了多个channel类型, 它们的主要区别在于:

  • 内部可以存储的元素数量;
  • send是否可以被挂起.

所有channel类型的receive方法都是同样的行为: 如果channel不为空, 接收一个元素, 否则挂起.

Channel的不同类型:

  • Rendezvous channel: 0尺寸buffer, sendreceive要meet on time, 否则挂起. (默认类型).
  • Unlimited channel: 无限元素, send不被挂起.
  • Buffered channel: 指定大小, 满了之后send挂起.
  • Conflated channel: 新元素会覆盖旧元素, receiver只会得到最新元素, send永不挂起.

创建channel:

val rendezvousChannel = Channel<String>()
val bufferedChannel = Channel<String>(10)
val conflatedChannel = Channel<String>(CONFLATED)
val unlimitedChannel = Channel<String>(UNLIMITED)

默认是Rendezvous channel.

练习: 分析代码输出

看这段代码:

fun main() = runBlocking<Unit> {
val channel = Channel<String>()
launch {
channel.send("A1")
channel.send("A2")
log("A done")
}
launch {
channel.send("B1")
log("B done")
}
launch {
repeat(3) {
val x = channel.receive()
log(x)
}
}
} fun log(message: Any?) {
println("[${Thread.currentThread().name}] $message")
}

这段代码创建了一个channel, 传递String类型的元素.

两个producder协程, 分别向channel发送不同的字符串, 发送完毕后打印各自的"done".

一个receiver协程, 接收channel中的3个元素并打印.

程序的运行输出结果会是怎样呢?

记得在Configurations中加上VM options: -Dkotlinx.coroutines.debug. 可以看到协程信息.

答案揭晓:

[main @coroutine#4] A1
[main @coroutine#4] B1
[main @coroutine#2] A done
[main @coroutine#3] B done
[main @coroutine#4] A2

答对了吗?

为什么会是这样呢? 原因主要有两点:

  • 这里创建的channel是默认的Rendezvous类型, 没有buffer, send和receive必须要meet, 否则挂起.
  • 两个producer和receiver协程都运行在同一个线程上, ready to be resumed也只是加入了一个等待队列, resume要按顺序来.

这个例子在Introduction to Coroutines and Channels中有一个视频解说.

另外, 官方文档中还有一个ping-pang的例子, 为了说明Channels are fair.

参考

欢迎关注微信公众号: 圣骑士Wind

Kotlin协程通信机制: Channel的更多相关文章

  1. Kotlin 协程一 —— 全面了解 Kotlin 协程

    一.协程的一些前置知识 1.1 进程和线程 1.1.1基本定义 1.1.2为什么要有线程 1.1.3 进程与线程的区别 1.2 协作式与抢占式 1.2.1 协作式 1.2.2 抢占式 1.3 协程 二 ...

  2. rxjava回调地狱-kotlin协程来帮忙

    本文探讨的是在tomcat服务端接口编程中, 异步servlet场景下( 参考我另外一个文章),用rxjava来改造接口为全流程异步方式 好处不用说 tomcat的worker线程利用率大幅提高,接口 ...

  3. python并发编程之Queue线程、进程、协程通信(五)

    单线程.多线程之间.进程之间.协程之间很多时候需要协同完成工作,这个时候它们需要进行通讯.或者说为了解耦,普遍采用Queue,生产消费模式. 系列文章 python并发编程之threading线程(一 ...

  4. Android Kotlin协程入门

    Android官方推荐使用协程来处理异步问题.以下是协程的特点: 轻量:单个线程上可运行多个协程.协程支持挂起,不会使正在运行协程的线程阻塞.挂起比阻塞节省内存,且支持多个并行操作. 内存泄漏更少:使 ...

  5. Kotlin协程解析系列(上):协程调度与挂起

    vivo 互联网客户端团队- Ruan Wen 本文是Kotlin协程解析系列文章的开篇,主要介绍Kotlin协程的创建.协程调度与协程挂起相关的内容 一.协程引入 Kotlin 中引入 Corout ...

  6. Kotlin协程第一个示例剖析及Kotlin线程使用技巧

    Kotlin协程第一个示例剖析: 上一次https://www.cnblogs.com/webor2006/p/11712521.html已经对Kotlin中的协程有了理论化的了解了,这次则用代码来直 ...

  7. Retrofit使用Kotlin协程发送请求

    Retrofit2.6开始增加了对Kotlin协程的支持,可以通过suspend函数进行异步调用.本文简单介绍一下Retrofit中协程的使用 导入依赖 app的build文件中加入: impleme ...

  8. Openresty Lua协程调度机制

    写在前面 OpenResty(后面简称:OR)是一个基于Nginx和Lua的高性能Web平台,它内部集成大量的Lua API以及第三方模块,可以利用它快速搭建支持高并发.极具动态性和扩展性的Web应用 ...

  9. Kotlin协程基础

    开发环境 IntelliJ IDEA 2021.2.2 (Community Edition) Kotlin: 212-1.5.10-release-IJ5284.40 我们已经通过第一个例子学会了启 ...

随机推荐

  1. 【排列组合】给定一个M*N的格子或棋盘,从左下角走到右上角的走法总数(每次只能向右或向上移动一个方格边长的距离)

    版权声明:本文为CSDN博主「梵解君」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明. 原文链接:https://blog.csdn.net/hadeso/art ...

  2. Redis持久化--Redis宕机或者出现意外删库导致数据丢失--解决方案

    echo编辑整理,欢迎转载,转载请声明文章来源.欢迎添加echo微信(微信号:t2421499075)交流学习. 百战不败,依不自称常胜,百败不颓,依能奋力前行.--这才是真正的堪称强大!!! Red ...

  3. svg路径蒙版动画

    svg路径蒙版动画,是比较实用的一种动画效果,能够绘制如下图所示的动画. 接下来细说这样的动画是如何做成的: 1.准备工作 2.SVG路径动画 3.SVG路径蒙版动画 4.复杂图形的编辑技巧 1.准备 ...

  4. UiPath之文件操作

    今天给大家介绍一下,在UiPath中如何操作文件,比如需要在某个文件夹中自动创建一个当天日期的文本. 主要使用的activity有: l  Assign l  Path Exists l  If l  ...

  5. 012.Kubernetes二进制部署worker节点Flannel

    一 部署flannel 1.1 安装flannel kubernetes 要求集群内各节点(包括 master 节点)能通过 Pod 网段互联互通.flannel 使用 vxlan 技术为各节点创建一 ...

  6. Vue躬行记(8)——Vue Router

    虽然Vue.js未提供路由功能,但是官方推出了Vue Router(即vue-router库),以插件的形式支持.它与Vue.js深度集成,可快速的创建单页应用(Single Page Applica ...

  7. shell的运用 : jenkins 编译 打包前端发布 生产(tomcat)

    生产隔离做得非常.....文件上传只能通过固定ip机器的sftp账户上传,账户密码每个月要写申请才能获得. 登陆生产服务只能通过浏览器登陆!!! 发布一次生产,很痛苦. 做了简单的shell来减轻痛苦 ...

  8. php如何在mysql里批量插入数据

    假如说我有这样一个表,我想往这个表里面插入大量数据 CREATE TABLE IF NOT EXISTS `user_info` ( `id` int(11) NOT NULL AUTO_INCREM ...

  9. Github PageHelper 原理解析

    任何服务对数据库的日常操作,都离不开增删改查.如果一次查询的纪录很多,那我们必须采用分页的方式.对于一个Springboot项目,访问和查询MySQL数据库,持久化框架可以使用MyBatis,分页工具 ...

  10. hdu 1087 Super Jumping! Jumping! Jumping!(动态规划DP)

    Super Jumping! Jumping! Jumping!Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 ...