Kotlin+协程+FLow+Channel,实现生产消费者模式3种案例

生成者消费模式,在设计中也是一种频繁的应用,掌握它便能轻松应对各种场景

一、前言

生产消费者模式在Andoird 里面应用还相当广泛,它带来的好处大致可以分为:

  1. 解耦:能彻底解耦生产模块和消费模块,生产者和消费者之间不直接进行交互,而是通过一个缓冲区(如队列或缓冲区)来进行交互。这种方式减少了生产者和消费者之间的直接依赖关系,使得系统的各个部分更加独立,易于维护和扩展‌
  2. 同时,它也能平衡生产消费之间的速度。在多线程环境中,生产者和消费者的处理速度往往是不一致的。生产者-消费者模式通过缓冲区来平衡这种速度差异,生产者只需将数据放入缓冲区,而消费者从缓冲区取出数据处理。这样,生产者不需要等待消费者处理完数据再继续生产,反之亦然,从而提高了系统的整体效率‌
  3. 提高系统稳定性和效率‌:通过缓冲区的使用,系统可以在生产者速度过快或消费者速度过慢时进行调节,避免了直接的生产者-消费者间的等待,减少了系统负载,提高了程序的稳定性和效率‌
  4. 应用广泛 ‌:生产者-消费者模式在许多系统中都有应用:
    Android系统中的Handler,Looper, message,MessageQueue‌ 它们的运行模式就是一种生成消费者模式。
    同样:在Android直播应用中 :我们把每一帧的数据编码好,准备成传输数据包: RTMPPackage 加入到缓冲队列里面,这个过程就是数据生成阶段,另一边我们不停地从缓冲队列里面取出 传输数据包: RTMPPackage,推送给服务端,这个过程就是消费阶段。
  • 这里需要注意的,生产消费者模式并不是23种设计模式中的。

  • 本文主要介绍:Kotlin+协程+FLow+Channel,实现生产消费者模式几种案例

二、案例一,传统式写法:

  • 生产者向缓冲区产生数据,当缓冲区超过设置最大数时候,让其等待,等待消费者处理完了,再加入进去。
  • 当消费者发现缓冲区为空时候,等待,知道有可以消费的数据时候为止。
  • 关键点:使用while循环检查条件避免虚假唤醒,notifyAll确保唤醒正确线程
kotlin 复制代码
val buffer = LinkedList<Int>()
val MAX_SIZE = 5
val lock = Object()

// 生产者
fun produce(item: Int) {
    synchronized(lock) {
        while (buffer.size >= MAX_SIZE) {
            lock.wait()  // 缓冲区满时等待
        }
        buffer.add(item)
        lock.notifyAll() // 必须使用notifyAll避免死锁
    }
}

// 消费者
fun consume(): Int {
    synchronized(lock) {
        while (buffer.isEmpty()) {
            lock.wait()  // 缓冲区空时等待
        }
        val item = buffer.removeFirst()
        lock.notifyAll()
        return item
    }
}

