kotlin协程也有并发?

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: kotlin协程也有并发?

概览


我们在 java 中处理并发是家常便饭,但是协程的并发你有没有想过呢,协程是否也有java一样的并发问题?

我们知道协程是轻量级的进程,而且是可以多线程调度的。那么想想这样一个情景:

我们开启1000个协程,每个协程中对count进行自增,协程执行完成后能否拿到count==1000的结果,答案在后面的章节中,最终结论就是kkotlin也是需要处理并发的

那么这种并发该如何处理呢,我想先给你说的是,协程有自己的一套并发规则,你应该试图优先用 kotlin 的并发方法来处理协程的并发

本文主要讲几种协程处理并发的例子


模拟协程并发


模拟案例

既然是讲协程的并发,那么我们首先应该证明一下协程中存在并发问题,对吧,那就来吧。

先看一下模拟的代码:

  1. 代码
fun concurrent(){
    val scope = CoroutineScope(Dispatchers.Default)//创建协程作用域,Default支持并发
    var count=0
    repeat(1000){//重复1000次,每次开启一个协程,count自增1
        scope.launch {
            count++
            println(count)
        }
    }
}
fun main() = runBlocking {
    concurrent()
}
复制代码

本例中,我们模拟了这样一个场景,使用自定义的作用域 scope 短时间内开启 1000 个协程,协程中每次把 count 自增 1 打印数据。如果最终打印的数据是 1000 那么说明不存在并发,如果打印的数据小于 1000,那么说明协程中也存在如 java 一样的并发问题。


  1. 日志

这里只发出最后一部分的日志:

696
695
694
693
692
691
690
703
702
复制代码
  1. 结论

日志打印最终验证协程中也存在并发性的问题,那么后面的章节我们就拿出我们的协程解决方案吧。


模拟案例扩展

我们将本例中的代码做一个扩展,在concurrent方法尾部加一个1s的延时

  1. 代码

image.png


  1. 输出日志
999
1000
978
995
994
993
992
991
1000
复制代码
  1. 结论

当最后一个协程执行完成时可以计算出1000这个正确结果但是打印的中间值顺序却是错乱的(这一点和java不一样),但是我们在业务代码中却没有办法获取这个正确结果产生的时间,所以我们依然要处理并发问题


解决并发的方法


解决并发的问题既可以使用 java 中的部分方式,也可以使用 kotlin 自有的方式。协程自有的处理方法适用性更广一些。


java 方式解决

如果我们想使用java的方式解决并发,那么我们可以尝试让协程运行在单线程中,也可以尝试使用Atomic方式


单线程解决方法

我们将 模拟并发章节的代码进行一些改动试试:

  1. 代码
fun concurrent() {
    val scope = CoroutineScope(Dispatchers.Unconfined)//创建协程作用域,使用Unconfined,这样在协程被挂起前都不会改变线程,也就是说协程始终运行在单线程中
    var count = 0
    repeat(1000) {//重复1000次,每次开启一个协程,count自增1
        scope.launch {
            println("线程id:${Thread.currentThread().id}")//这个线程始终不会变,除非你在这里挂起
            count++
            println(count)
        }
    }
}
复制代码
  1. 日志
994
线程id:1
995
线程id:1
996
线程id:1
997
线程id:1
998
线程id:1
999
线程id:1
1000
复制代码
  1. 结论

本例代码中因为launch始终运行在单线程中所以最终输出count==1000,中间值的顺序也没有错乱


使用Atomic方式解决

上一小节中我们的代码始终运行在单线程中,所以最终输出正确结果,那么如果我们的代码不允许在单线程中有没有解决方法呢,答案当然是有, Atomic机制即可解决。

试想一下如果我们把上面的代码scope.launch 改为launch(Dispatchers.Default) ,那么线程就变成了运行在多线程中了,此时输出的最终结果一定不会是我们想要的(我已经帮你试过了,就不贴代码和日志了)

那么我们可以尝试用Atomic方式解决这个问题

  1. 代码
suspend fun concurrent() {
    var count =AtomicInteger(0)
    coroutineScope {
        repeat(10000) {//重复1000次,每次开启一个协程,count自增1
            launch(Dispatchers.Default) {
                count.incrementAndGet()
                println("计算中间值$count")
            }
        }
    }
    delay(1000)
    println("计算结果:$count")
}
复制代码
  1. 日志
