Channel 实际上是一个并发安全的队列,它可以用来连接协程,实现不同协程的通信。
@Test
fun `test know channel`() = runBlocking {
//创建channel
val channel = Channel()
//生产者
val producer = GlobalScope.launch {
var i = 0
while (true) {
delay(1000)
channel.send(++i)
println("send $i")
}
}
//消费者
val consumer = GlobalScope.launch {
while (true) {
val element = channel.receive()
println("receive $element")
}
}
joinAll(producer, consumer)//主协程等待启动的两个协程执行完毕
}
打印输出如下
send 1
receive 1
send 2
receive 2
send 3
receive 3
send 4
receive 4
……
因为生产的效率远远小于消费的效率,所以这里生产出一个就消费一个。这就是简单的两个协程的通信。
channel是一个队列,队列就有大小,其大小就是缓冲区的大小。当然 缓冲大小是可以指定的,默认是0
@Test
fun `test know channel2`() = runBlocking {
val channel = Channel()
//生产者,发完后会等着,消费完后再生产,在继续发
val producer = GlobalScope.launch {
var i = 0
while (true) {
delay(1000)
channel.send(++i)
println("send $i")
}
}
//消费者
val consumer = GlobalScope.launch {
while (true) {
delay(2000)
val element = channel.receive()
println("receive $element")
}
}
joinAll(producer, consumer)
}
生产效率大于消费效率,缓冲区(默认大小是0)满的时候会挂起,可以发现,消费完了才会生产。
iterator就是迭代器,场景,迅速获取网络数据,缓缓地展示在UI上。
@Test
fun `test iterate channel`() = runBlocking {
//缓冲区大小为Channel.UNLIMITED,大小为Int.MAX_VALUE
val channel = Channel(Channel.UNLIMITED)
//生产者,快速发,发到后会放到缓存队列里
val producer = GlobalScope.launch {
for (x in 1..5) {
channel.send(x * x)//发数字的平方
println("send ${x * x}")
}
}
//消费者,慢慢消费
val consumer = GlobalScope.launch {
val iterator = channel.iterator()
while (iterator.hasNext()){
val element = iterator.next()
println("receive $element")
delay(2000)//每隔两秒取出一个元素
}
}
joinAll(producer, consumer)
}
打印输出如下,会发现send迅速打印完毕,然后每隔两秒打印一次receive
send 1
send 4
send 9
send 16
send 25
receive 1
receive 4
receive 9
for in的写法
@Test
fun `test iterate channel`() = runBlocking {
//缓冲区大小为Channel.UNLIMITED
val channel = Channel(Channel.UNLIMITED)
//生产者,快速发
val producer = GlobalScope.launch {
for (x in 1..5) {
channel.send(x * x)
println("send ${x * x}")
}
}
//消费者,慢慢消费
val consumer = GlobalScope.launch {
for (element in channel) {
println("receive $element")
delay(2000)
}
}
joinAll(producer, consumer)
}
@Test
fun `test fast producer channel`() = runBlocking {
val receiveChannel: ReceiveChannel = GlobalScope.produce {
repeat(100) {
delay(1000)
send(it)
}
}
val consumer = GlobalScope.launch {
for (i in receiveChannel) {
println("received: $i")
}
}
consumer.join()
}
运行结果是每隔一秒钟打印一次
actor代码演示如下
@Test
fun `test fast consumer channel`() = runBlocking {
val sendChannel: SendChannel = GlobalScope.actor {
while (true) {
val element = receive()
println(element)
}
}
val producer = GlobalScope.launch {
for (i in 0..3) {
sendChannel.send(i)
}
}
producer.join()
}
打印输出0123。
@Test
fun `test close channel`() = runBlocking {
val channel = Channel(3)//通道缓冲区大小是3
//生产者
val producer = GlobalScope.launch {
List(3) {
channel.send(it)
println("send $it")
}
//发完就关闭掉
channel.close()
//trimMargin()去掉空格和换行
println("""close channel.
| - ClosedForSend: ${channel.isClosedForSend}
| - ClosedForReceive: ${channel.isClosedForReceive}""".trimMargin())
}
//消费者
val consumer = GlobalScope.launch {
for (element in channel){
println("receive $element")
delay(1000)//每隔一秒消费一次
}
println("""After Consuming.
| - ClosedForSend: ${channel.isClosedForSend}
| - ClosedForReceive: ${channel.isClosedForReceive}""".trimMargin())
}
joinAll(producer, consumer)
}
每隔三秒钟取一次,但是发送瞬间完成
四个bool值打印输出如下
true false
true true
注:同一个元素只会被一个接收端读到:比如接收端有ABC,A接收到了,BC就接收不到了
@Test
fun `test broadcast`() = runBlocking {
//capacity是Channel.BUFFERED,0和不限制大小都会崩溃。
val broadcastChannel = BroadcastChannel(Channel.BUFFERED)
val producer = GlobalScope.launch {
List(3){
delay(100)
broadcastChannel.send(it)
}
broadcastChannel.close()
}
List(3){ index ->//启动三个协程
GlobalScope.launch {
val receiveChannel = broadcastChannel.openSubscription()//接收者要订阅的
for (i in receiveChannel){
println("[#$index] received: $i")
}
}
}.joinAll()
}
打印输出如下
[#0] received:0
[#1] received:0
[#2] received:0
[#0] received:1
[#1] received:1
[#2] received:1
[#0] received:2
[#1] received:2
[#2] received:2
channel 与broadcastchannel的转换
@Test
fun `test broadcast`() = runBlocking {
val channel = Channel()
//3是可以缓存的大小
val broadcastChannel = channel.broadcast(3)
val producer = GlobalScope.launch {
List(3){
delay(100)
broadcastChannel.send(it)
}
broadcastChannel.close()
}
List(3){ index ->//启动三个协程
GlobalScope.launch {
val receiveChannel = broadcastChannel.openSubscription()
for (i in receiveChannel){
println("[#$index] received: $i")
}
}
}.joinAll()
}
打印输出如下
[#0] received:0
[#1] received:0
[#2] received:0
[#0] received:1
[#1] received:1
[#2] received:1
[#0] received:2
[#1] received:2
[#2] received:2
只有一个管道,不同的水都经过这个管道。音视频中传输就是这样。
协程里的多路复用:我们是哪个快就选择哪个,不需要还原成多路。
我们代码看下协程中的多路复用
package com.dongnaoedu.kotlincoroutinechannel
import com.dongnaoedu.kotlincoroutinechannel.api.User
import com.dongnaoedu.kotlincoroutinechannel.api.userServiceApi
import com.google.gson.Gson
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.selects.select
import org.junit.Test
import java.io.File
private val cachePath = "E://coroutine.cache"
private val gson = Gson()//Gson解析
data class Response(val value: T, val isLocal: Boolean)
//因为要获取协程的返回结果,所以使用async
//但async要在协程里执行。当然可以使用GlobalScope,但我们不推荐使用
//推荐使用CoroutineScope的扩展函数,扩展函数里面有隐士调用,this对象,this对象指向当前调用的CoroutineScope这一协程作用域
//从本地读取
fun CoroutineScope.getUserFromLocal(name: String) = async(Dispatchers.IO) {
//delay(1000) //故意的延迟
//读取json文本,转成User对象
File(cachePath).readText().let { gson.fromJson(it, User::class.java) }
}
//从网络获取数据
fun CoroutineScope.getUserFromRemote(name: String) = async(Dispatchers.IO) {
userServiceApi.getUser(name)
}
/**
*
* @author ningchuanqi
* @version V1.0
*/
class CoroutineTest02 {
@Test
fun `test select await`() = runBlocking {
GlobalScope.launch {
val localRequest = getUserFromLocal("xxx")
val remoteRequest = getUserFromRemote("yyy")
//谁更快返回select就用谁。Response返回值的类型
val userResponse = select> {
//这里的it就是协程返回的user对象
//注意这里是onAwait,
localRequest.onAwait { Response(it, true) }
remoteRequest.onAwait { Response(it, false) }
}
userResponse.value?.let { println(it) }
}.join()
}
打印输出的是服务器返回的数据。如果把本地的故意延迟删除掉,打印输出的就是本地的数据(本地肯定比远程快)。
@Test
fun `test select channel`() = runBlocking {
//创建两个channel
val channels = listOf(Channel(), Channel())
GlobalScope.launch {
delay(100)
//第0个通道隔100毫秒发消息
channels[0].send(200)
}
GlobalScope.launch {
delay(50)
//第1个通道隔50毫秒发消息
channels[1].send(100)
}
//可能两个都没收到消息,所以泛型我们使用Int?
val result = select {
channels.forEach { channel ->
channel.onReceive { it }
}
}
println(result)
}
看下打印输出
100
@Test
fun `test SelectClause0`() = runBlocking {
val job1 = GlobalScope.launch {
delay(100)
println("job 1")
}
val job2 = GlobalScope.launch {
delay(10)
println("job 2")
}
select {//job1和job2没有返回值,所以泛型类型是Unit
job1.onJoin { println("job 1 onJoin") }
job2.onJoin { println("job 2 onJoin") }
}
delay(1000)
}
打印输出显示,job2先执行完。打印输出如下
job 2
job 2 onJoin
job 1
@Test
fun `test SelectClause2`() = runBlocking {
val channels = listOf(Channel(), Channel())
println(channels)
launch(Dispatchers.IO) {
select { //没有返回值
launch {
delay(10)
//onSend()要传入两个参数,一个是要发送的数据,一个是发送成功的回掉
channels[1].onSend(200) { sentChannel ->
println("sent on $sentChannel")
}
}
launch {
delay(100)
channels[0].onSend(100) { sentChannel ->
println("sent on $sentChannel")
}
}
}
}
//再开两个协程
GlobalScope.launch {
println(channels[0].receive())
}
GlobalScope.launch {
println(channels[1].receive())
}
delay(1000)
}
打印输出如下
[RendezvousChannel@43a25848{EmptyQueue},RendezvousChannel@1e643faf{EmptyQueue}]
200
sent on RendezvousChannel@1e643faf{EmptyQueue}
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
@Test
fun `test select flow`() = runBlocking {
// 函数返回 协程, 转成 Flow 然后 Flow合并
val name = "guest"
coroutineScope {
//::是函数的引用
listOf(::getUserFromLocal, ::getUserFromRemote)
.map { function ->
//函数引用,是调用cotlin反射,name是需要传入的参数
function.call(name)
}.map { deferred ->
flow { emit(deferred.await()) }
//merge合并两个协程
}.merge().collect { user -> println(user) }
}
}
}
打印输出的显示,无论是网络还是本地,都打印输出了。
@Test
fun `test not safe concurrent`() = runBlocking {
var count = 0
List(1000) {//启动1000个协程
GlobalScope.launch { count++ }
}.joinAll()
println(count)
}
count++不是原子性操作,所以count最后的值不是1000,而且多次运行每次打印的数也不相同
可以使用java自己的API解决
@Test
fun `test safe concurrent`() = runBlocking {
var count = AtomicInteger(0)//变成原子性操作
List(1000) {
//这里不再是count++
GlobalScope.launch { count.incrementAndGet() }
}.joinAll()
println(count.get())
}
每次输出都是1000
也可以使用协程提供的工具
注:信号量 联想linux杀死进程 kill -9 pid,9就是信号量
@Test
fun `test safe concurrent tools`() = runBlocking {
var count = 0
val mutex = Mutex()
List(1000) {
GlobalScope.launch {
//加锁,是一个挂起函数
mutex.withLock {
count++
}
}
}.joinAll()
println(count)
}
每次输出都是1000
@Test
fun `test safe concurrent tools2`() = runBlocking {
var count = 0
val semaphore = Semaphore(1)//信号
List(1000) {
GlobalScope.launch {
semaphore.withPermit {
count++
}
}
}.joinAll()
println(count)
}
@Test
fun `test avoid access outer variable`() = runBlocking {
var count = 0
val result = count + List(1000){
GlobalScope.async { 1 }
}.map { it.await() }.sum()
println(result)
}
count在协程外部,就不存在并发安全的操作了。