王学岗Kotlin协程(五)————Channel通道-多路复用-并发安全

使用channel进行通信

Channel 实际上是一个并发安全的队列,它可以用来连接协程,实现不同协程的通信。
在这里插入图片描述

 @Test
    fun `test know channel`() = runBlocking<Unit> {
    //创建channel
        val channel = Channel<Int>()
        //生产者
        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的容量

在这里插入图片描述

channel是一个队列,队列就有大小,其大小就是缓冲区的大小。当然 缓冲大小是可以指定的,默认是0

@Test
    fun `test know channel2`() = runBlocking<Unit> {
        val channel = Channel<Int>()
        //生产者,发完后会等着,消费完后再生产,在继续发
        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)满的时候会挂起,可以发现,消费完了才会生产。

迭代chanel

在这里插入图片描述
iterator就是迭代器,场景,迅速获取网络数据,缓缓地展示在UI上。

   @Test
    fun `test iterate channel`() = runBlocking<Unit> {
//缓冲区大小为Channel.UNLIMITED,大小为Int.MAX_VALUE

        val channel = Channel<Int>(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<Unit> {
//缓冲区大小为Channel.UNLIMITED

        val channel = Channel<Int>(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)

    }

produce 与actor

在这里插入图片描述
Produce代码演示如下

@Test
    fun `test fast producer channel`() = runBlocking<Unit> {
        val receiveChannel: ReceiveChannel<Int> = GlobalScope.produce<Int> {
            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<Unit> {
        val sendChannel: SendChannel<Int> = GlobalScope.actor<Int> {
            while (true) {
                val element = receive()
                println(element)
            }
        }

        val producer = GlobalScope.launch {
            for (i in 0..3) {
                sendChannel.send(i)
            }
        }

        producer.join()
    }

打印输出0123。

channel的关闭

在这里插入图片描述

 @Test
    fun `test close channel`() = runBlocking<Unit> {
        val channel = Channel<Int>(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

BroadCastChannel

在这里插入图片描述
注:同一个元素只会被一个接收端读到:比如接收端有ABC,A接收到了,BC就接收不到了

@Test
    fun `test broadcast`() = runBlocking<Unit> {
    //capacity是Channel.BUFFERED,0和不限制大小都会崩溃。
        val broadcastChannel = BroadcastChannel<Int>(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<Unit> {
        val channel = Channel<Int>()
        //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

多路复用

在这里插入图片描述
只有一个管道,不同的水都经过这个管道。音视频中传输就是这样。

await 多路复用

协程里的多路复用:我们是哪个快就选择哪个,不需要还原成多路。
在这里插入图片描述
我们代码看下协程中的多路复用

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<T>(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<Unit> {
        GlobalScope.launch {
            val localRequest = getUserFromLocal("xxx")
            val remoteRequest = getUserFromRemote("yyy")
           //谁更快返回select就用谁。Response<User>返回值的类型
            val userResponse = select<Response<User>> {
            //这里的it就是协程返回的user对象
            //注意这里是onAwait,
                localRequest.onAwait { Response(it, true) }
                remoteRequest.onAwait { Response(it, false) }
            }

            userResponse.value?.let { println(it) }
        }.join()
    }

打印输出的是服务器返回的数据。如果把本地的故意延迟删除掉,打印输出的就是本地的数据(本地肯定比远程快)。

复用多个channel

在这里插入图片描述

 @Test
    fun `test select channel`() = runBlocking<Unit> {
    //创建两个channel
        val channels = listOf(Channel<Int>(), Channel<Int>())
        GlobalScope.launch {
            delay(100)
            //第0个通道隔100毫秒发消息
            channels[0].send(200)
        }

        GlobalScope.launch {
            delay(50)
              //第1个通道隔50毫秒发消息
            channels[1].send(100)
        }
//可能两个都没收到消息,所以泛型我们使用Int?
        val result = select<Int?> {
            channels.forEach { channel ->
                channel.onReceive { it }
            }
        }
        println(result)
    }

看下打印输出

100

SelectClause

在这里插入图片描述

 @Test
    fun `test SelectClause0`() = runBlocking<Unit> {
        val job1 = GlobalScope.launch {
            delay(100)
            println("job 1")
        }

        val job2 = GlobalScope.launch {
            delay(10)
            println("job 2")
        }

        select<Unit> {//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<Unit> {
        val channels = listOf(Channel<Int>(), Channel<Int>())
        println(channels)

        launch(Dispatchers.IO) {
            select<Unit?> { //没有返回值
            
                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}

flow实现多路复用

在这里插入图片描述
用到了kotlin反射,需要添加反射的依赖

 implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
 @Test
    fun `test select flow`() = runBlocking<Unit> {
    // 函数返回 协程, 转成 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<Unit> {
        var count = 0
        List(1000) {//启动1000个协程
            GlobalScope.launch { count++ }
        }.joinAll()
        println(count)
    }

count++不是原子性操作,所以count最后的值不是1000,而且多次运行每次打印的数也不相同

协程并发安全

可以使用java自己的API解决

  @Test
    fun `test safe concurrent`() = runBlocking<Unit> {
        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<Unit> {
        var count = 0
        val mutex = Mutex()
        List(1000) {
            GlobalScope.launch {
            //加锁,是一个挂起函数
                mutex.withLock {
                    count++
                }
            }
        }.joinAll()
        println(count)
    }

每次输出都是1000

  @Test
    fun `test safe concurrent tools2`() = runBlocking<Unit> {
        var count = 0
        val semaphore = Semaphore(1)//信号
        List(1000) {
            GlobalScope.launch {
                semaphore.withPermit {
                    count++
                }
            }
        }.joinAll()
        println(count)
    }

每次输出都是1000
我们也可以不使用协程并发工具而是如下
在这里插入图片描述

  @Test
    fun `test avoid access outer variable`() = runBlocking<Unit> {
        var count = 0
        val result = count + List(1000){
            GlobalScope.async { 1 }
        }.map { it.await() }.sum()
        println(result)
    }

count在协程外部,就不存在并发安全的操作了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值