侧边栏壁纸
博主头像
术业有道之编程博主等级

亦是三月纷飞雨,亦是人间惊鸿客。亦是秋霜去叶多,亦是风华正当时。

  • 累计撰写 99 篇文章
  • 累计创建 50 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

kotlin协程的使用

Administrator
2022-12-26 / 0 评论 / 0 点赞 / 205 阅读 / 3469 字

写在前面

有一个使用kotlin多线程的爬虫项目,想让爬虫效率更高,于是采用了线程池的方式进行并发,在一台2核8G的服务器上运行5分钟就会耗尽cpu资源,在不限制并发数的情况下调整线程池不能解决这个问题,于是想到了kotlin协程

一、如何确定项目中是否需要多线程或协程

可以从以下方向评估:

  • 不实时性:协程不像线程一样运行过程是实时的,协程会受自身调度机制影响运行和挂起,这个行为不完全受控(这个行为在 kotlin-协程上下文与调度器 中有详细介绍)。所以不适合用协程做实时性比较高的事情。
  • 异步消息:协程内部的数据可以通过基础通道来通讯,但是这本身不是一个字面意义上的通道,有点像复杂度稍高的消息订阅发布模式或观察者模式(双向异步读写),它是一个先进先出的栈,有别于java atomicreference,同时对通道的使用也有一些异步限制,类似于nodejs Promise的概念
  • 代码改造:如果代码原来是多线程,那么改成协程几乎是零成本,只需要写一个CoroutinesSupport来创建协程,替换掉原来创建线程的代码即可

二、协程的运作

协程的运作大致可以分为以下几个步骤:

  • 初始化协程调度器,根据当前硬件cpu情况计算调度器核心工作线程数
  • 创建协程运行范围(全局、当前方法范围可挂起、可阻塞等)
  • 创建协程(在一个运行范围内可以创建多个协程)并注册到调度器上
  • 调度器分配工作线程,创建协程运行资源,绑定到分配的线程上
  • 调度器调度协程挂起,等待运行(这个过程是重复的)

三、建议使用协程的方式

通过 kotlin-协程基础 可以很清楚的了解协程的使用方式。但是经过实际的项目使用,建议使用以下方式:

fun exec(runnable: Runnable) {
       # 创建一个全局协程
        GlobalScope.launch {
            # 设置协程作用域-可挂起
            coroutineScope {
               # 创建协程
                launch {
                    try {
                        # 协程内部实际运行的代码
                        runnable.run()
                    } catch (e: Exception) {
                        logger.error("协程运行异常", e)
                    }
                }
            }
        }
    }

四、使用协程的注意事项

  • 使用runBlocking时千万注意,它会阻塞当前调用它的主线程,直到内部的协程全部执行完毕。它相当于thread.join()

    • 假设你在springboot controller的方法中调用了一个带有runBlocking的方法,会导致整个springboot程序卡住,等待协程全部执行完毕才会释放
  • 使用通道时,注意方法头部的suspend修饰,这与nodejs Promise高度相似,会导致调用堆栈的所有上层方法都需要使用suspend修饰。

  • 协程的原子性是无法使用@Volatile或者多线程锁来控制的,需要使用newSingleThreadContext来显式表达上下文,具体参见 kotlin协程-共享的可变状态与并发

五、总结

  • 协程是可以提升对结果要求实时性不高的代码执行效率

  • 需要认识到协程的工作机制,才能正确使用协程,不要用多线程的知识去理解协程,容易陷入误区

  • kotlin 协程golang 协程有非常多的相似之处,两者可以相互印证便于理解,但api不是通用的,golang 协程表现的更加原始和狂野一些。

  • 在没有多线程上下文数据同步的情况下,多线程切换到协程上代价是非常低的

    个人公众号

0

评论区