// 测试代码
fun main() {
    repeat(3) { i ->
        Thread { produce(i) }.start() // 启动3个生产者
    }
    repeat(2) { 
        Thread { println(consume()) }.start() // 启动2个消费者
    }

从上面我们可以看出,最经典的,是完全自由的,可完全自定义的,涉及到缓存,最大值,锁相关知识,以及如果 异步 也需要自己来实现管理处理。这样写起来需要基本功深才行。

三、案例二、协程+Channel方式实现

下面示例:

我们创建了容量为5的缓冲通道,

生产者每100ms发送数据,

消费者每200ms处理数据,

自动实现流量控制。

当缓冲区满时send挂起,

空时receive挂起

同时,由于我们协程中,我们可以通过直接通过协程中的 Dispatchers来直接切换线程,让其异步中实现生产和消费。

scss 复制代码
fun main() = runBlocking {
    // 创建容量为5的缓冲通道
    val channel = Channel<Int>(capacity = 5)
    
    // 生产者协程
    val producer = launch {
        repeat(10) { i ->
            delay(100) // 模拟生产耗时
            channel.send(i)
            println("生产: $i (缓冲区剩余: ${channel.capacity - channel.size})")
        }
        channel.close() // 生产完成后关闭通道
    }

    // 消费者协程
    val consumer = launch {
        channel.consumeEach { item ->
            delay(200) // 模拟消费耗时
            println("消费: $item")
        }
    }

    // 等待生产和消费完成
    joinAll(producer, consumer)
}

四、案例三、协程结合Flow方式实现

如下面代码所示:

我们通过flow构建生产者流,buffer控制背压,onEach处理消费逻辑,flowOn实现线程切换 涉及到的核心点:

  1. 背压策略选择
    buffer():缓冲指定数量元素,默认溢出策略为挂起生产者
    conflate():仅保留最新元素,适合实时状态更新场景
    collectLatest():新元素到达时取消当前消费任务

  2. 线程调度控制
    flowOn(Dispatchers.IO):指定上下游执行线程池

    生产者与消费者默认共享协程上下文,需显式分离避免阻塞

  3. 异常处理增强

    可通过Flow的.catch统一处理生产阶段和消费阶段的异常。

scss 复制代码
fun producer(): Flow<Int> = flow {
    repeat(10) { i ->
        delay(100) // 模拟生产耗时
        emit(i)
        println("生产: $i")
    }
}.catch{
    e -> println("生产异常: $e")
}
.flowOn(Dispatchers.IO) // 指定生产者协程上下文

fun main() = runBlocking {
    producer()
        .buffer(5) // 设置缓冲区容量
        .onEach { item ->
            delay(200) // 模拟消费耗时
            println("消费: $item")
        }.catch{
            e -> println("生产异常: $e")
        }.flowOn(Dispatchers.IO) // 指定消费者协程上下文
        .collect()
}

从上面我们也可以看出:

Flow与Channel方案对比

特性 Flow方案 Channel方案
数据生成方式 冷流(按需生产) 热流(独立于消费)
背压处理 通过操作符(buffer/conflate)控制 依赖通道容量设置
异常处理 统一处理 需要手动分别处理
适用场景 纯数据流处理 需要双向通信的场景

五、总结

本文重点介绍了,传统生产消费者模式和 Kotlin+协程+FLow+Channel实现消费者模式

  1. 传统的完全自定义,涉及到相关内容较多,需要基础扎实
  2. 基于协程和Flow或者协程+Channel来实现,可以很简单的背压处理,切换线程处理。使用起来更加方便。

感谢阅读:

欢迎用你发财的小手 关注,点赞、收藏

这里你会学到不一样的东西

相关推荐
程序猿追7 分钟前
深度剖析 CANN ops-nn 算子库:架构设计、演进与代码实现逻辑
人工智能·架构
独行soc9 分钟前
2026年渗透测试面试题总结-17(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
程序猿追17 分钟前
深度解码昇腾 AI 算力引擎:CANN Runtime 核心架构与技术演进
人工智能·架构
金融RPA机器人丨实在智能18 分钟前
Android Studio开发App项目进入AI深水区:实在智能Agent引领无代码交互革命
android·人工智能·ai·android studio
科技块儿19 分钟前
利用IP查询在智慧城市交通信号系统中的应用探索
android·tcp/ip·智慧城市
晚霞的不甘25 分钟前
CANN 编译器深度解析:TBE 自定义算子开发实战
人工智能·架构·开源·音视频
独行soc1 小时前
2026年渗透测试面试题总结-18(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
程序猿追1 小时前
昇腾算力之锚:深度解读 CANN ascend-toolkit 异构计算架构与工程实践
架构
一枕眠秋雨>o<1 小时前
深入 CANN ops-nn:昇腾 NPU 算子开发的工程化实践与架构哲学
架构
未来龙皇小蓝1 小时前
RBAC前端架构-01:项目初始化
前端·架构