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日志并进行多维度分析。
相关文章
|
1月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
43 4
|
5天前
|
数据采集 消息中间件 并行计算
进程、线程与协程:并发执行的三种重要概念与应用
进程、线程与协程:并发执行的三种重要概念与应用
16 0
|
2月前
|
调度 开发者 UED
Kotlin 中的协程是什么?
【8月更文挑战第31天】
56 0
|
3月前
|
消息中间件 算法 Java
(十四)深入并发之线程、进程、纤程、协程、管程与死锁、活锁、锁饥饿详解
本文深入探讨了并发编程的关键概念和技术挑战。首先介绍了进程、线程、纤程、协程、管程等概念,强调了这些概念是如何随多核时代的到来而演变的,以满足高性能计算的需求。随后,文章详细解释了死锁、活锁与锁饥饿等问题,通过生动的例子帮助理解这些现象,并提供了预防和解决这些问题的方法。最后,通过一个具体的死锁示例代码展示了如何在实践中遇到并发问题,并提供了几种常用的工具和技术来诊断和解决这些问题。本文旨在为并发编程的实践者提供一个全面的理解框架,帮助他们在开发过程中更好地处理并发问题。
|
4月前
|
安全 Android开发 Kotlin
Android面试题之Kotlin协程并发问题和互斥锁
Kotlin的协程提供轻量级并发解决方案,如`kotlinx.coroutines`库。`Mutex`用于同步,确保单个协程访问共享资源。示例展示了`withLock()`、`lock()`、`unlock()`和`tryLock()`的用法,这些方法帮助在协程中实现线程安全,防止数据竞争。
52 1
|
4月前
|
监控 程序员 调度
协程实现单线程并发(入门)
协程实现单线程并发(入门)
50 1
|
4月前
|
存储 Java 调度
Android面试题之Kotlin 协程的挂起、执行和恢复过程
了解Kotlin协程的挂起、执行和恢复机制。挂起时,状态和上下文(局部变量、调用栈、调度器等)被保存;挂起点通过`Continuation`对象处理,释放线程控制权。当恢复条件满足,调度器重新分配线程,调用`resumeWith`恢复执行。关注公众号“AntDream”获取更多并发知识。
79 2
|
5月前
|
移动开发 Android开发 开发者
构建高效Android应用:Kotlin与协程的完美融合
【5月更文挑战第25天】 在移动开发的世界中,性能和响应性是衡量应用质量的关键指标。随着Kotlin的流行和协程的引入,Android开发者现在有了更强大的工具来提升应用的性能和用户体验。本文深入探讨了Kotlin语言如何与协程相结合,为Android应用开发带来异步处理能力的同时,保持代码的简洁性和可读性。我们将通过实际案例分析,展示如何在Android项目中实现协程,以及它们如何帮助开发者更有效地管理后台任务和用户界面的流畅交互。
|
5月前
|
移动开发 数据库 Android开发
构建高效Android应用:探究Kotlin的协程优势
【5月更文挑战第22天】随着移动开发技术的不断进步,Android平台的性能优化已经成为开发者关注的焦点。在众多提升应用性能的手段中,Kotlin语言提供的协程概念因其轻量级线程管理和异步编程能力而受到广泛关注。本文将深入探讨Kotlin协程在Android开发中的应用,以及它如何帮助开发者构建出更高效、响应更快的应用,同时保持代码的简洁性和可读性。
|
5月前
|
移动开发 Android开发 开发者
构建高效安卓应用:Kotlin 协程的实践指南
【5月更文挑战第18天】 随着移动开发技术的不断进步,安卓平台亟需一种高效的异步编程解决方案来应对日益复杂的应用需求。Kotlin 协程作为一种新兴的轻量级线程管理机制,以其简洁的语法和强大的功能,成为解决这一问题的关键。本文将深入探讨Kotlin协程在安卓开发中的实际应用,从基本概念到高级技巧,为开发者提供一份全面的实践指南,旨在帮助读者构建更加高效、稳定的安卓应用。