Kotlin协程原理理解和思考

先来看一段很简单的kotlin协程代码:

fun test4() {
    GlobalScope.launch {
        println("准备执行")
        val value = async {
            println("执行异步")
            1024
        }.await()
        println("执行完毕:${value}")
    }
}

就是中途有段异步执行,代码需要等待这段异步执行完毕才会继续执行下面的操作,现在,让我们用Java实现一遍,用两个Runnable来模拟:

private static final Object COROUTINE_SUSPEND = "suspend";

class MyRunnable implements Runnable {

  	 int label = 0;

     @Override
     public void run() {
         invoke(null);
     }

	// 这段invoke代码就是上面那段了
     public void invoke(Object result) {
         Object value = COROUTINE_SUSPEND;
         checkException(result);
         switch (label) {
             case 0:
                 println("准备执行");
                 value = async(this);
                 label = 1;
                 if (value == COROUTINE_SUSPEND) {
                     return;
                 }
                 break;
             case 1:
                 value = result;
                 break;
         }
         int num = (int) value;
         println("执行完毕:" + num);
     }
 };

  class AsyncRunnable implements Runnable {

       boolean isCompleted;
       MyRunnable otherRunnable;

       public Object myAsync(MyRunnable runnable) {
           otherRunnable = runnable;
           executors.submit(this);
           return COROUTINE_SUSPEND;
       }

       @Override
       public void run() {
           invoke(null);
       }

       private void invoke(Object result) {
           checkException(result);
           println("执行异步");
           int value = 1024;
           isCompleted = true;
           otherRunnable.invoke(value);
        }
    }

	
  private Object async(MyRunnable myRunnable) {
       AsyncRunnable asyncRunnable = new AsyncRunnable();
       return asyncRunnable.myAsync(myRunnable);
   }

其实挺简单的,协程最核心的点就是函数或者一段程序能被挂起,之后能在挂起的位置重新恢复,而这挂起和恢复就是『先返回函数,异步完成后再重新执行』,里面的代码再被分成好几段去执行。

review以下刚才的代码,有两个点非常重要:
(1)调用async函数的时候会把MyRunnable传进去,对应到协程的实现,就是每个suspend函数都有一个隐藏的参数Continuation,这就是典型的CPS变换(continuation Passing Style)。这样的话待异步操作完成后,重新调用刚才参数MyRunnable的invoke方法即可。
(2)依靠label变量记录执行点,这样下次重新invoke的时候就知道上次执行到哪步了,分段的地方就是调用了suspend函数并且返回COROUTINE_SUSPEND标志。

看完之后你再去看kotlin代码编译后的字节码,就是这样实现的。再通俗点理解,你可以把隐藏的Continuation当做是Callback,这就很常见了,Kotlin只是帮你把这些都省去了,让你不需要写那些Callback,把异步代码写得看起来就像同步的代码一样。

那么,接下来就有一个很重要的问题了,其实看那段代码,你用线程池还是Kotlin的协程,其实效率是无差的,那么,除了代码的简洁外,怎么使用Kotlin的协程才能体现出优势

这里说点我的理解:对于会造成阻塞的IO任务,不管是协程还是线程池,都不可避免地阻塞线程,我们能提高效率的就是把一些原本Java会造成线程阻塞的行为,都用kotlin的协程API替代,这样才能最大化的利用线程的资源

比如:

  1. 用delay代替Thread.sleep;
  2. 用channel代替BlockCollection;
  3. 用kotlin的yield、join代替线程的yield、join;
  4. 用协程的Metux代替java的锁机制;

像Thread.sleep这些方法,一旦阻塞到了线程,那么这个线程就没办法继续工作了,在任务量多的时候,就只能重新开新的线程去执行,而我们都知道线程是一个占用资源比较多的机制,而协程的API,都巧妙地避开了这些阻塞方法,用更轻量的操作去实现,比如delay方法,就是起了个定时器再重新去执行,感兴趣可以去研究一下代码。

猜你喜欢

转载自blog.csdn.net/aa642531/article/details/112055914