计算结果:10000
复制代码
  1. 结论

虽然最终输出结果是正确的

但是对于负责的状态Atomic明显是处理不了的,负责的状态可以考虑后面要讲的Mutex方式


kotlin 方式解决

互斥

kotlin 为我们提供了Mutex实现线程安全,Mutex通俗点来说就是kotlin的锁,和java 的synchronized和RecentLock对应。

使用mutex.withLock {*} 即可实现数据的同步

看代码:

  1. 代码
val mutex = Mutex()
suspend fun concurrent() {
    var count =0
    coroutineScope {
        repeat(10000) {//重复1000次,每次开启一个协程,count自增1
            launch(Dispatchers.Default) {
                mutex.withLock {
                    count++
                }
                println("中间值:$count")
            }
        }
    }
    delay(1000)
    println("计算结果:$count")
}
复制代码
  1. 日志
****
中间值:9993
中间值:9994
中间值:9995
中间值:9996
中间值:9997
中间值:9998
中间值:9999
中间值:10000
计算结果:10000
复制代码
  1. 结论

使用Mutex方式解决并发,最终输出正确结果,并且中间值也是按顺序输出


结尾


掌握上面的几种方式基本上能处理大部分业务逻辑了

如果你有其他方案欢迎留言,我会及时补充



相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
3月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
89 4
|
2月前
|
安全 Go 调度
探索Go语言的并发模式:协程与通道的协同作用
Go语言以其并发能力闻名于世,而协程(goroutine)和通道(channel)是实现并发的两大利器。本文将深入了解Go语言中协程的轻量级特性,探讨如何利用通道进行协程间的安全通信,并通过实际案例演示如何将这两者结合起来,构建高效且可靠的并发系统。
|
2月前
|
JSON 调度 数据库
Android面试之5个Kotlin深度面试题:协程、密封类和高阶函数
本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点。文章详细解析了Kotlin中的协程、扩展函数、高阶函数、密封类及`inline`和`reified`关键字在Android开发中的应用,帮助读者更好地理解和使用这些特性。
26 1
|
2月前
|
Java 调度 Android开发
Android面试题之Kotlin中async 和 await实现并发的原理和面试总结
本文首发于公众号“AntDream”,详细解析了Kotlin协程中`async`与`await`的原理及其非阻塞特性,并提供了相关面试题及答案。协程作为轻量级线程,由Kotlin运行时库管理,`async`用于启动协程并返回`Deferred`对象,`await`则用于等待该对象完成并获取结果。文章还探讨了协程与传统线程的区别,并展示了如何取消协程任务及正确释放资源。
29 0
|
3月前
|
数据采集 消息中间件 并行计算
进程、线程与协程:并发执行的三种重要概念与应用
进程、线程与协程:并发执行的三种重要概念与应用
61 0
|
4月前
|
调度 开发者 UED
Kotlin 中的协程是什么?
【8月更文挑战第31天】
254 0
|
4月前
|
监控 Devops 测试技术
|
5月前
|
消息中间件 算法 Java
(十四)深入并发之线程、进程、纤程、协程、管程与死锁、活锁、锁饥饿详解
本文深入探讨了并发编程的关键概念和技术挑战。首先介绍了进程、线程、纤程、协程、管程等概念,强调了这些概念是如何随多核时代的到来而演变的,以满足高性能计算的需求。随后,文章详细解释了死锁、活锁与锁饥饿等问题,通过生动的例子帮助理解这些现象,并提供了预防和解决这些问题的方法。最后,通过一个具体的死锁示例代码展示了如何在实践中遇到并发问题,并提供了几种常用的工具和技术来诊断和解决这些问题。本文旨在为并发编程的实践者提供一个全面的理解框架,帮助他们在开发过程中更好地处理并发问题。
|
6月前
|
安全 Android开发 Kotlin
Android面试题之Kotlin协程并发问题和互斥锁
Kotlin的协程提供轻量级并发解决方案,如`kotlinx.coroutines`库。`Mutex`用于同步,确保单个协程访问共享资源。示例展示了`withLock()`、`lock()`、`unlock()`和`tryLock()`的用法,这些方法帮助在协程中实现线程安全,防止数据竞争。
77 1
|
6月前
|
监控 程序员 调度
协程实现单线程并发(入门)
协程实现单线程并发(入门)
64 1