java之Kotlin 协程实践之 异步和 Callback 地狱

lingyuli
发布于 2020-9-8 10:38
浏览
0收藏

Kotlin 协程实践系列文章自Roman Elizarov在KotlinConf 2018关于Kotlin协程演讲和笔者构建网络爬虫服务实践过程中的一些总结而来。

kotlin协程实现了同步非阻塞编程模式,目的之一就是让程序逻辑不阻塞当前线程,从而提高吞吐量和线程利用率。在开始之前我们还需要明白几个重要的概念。

 

同步、异步 & 阻塞、非阻塞

这几个概念,在刚开始参加工作的头几年里,一直没搞明白,感觉非常烧脑。网上也有很多文章通过举例子的方式来解释,但终究有些牵强。工作多年后,在对于操作系统、JVM、编程范式有了进一步理解后,我想用程序员的思维视角来解释一下。

 

至于这和协程有什么关系,我们留着后面讨论。

 

同步、异步:

这个需要站在编程语言的层面来看它们的差别。从API形式上来看,同步和异步的区别在于:

同步:函数直接返回结果
异步:调用函数时同时传递一个Callback,或者函数返回的一个Feature

阻塞、非阻塞:

 

一般情况都是由于IO操作导致阻塞和非阻塞。你可以站在程序或者线程的角度看待这个问题,但是站在程序的立场,很难解释非阻塞这个词汇,所以我们站在线程这个角度来看。

阻塞:我是一个线程,当程序进行某一个调用时,我被阻塞(绑架)了,我不能去干其他事情,只能等待
非阻塞 :我是一个线程,当程序进行某一个IO调用时,我知道他需要一些时间,所以我先去执行其他程序

显然非阻塞要能实现,程序必须是复杂的,必须需要运行时环境提供支持。至于运行时环境如何高效实现非阻塞,则必须和操作系统相互配合。(Java的NIO、linux的select、poll、epoll了解一下)

 

此刻记在你脑子里的是,是否阻塞是底层系统在IO调用时给你的能力,同步和异步只是API的形式。

 

从编程难以程度上来看,同步比异步要简单很多,也和人类思维过程比较接近。从系统性能角度来看,非阻塞效率相当高,在互联网高并发场景下,这尤为重要。

 

一般来讲阻塞和同步是配对出现。

val json = getDataFromUrl(url)
// 在漫长的等待后,执行第二条语句
println(json)

非阻塞和异步是配对出现。

//初始化异步http客户端
val httpAsyncClient = ...

// 该调用立刻返回
httpAsyncClient.execute(HttpGet("https://github.com/mayabot/mynlp"), object : FutureCallback<HttpResponse> {
        override fun cancelled() {
            TODO("Not yet implemented")
        }

        override fun completed(result: HttpResponse?) {
            TODO("Not yet implemented")
        }

        override fun failed(ex: Exception?) {
            TODO("Not yet implemented")
        }
    } )

// 打印语句无需等待,立刻执行
println("send call")

异步阻这个组合属于编程难度大、效率低下,吃力不讨好,我们放弃讨论

同步非阻塞
这个组合非常棒,编程难度低,执行效率高,非常难得。

Kotin协程就实现了这种组合,也就解决了异步编程痛点。

 

异步编程&Callback地狱

让我们看一个玩具级演示例子:

 

这个一个网络应用,要向服务器创建一个Post,必须先获得一个token令牌。

fun requestToken() {
  // 发起阻塞的网络请求,等待返回Token对象
  // 执行Thread被阻塞
  return token // return result when received
}

fun createPost(token: Token,item: Item): Post {
  // send item to the server and wait
  // 执行Thread被阻塞
  return post // return result when post
}

fun processPost(post: Post){
  // 对Post对象进行一些处理,比如更新UI
}

// 整合起来
fun postItem(item: Item){
  val token = requestToken()
  val post = createPost(token,item)
  processPost(post)
}

程序很简单对吧,这就是同步编程的优点,程序简单易读,但遗憾的是,Thread被阻塞了。

 

JVM的线程数量是有限的,创建100线程不是什么难事,但是创建10 000个呢?一个线程大概会占2M内存,1万个线程,就会占用20G,所以在有限的线程下,同步阻塞编程模型的吞度量会很快达到极限。

 

那么callback就出场来拯救这一切。nodejs为什么能处理高并发,就是得益于高性能的event loop和callback机制。

 

让我们看看Callback的代码形式

fun requestTokenAsync(callback: (Token)->Unit){
  // 发起异步IO请求,当有返回结果时callback会被调用(用异步httpclient很容易实现)
  // 函数立刻返回
}

fun createPostAsync(token: Token,item: Item,callback: (Post)->Unit) {
  // 发送item和token到服务器,当返回Post时,callback被调用
  // 函数立刻返回
}

fun processPost(post: Post){
  // 对Post对象进行一些处理,比如更新UI
}

// 整合起来
fun postItem(item: Item){
  requestTokenAsync{ token->
  	 createPostAsync(token,item){ post->
        processPost(post)
     }
  }
}

postItem里面的多层callback函数嵌套,就是callback地狱了。

 

也行你会想这种嵌套没那么可怕?no,no,no!

实际代码中轻松嵌套几十层,看的头皮发麻。然后上面只是描述了一个顺序结构,a->b->c。但是如果存在条件分支、异常处理、循环、错误重试等等逻辑呢。虽然后来RxJava等框架尝试改善异步编程的困境,大概思路把嵌套结构改为了chain结构,但是依然复杂且程序接近非人类可读。

 

nodejs后来通过promise对象与async/await关键字来解决callback地狱问题

让我们看看kotlin的协程版本

suspend fun requestToken() {
  // 发起阻塞的网络请求,等待返回Token对象
  // 执行Thread被阻塞
  return token // return result when received
}

suspend fun createPost(token: Token,item: Item): Post {
  // send item to the server and wait
  // 执行Thread被阻塞
  return post // return result when post
}

 fun processPost(post: Post){
  // 对Post对象进行一些处理,比如更新UI
}

// 整合起来
suspend fun postItem(item: Item){
(挂起点)  val token = requestToken()
(挂起点)  val post = createPost(token,item)
  processPost(post)
}

和正常程序比,只是多了多了suspend关键字。其本质是kotlin在编译时,在suspend函数的参数签名,自动加入callback。

 

kotlin协程的实现方式,只引入了suspend一个关键字,剩余的实现是通过协程库来补充实现的。这样的好处在于通过升级库就能提升协程的功能,而不需要频繁求助于编译器。

 

当然编译器在处理suspend关键字时聪明得多,能处理任意复杂的程序逻辑,这样你可以继续利用已经掌握的传统编程里面的try\cache、while或者filter\map\forEach\repeat\filter等函数。

 

总而言之,就是kotlin协程帮你用阻塞的编程方式,实现异步编程。

 

小结
好了,希望我把这阻塞、异步等这些概念讲清楚了。

 

我当初学习使用kotlin协程,一开始也有很多困惑点。放心这些后面我们一一解释。但是要想能用好协程,得了解来龙去脉,了解为什么要设计协程这个抽象概念。

 

协程就是解决callback地狱,用阻塞编程方式实现异步程序,提高系统性能。

 

来源:InfoQ 

分类
标签
已于2020-9-8 11:10:13修改
收藏
回复
举报
回复
    相关推荐