Kotlin学习系列之:协程的创建(一)

x33g5p2x  于2022-03-08 转载在 其他  
字(4.3k)|赞(0)|评价(0)|浏览(504)

1.协程:Coroutine

2.如何去理解协程: 可以视为轻量级的线程

  • 我们可以回顾一下什么是线程。从操作系统原理的角度来讲,进程是资源分配的基本单位,而线程是调度的基本单位,也就是说线程实际上是系统级别的,它运行与否是由操作系统决定的。从Java语言层面上讲,我们可以通过new Thread().start这种形式去启动一个线程,我们可以查看Thread类的源代码:
public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

private native void start0();

我们可以看到start0是一个native方法,也就是说开启一个线程实际上是需要jvm同底层操作系统进行打交道的。那么我们可以用一句话总结就是:线程是系统级别的,它的开销是巨大的,比较耗费计算机资源,而协程是轻量级的,开销小。

  • 既然协程如此轻便,那么是不是意味着我们可以抛弃线程了呢?当然不是,协程是依赖于线程的,不过协程挂起时是不会阻塞线程的,而一个线程中可以创建任意个协程
  • 协程的作用:为了简化异步编程。回想Java中,我们需要线程同步或者接口回调的方式来实现异步编程,实际上很繁琐。而像Flutter中,或者说Dart语言本身,提供了async、await等关键字来实现异步编程。Kotlin中就提供了协程来简化异步编程

3.编写第一个协程代码:

fun main() {

    GlobalScope.launch {
        delay(1000)
        println("world")
    }

    println("hello")

    Thread.sleep(2000)

    println("welcome")
}

输出结果:

hello
(等待1s)
world
(等待1s)
welcome

这段代码我们待会儿再来解释,我们再来看一个熟悉的例子:

fun main() {

    Thread{
        Thread.sleep(1000)
        println("world")
    }.start()

    println("hello")
    Thread.sleep(2000)
    println("welcome")

}

输出结果同上面一样,等待时间也是一样的。后面这段代码易懂,我们按照时间顺序来解释:

  • 0s: 启动一个子线程;子线程开始休眠;主线程打印hello;主线程开始休眠。
  • 1s:子线程到达休眠时间,唤醒后打印world
  • 2s:主线程被唤醒,打印welcome

理解到这,我们再来对比第一段协程代码:

  • 如何创建一个协程:GlobalScope.launch{...} 是不是和创建一个子线程的形式类似
  • launch后面的代码块是异步执行的,并且不会阻塞线程

同样,我们按照时间顺序来解释协程代码:

  • 0s:创建一个协程;执行delay延迟;主线程打印hello;主线程开始休眠
  • 1s:协程的delay时间已到,打印world
  • 2s:主线程被唤醒,打印welcome

现在,我们针对第一段协程代码,稍微改动一下主线程的休眠时间,将2000毫秒改为500毫秒,再来看一看运行结果:

hello
休眠0.5s
welcome

好了,到这程序就运行结束了,"world"永远也不会有机会输出。为什么呢?

  •  0s:创建一个协程;执行delay延迟;主线程打印hello;主线程开始休眠
  • 0.5s:主线程被唤醒,打印welcome;主线程运行结束,程序退出。

程序都已经退出了,那么协程当然也就不存在了。感兴趣你还可以针对第二段线程代码进行同样的修改,看看结果有什么不同,这里就不再详述了。

4.GlobalScope.launch{}

Launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a [Job]

这是launch方法的描述,翻译过来就是:它会在不阻塞当前线程的前提下去启动一个新的协程,然后返回一个关联该协程的Job类型的引用。

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

另外从源代码的角度上可以看出:launch是CoroutineScope的一个扩展方法,并且返回值是一个Job类型

  • CoroutineScope:
/**
 * Defines a scope for new coroutines. Every coroutine builder
 * is an extension on [CoroutineScope] and inherits its [coroutineContext][CoroutineScope.coroutineContext]
 * to automatically propagate both context elements and cancellation.
*/
public interface CoroutineScope {
/**
     * The context of this scope.
     * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
     * Accessing this property in general code is not recommended for any purposes except accessing [Job] instance for advanced usages.
     *
     * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
     */
    public val coroutineContext: CoroutineContext
}

用来定义协程的作用范围,有个概念就行。

  • GlobalScope:
public object GlobalScope : CoroutineScope {
    /**
     * Returns [EmptyCoroutineContext].
     */
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}

可以看出,GlobalScope是一个实现了CoroutineScope接口的对象,并且重写coroutineContext的get方法。

当然,CoroutineScope.launch{}只是启动协程的方式之一,Kotlin还提供了其他启动协程的方式,这些方式或者叫方法有一个名字,叫做协程建造器(coroutine builder)。我们将在接下来的篇章去进行介绍

5.补充:

我们刚刚在创建一个线程时,使用了如下代码:

Thread{
    ....
}.start()

实际上Kotlin中提供一种更为简便的方式:

thread {
    ...
}

这个thread实际上一个顶级函数(top-level function),在kotlin.concurrent包下,它的实现如下:

public fun thread(
    start: Boolean = true,
    isDaemon: Boolean = false,
    contextClassLoader: ClassLoader? = null,
    name: String? = null,
    priority: Int = -1,
    block: () -> Unit
): Thread {
    val thread = object : Thread() {
        public override fun run() {
            block()
        }
    }
    if (isDaemon)
        thread.isDaemon = true
    if (priority > 0)
        thread.priority = priority
    if (name != null)
        thread.name = name
    if (contextClassLoader != null)
        thread.contextClassLoader = contextClassLoader
    if (start)
        thread.start()
    return thread
}

实现也是一看就懂,就是Kotlin为了开发者编码方便而做了一层封装。

相关文章