详细聊一聊kotlin协程中的线程池是如何工作的

前言

协程并不是kotlin发明的,但是在kotlin中协程是真的好用。但是协程的初衷是单线程中任务的调度,可以充分利用线程资源,如果我们任务多到单线程已经无法及时处理怎么办?

脑海中的第一反应就是多线程处理,通过线程池进行管理。但是java传统的线程池是无法满足kotlin协程的灵活调度的。为什么?不妨来想一下,当用线程1来执行任务,这时候来了一个新的任务,是用线程1执行还是新开线程执行?最佳的方案是当线程1繁忙的时候新开线程来执行,否则用线程1执行,但是java传统的线程池是不具备这种功能的。

那么我们需要改造java线程池来满足需求么?

当然不需要,kotlin已经为我们实现了可以灵活调度协程的线程池——CoroutineDispatcher

CoroutineDispatcher

CoroutineDispatcher是一个抽象类,不过kotlin中已经提供了几个实现,都在Dispatchers中:

  • Default: 用于处理cup密集型的任务
  • Main:处理主线程(即UI线程)的任务
  • IO:用于处理IO密集型任务

可以看到一般情况下我们不必自己去实现CoroutineDispatcher,现有的已经完全可以满足我们的需求。

使用起来也很简单,在使用launch的时候添加即可,如下:

launch(Dispatchers.Default) {
    ...
}

launch这个函数实际上有三个参数

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
}

其中前两个参数是有默认值的,其中第一个参数context是CoroutineContext类型,而CoroutineDispatcher最终也继承了CoroutineContext。当为context赋值后,就会使用对应的线程池来分配处理任务。

那么这些线程池是如何实现对协程的灵活分配的?

Dispatchers.Default

先从Dispatchers.Default来看,在Dispatchers是这样定义的

@JvmStatic
public actual val Default: CoroutineDispatcher = DefaultScheduler

实际上是DefaultScheduler,而它则继承了SchedulerCoroutineDispatcher

internal object DefaultScheduler : SchedulerCoroutineDispatcher(
    CORE_POOL_SIZE, MAX_POOL_SIZE,
    IDLE_WORKER_KEEP_ALIVE_NS, DEFAULT_SCHEDULER_NAME
) {
   ...
}

这里面有几个参数,大家看着应该很眼熟,跟java线程池的那几个参数很像:核心线程数、最大线程数、存活时间,那么这几个数都是多少呢

线程池参数

CORE_POOL_SIZE

核心线程数定义如下:

@JvmField
internal val CORE_POOL_SIZE = systemProp(
    "kotlinx.coroutines.scheduler.core.pool.size",
    AVAILABLE_PROCESSORS.coerceAtLeast(2),
    minValue = CoroutineScheduler.MIN_SUPPORTED_POOL_SIZE
)

这里看并不是一个固定的值,而是通过systemProp函数来得到的,这个函数代码如下:

internal fun systemProp(
    propertyName: String,
    defaultValue: Long,
    minValue: Long = 1,
    maxValue: Long = Long.MAX_VALUE
): Long {
    val value = systemProp(propertyName) ?: return defaultValue
    val parsed = value.toLongOrNull()
        ?: error("System property '$propertyName' has unrecognized value '$value'")
    if (parsed !in minValue..maxValue) {
        error("System property '$propertyName' should be in range $minValue..$maxValue, but is '$parsed'")
    }
    return parsed
}

一共四个参数,命名一目了然就不一一说了,这个函数的简单来说就是获取对应名称的系统属性,如果没有则使用默认值,然后将这个值与最大最小值比较校验。

返回去看CORE_POOL_SIZE,最大值没设置所以就算MAX_VALUE,最小值MIN_SUPPORTED_POOL_SIZE是1,最关键的还是默认值是

AVAILABLE_PROCESSORS.coerceAtLeast(2)

AVAILABLE_PROCESSORS定义是

internal val AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors()

就是当前可用处理器的数量,coerceAtLeast(2)表示下限是2,所以默认值就是当前可用处理器数量,如果小于2则是2。

这样CORE_POOL_SIZE也就很清晰了,可以很清楚的看到线程数是与CPU核心数相关的,这样可以充分利用CPU资源也不会创建过多的线程。

MAX_POOL_SIZE

最大线程数定义如下

@JvmField
internal val MAX_POOL_SIZE = systemProp(
    "kotlinx.coroutines.scheduler.max.pool.size",
    CoroutineScheduler.MAX_SUPPORTED_POOL_SIZE,
    maxValue = CoroutineScheduler.MAX_SUPPORTED_POOL_SIZE
)

这个就更简单了,默认值和最大值是一样的,都是MAX_SUPPORTED_POOL_SIZE,这个数是(1 shl 21) - 2

IDLE_WORKER_KEEP_ALIVE_NS

存活时间定义如下

@JvmField
internal val IDLE_WORKER_KEEP_ALIVE_NS = TimeUnit.SECONDS.toNanos(
    systemProp("kotlinx.coroutines.scheduler.keep.alive.sec", 60L)
)

可以看到没有系统设置的话,最大就是60秒

CoroutineScheduler

上面这几个参数最终是在CoroutineScheduler中发挥作用的,回到一开始的SchedulerCoroutineDispatcher中,会创建一个CoroutineScheduler,如下:

private var coroutineScheduler = createScheduler()

private fun createScheduler() =
    CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName)

这个CoroutineScheduler就是用来管理线程池,进行任务分发的调度器。

其实无论是Dispatchers.Default还是Dispatchers.IO,最终都是通过CoroutineScheduler来管理线程池,只不过在线程池的参数和一些其他细节有些许不一样。下面来看看分发的过程是什么样的。

任务分发

任务分发是核心功能,是由dispatch函数来实现的,最终是在CoroutineScheduler的dispatch函数来处理,代码如下:

fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) {
    trackTask() // this is needed for virtual time support
    val task = createTask(block, taskContext)   //1
    // try to submit the task to the local queue and act depending on the result
    val currentWorker = currentWorker()     //2
    val notAdded = currentWorker.submitToLocalQueue(task, tailDispatch)  //3
    if (notAdded != null) {
        if (!addToGlobalQueue(notAdded)) {    //4
            // Global queue is closed in the last step of close/shutdown -- no more tasks should be accepted
            throw RejectedExecutionException("$schedulerName was terminated")
        }
    }
    val skipUnpark = tailDispatch && currentWorker != null  //5
    // Checking 'task' instead of 'notAdded' is completely okay
    if (task.mode == TASK_NON_BLOCKING) {
        if (skipUnpark) return
        signalCpuWork()  //6
    } else {
        // Increment blocking tasks anyway
        signalBlockingWork(skipUnpark = skipUnpark)   //7
    }
}

可以看到代码1和代码2分别创建Task和获取Worker,这也是整个线程池中的两个重要概念

task

task就是任务,实际上就是将我们要执行的代码封装成Runnable,源码如下:

internal abstract class Task(
    @JvmField var submissionTime: Long,
    @JvmField var taskContext: TaskContext
) : Runnable {
    constructor() : this(0, NonBlockingContext)
    inline val mode: Int get() = taskContext.taskMode // TASK_XXX
}

可以看到它继承Runnable,有一个很重要的属性——mode,标记这个任务是不是阻塞的,有两种状态:

  • TASK_NON_BLOCKING:非阻塞任务,计算密集型任务
  • TASK_PROBABLY_BLOCKING:可能会阻塞

这个mode是taskContext带过来的,taskContext就是dispatch中的第二个参数,可以看到在Dispatchers.Default中这个参数是NonBlockingContext,所以通过它执行的task都是TASK_NON_BLOCKING状态。同理可以想到Dispatchers.IO的任务应该就是TASK_PROBABLY_BLOCKING状态。

后面可以看到针对不同状态的Task会有不同的处理。

worker

worker其实就是线程,它的源码很大,后面我们讲分发还会涉及到,所以这里就先不贴出来了。它实际上是继承了Thread,比较重要的是它内部维护了一个localQueue队列,这个队列也是CoroutineDispatcher的一个比较重要部分,后面会遇到。

回头看获取Worker的函数是currentWorker,代码如下:

private fun currentWorker(): Worker? = (Thread.currentThread() as? Worker)?.takeIf { it.scheduler == this }

可以看到就是获取了当前的线程,因为协程代码是在一个Worker中执行,所以当前线程也是一个Worker对象。

本地队列和全局队列

继续看dispatch的代码,代码3处执行了submitToLocalQueue,故名思义是将这个任务加入到本地队列中,这个本地队列就是上面Worker内部维护的localQueue。

继续看代码4,如果提交失败则将任务加入到全局队列。

本地队列

全局队列先放一边,我们来看看提交失败的情况有哪些。看看submitToLocalQueue的实现:

private fun Worker?.submitToLocalQueue(task: Task, tailDispatch: Boolean): Task? {
    if (this == null) return task

    if (state === WorkerState.TERMINATED) return task

    if (task.mode == TASK_NON_BLOCKING && state === WorkerState.BLOCKING) {
        return task
    }
    mayHaveLocalTasks = true
    return localQueue.add(task, fair = tailDispatch)
}

如果当前Worker状态是停止(TERMINATED),提交失败;如果是非阻塞任务(即计算密集型任务)而当前Worker是阻塞状态,也提交失败(这是因为计算密集型任务要尽快执行,如果当前Worker是阻塞的,无法知道何时能执行到,所以就不向这个Worker提交这种任务)。

但是提交失败不仅仅是这些,来看看localQueue的add函数:

fun add(task: Task, fair: Boolean = false): Task? {
    if (fair) return addLast(task)
    val previous = lastScheduledTask.getAndSet(task) ?: return null
    return addLast(previous)
}


private fun addLast(task: Task): Task? {
    if (task.isBlocking) blockingTasksInBuffer.incrementAndGet()
    if (bufferSize == BUFFER_CAPACITY - 1) return task
    val nextIndex = producerIndex.value and MASK

    while (buffer[nextIndex] != null) {
        Thread.yield()
    }
    buffer.lazySet(nextIndex, task)
    producerIndex.incrementAndGet()
    return null
}

在addLast函数中还有一个容量校验,也就是说localQueue还有一个容量限制BUFFER_CAPACITY(128),所以当localQueue中的任务超过这个也会提交失败。

全局队列

当本地队列添加失败,就会添加到全局队列,addToGlobalQueue的代码如下:

private fun addToGlobalQueue(task: Task): Boolean {
    return if (task.isBlocking) {
        globalBlockingQueue.addLast(task)
    } else {
        globalCpuQueue.addLast(task)
    }
}

可以看到全局队列有两个globalBlockingQueue和globalCpuQueue,分别装载阻塞任务和CPU任务。这两个队列是CoroutineScheduler的属性。

这里可以看到协程线程池的一个调度技巧,当一个线程繁忙(阻塞或者队列任务太多)的时候,会将后续的任务放到全局队列中,后续就会根据当前线程数来判断是否创建新的线程来执行这些任务。那么这块是如何实现的?

启动Worker

继续看dispatch的代码,在将任务加入队列后,下一步就是启动worker来执行任务。

先是在代码5处先判断当前worker是不是空,如果不为空且tailDispatch是true则skipUnpark是true。

skipUnpark如果是true,则跳过后面唤起和创建worker的过程。

继续看下面的代码,可以看到通过当前任务的阻塞状态来执行不同的代码signalCpuWorksignalBlockingWork,不过对比这两个函数的代码

    private fun signalBlockingWork(skipUnpark: Boolean) {
        // Use state snapshot to avoid thread overprovision
        val stateSnapshot = incrementBlockingTasks()
        if (skipUnpark) return
        if (tryUnpark()) return
        if (tryCreateWorker(stateSnapshot)) return
        tryUnpark() // Try unpark again in case there was race between permit release and parking
    }

    fun signalCpuWork() {
        if (tryUnpark()) return
        if (tryCreateWorker()) return
        tryUnpark()
    }

可以看到两个函数大致的流程是一样的,先通过tryUnpark函数来唤醒worker执行任务,如果唤醒失败则通过tryCreateWorker来创建Worker,然后再通过tryUnpark唤醒。

唤醒Worker

tryUnpark源码如下:

private fun tryUnpark(): Boolean {
    while (true) {
        val worker = parkedWorkersStackPop() ?: return false
        if (worker.workerCtl.compareAndSet(PARKED, CLAIMED)) {
            LockSupport.unpark(worker)
            return true
        }
    }
}

首先通过parkedWorkersStackPop来获取worker,来看看这部分细节:

private fun parkedWorkersStackPop(): Worker? {
    parkedWorkersStack.loop { top ->
        val index = (top and PARKED_INDEX_MASK).toInt()
        val worker = workers[index] ?: return null  //1
        val updVersion = (top + PARKED_VERSION_INC) and PARKED_VERSION_MASK
        val updIndex = parkedWorkersStackNextIndex(worker)
        if (updIndex < 0) return@loop 

        if (parkedWorkersStack.compareAndSet(top, updVersion or updIndex.toLong())) {
            worker.nextParkedWorker = NOT_IN_STACK
            return worker
        }
    }
}

parkedWorkersStack存储的是所有挂起Worker的索引。然后在代码1处,这里的workers是CoroutineScheduler的一个变量,这是一个全局性的列表,存储了当前所有的Worker。这里就是从这个列表中取出最后一个挂起的Worker来唤醒。

创建Worker

如果唤醒失败就会重新创建一个Worker,代码如下

private fun tryCreateWorker(state: Long = controlState.value): Boolean {
    val created = createdWorkers(state)
    val blocking = blockingTasks(state)
    val cpuWorkers = (created - blocking).coerceAtLeast(0)

    if (cpuWorkers < corePoolSize) {
        val newCpuWorkers = createNewWorker()

        if (newCpuWorkers == 1 && corePoolSize > 1) createNewWorker()
        if (newCpuWorkers > 0) return true
    }
    return false
}

这里可以看到有一个cpuWorkers的概念,它是当前所有Worker的数量减去当前阻塞任务的数量(即当前被阻塞任务占用的Worker数量),然后用这个cpuWorkers去跟corePoolSize比较,如果超过就不创建。所以这里也是与java传统线程池不一样的地方,corePoolSize对应的是执行计算密集型Worker的核心数量,这样可以让CPU充分发挥作用。(阻塞任务一般都是IO密集型,主要时间都是等待IO操作,这时候是不占用CPU资源的)

创建createNewWorker的源码如下

    private fun createNewWorker(): Int {
        synchronized(workers) {
            // Make sure we're not trying to resurrect terminated scheduler
            if (isTerminated) return -1
            val state = controlState.value
            val created = createdWorkers(state)
            val blocking = blockingTasks(state)
            val cpuWorkers = (created - blocking).coerceAtLeast(0)
            // Double check for overprovision
            if (cpuWorkers >= corePoolSize) return 0
            if (created >= maxPoolSize) return 0

            val newIndex = createdWorkers + 1
            require(newIndex > 0 && workers[newIndex] == null)

            val worker = Worker(newIndex)   //1
            workers.setSynchronized(newIndex, worker)   //2
            require(newIndex == incrementCreatedWorkers())
            worker.start()    //3
            return cpuWorkers + 1
        }
    }

这里同样用cpuWorkers与corePoolSize,但是注意与maxPoolSize对比就是全部Worker的数量,所以核心数量和最大数量对比的角度是不一样的。如果可以创建新Worker,则new一个Worker出来(代码1),然后添加到全局的workers中(代码2),最后启动这个Worker(代码3)。

执行任务

Worker是一个Thread,所以它的任务执行是在run函数中的,在Worker中run函数会直接调用runWorker函数,代码如下:

private fun runWorker() {
    var rescanned = false
    while (!isTerminated && state != WorkerState.TERMINATED) {
        val task = findTask(mayHaveLocalTasks)  //1
        // Task found. Execute and repeat
        if (task != null) {
            rescanned = false
            minDelayUntilStealableTaskNs = 0L
            executeTask(task)   //2
            continue
        } else {
            mayHaveLocalTasks = false
        }

        ...

        tryPark()  //3
    }
    tryReleaseCpu(WorkerState.TERMINATED)  //4
}

来关注一下关键代码,可以看到这里有一个while循环,这样可以不停的处理任务,在代码1处通过findTask来获取一个任务;如果有任务则在代码2处通过executeTask函数来执行任务,执行完成重新获取任务;如果没有任务会通过tryPark(代码3处)来挂起线程,等待新任务的到来后再激活线程继续执行任务。最后如果线程结束,退出while循环会执行tryReleaseCpu(代码4)来释放Cpu资源。

所以这里最主要的部分是findTask函数,它获取任务的逻辑是什么?

获取任务

获取任务的源码如下:

fun findTask(scanLocalQueue: Boolean): Task? {
    if (tryAcquireCpuPermit()) return findAnyTask(scanLocalQueue)
    // If we can't acquire a CPU permit -- attempt to find blocking task
    val task = if (scanLocalQueue) {
        localQueue.poll() ?: globalBlockingQueue.removeFirstOrNull()
    } else {
        globalBlockingQueue.removeFirstOrNull()
    }
    return task ?: trySteal(blockingOnly = true)
}

函数一开始如果tryAcquireCpuPermit是true,说明获得了CPU token,那么它就可以执行非阻塞任务(计算密集型任务),也可以执行阻塞任务,所以调用findAnyTask函数来获取任务;如果是false,则只能执行阻塞任务(因为无法立刻获取CPU资源来执行任务,而计算密集型任务应该尽快执行,所以这个Worker当前就不应该来执行非阻塞任务)。

那么对比看一下findAnyTask源码:

private fun findAnyTask(scanLocalQueue: Boolean): Task? {
    if (scanLocalQueue) {
        val globalFirst = nextInt(2 * corePoolSize) == 0
        if (globalFirst) pollGlobalQueues()?.let { return it }
        localQueue.poll()?.let { return it }
        if (!globalFirst) pollGlobalQueues()?.let { return it }
    } else {
        pollGlobalQueues()?.let { return it }
    }
    return trySteal(blockingOnly = false)
}

可以看到两个函数获取任务的逻辑大致相同,一般情况下都是先在localQueue中获取任务,如果没有任务则会在globalBlockingQueue中获取任务,这样就保证了任务调度的灵活性。

findAnyTask中获取全局任务用的是pollGlobalQueues这个函数,这里是通过一个随机数来在前面提到的两个全局队列中获取任务的,如下:

private fun pollGlobalQueues(): Task? {
    if (nextInt(2) == 0) {
        globalCpuQueue.removeFirstOrNull()?.let { return it }
        return globalBlockingQueue.removeFirstOrNull()
    } else {
        globalBlockingQueue.removeFirstOrNull()?.let { return it }
        return globalCpuQueue.removeFirstOrNull()
    }
}

所以,一个Worker并不是完全固定执行某一种任务,这样就更加灵活。

注意在两个函数的最后都有一个trySteal函数,这个就很灵性,它是用来偷取任务的,那么它是如何工作的?

偷取任务

在本地任务和全局任务列表中都没有获取到任务的时候,就会执行trySteal函数来偷取其他Worker的任务,来看看源码:

private fun trySteal(blockingOnly: Boolean): Task? {
    assert { localQueue.size == 0 }
    val created = createdWorkers
    // 0 to await an initialization and 1 to avoid excess stealing on single-core machines
    if (created < 2) {
        return null
    }

    var currentIndex = nextInt(created)  
    var minDelay = Long.MAX_VALUE
    repeat(created) {
        ++currentIndex
        if (currentIndex > created) currentIndex = 1
        val worker = workers[currentIndex]
        if (worker !== null && worker !== this) {
            assert { localQueue.size == 0 }
            val stealResult = if (blockingOnly) {
                localQueue.tryStealBlockingFrom(victim = worker.localQueue)
            } else {
                localQueue.tryStealFrom(victim = worker.localQueue)
            }
            if (stealResult == TASK_STOLEN) {
                return localQueue.poll()
            } else if (stealResult > 0) {
                minDelay = min(minDelay, stealResult)
            }
        }
    }
    minDelayUntilStealableTaskNs = if (minDelay != Long.MAX_VALUE) minDelay else 0
    return null
}

逻辑比较简单,通过随机数在全局的workers中选一个Worker,然后判断不是当前的Worker,最后在它的localQueue中偷一个任务来执行。这样就不会出现有的Worker闲着,而有的Worker中很多任务在等待执行的情况。

这里可以看到kotlin对协程的调度做了很多细节,充分的使用系统资源,保证了协程能得到及时处理。

这样整个分发和执行的流程大体上都讲完了,当然还有大量的细节没有提到,大家有兴趣自己去研究研究,包括这里面对于多线程间同步问题做了大量的工作。

Dispatchers.IO

最后来看看Dispatchers.IO的实现

@JvmStatic
public val IO: CoroutineDispatcher = DefaultIoScheduler

它是DefaultIoScheduler,里面有一个default

internal object DefaultIoScheduler : ExecutorCoroutineDispatcher(), Executor {

    private val default = UnlimitedIoScheduler.limitedParallelism(
        systemProp(
            IO_PARALLELISM_PROPERTY_NAME,
            64.coerceAtLeast(AVAILABLE_PROCESSORS)
        )
    )
    ...
}

实际上所有的工作都是这个default来完成的,那么它是什么,来看看limitedParallelism的代码:

public open fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
    parallelism.checkParallelism()
    return LimitedDispatcher(this, parallelism)
}

最终返回的是一个LimitedDispatcher,它的第一个参数传入的是this,就是UnlimitedIoScheduler对象。

来看看LimitedDispatcher的dispatch函数:

override fun dispatch(context: CoroutineContext, block: Runnable) {
    dispatchInternal(block) {
        dispatcher.dispatch(this, this)
    }
}

先通过dispatchInternal函数来进行内部处理,这个后面会详细说,然后还是由dispatcher,即UnlimitedIoScheduler来执行分发,看看它的源码

private object UnlimitedIoScheduler : CoroutineDispatcher() {

    @InternalCoroutinesApi
    override fun dispatchYield(context: CoroutineContext, block: Runnable) {
        DefaultScheduler.dispatchWithContext(block, BlockingContext, true)
    }

    override fun dispatch(context: CoroutineContext, block: Runnable) {
        DefaultScheduler.dispatchWithContext(block, BlockingContext, false)
    }
}

可以看到,都是执行了DefaultScheduler的dispatchWithContext函数。前面我们知道Dispatchers.Default也是DefaultScheduler,所以分发流程是一样的。

所以Dispatchers.Default和Dispatchers.IO大体上是差不多的,线程池的各个参数也是一样的。不同的是这里执行dispatch的时候传入的都是BlockingContext,来标记这个任务是阻塞的。

dispatchInternal

但是Dispatchers.IO也有不同之处,可以看到limitedParallelism这个函数有一个参数,传入的是一个int值:

systemProp(
    IO_PARALLELISM_PROPERTY_NAME,
    64.coerceAtLeast(AVAILABLE_PROCESSORS)
)

其实就是获取当前可用处理器的数量,那么这个值有什么用?

这个parallelism最终是传入到LimitedDispatcher中,前面提到它的dispatch函数首先会执行dispatchInternal,来看看做了什么:

private inline fun dispatchInternal(block: Runnable, dispatch: () -> Unit) {
    if (addAndTryDispatching(block)) return

    if (!tryAllocateWorker()) return
    dispatch()
}

先执行了addAndTryDispatching,这个函数如下

private fun addAndTryDispatching(block: Runnable): Boolean {
    queue.addLast(block)
    return runningWorkers >= parallelism
}

可以看到先将这个任务加入队列中。

然后可以看到有一个runningWorkers的变量,就是当前正在运行的Worker的数量,由于是Dispatchers.IO所以就是当前正在运行阻塞任务(IO任务)的Worker数。

而parallelism会与他进行比较,当runningWorkers大于等于parallelism就不再继续分发;否则执行tryAllocateWorker,这个函数如下

private fun tryAllocateWorker(): Boolean {
    synchronized(workerAllocationLock) {
        if (runningWorkers >= parallelism) return false
        ++runningWorkers
        return true
    }
}

这里同样判断了当runningWorkers大于等于parallelism就返回false,从前面的代码看,返回false就不再继续分发了。当小于parallelism则会将runningWorkers加1并返回true,这样才会继续执行到DefaultScheduler里分发。

这就是Dispatchers.IO与Dispatchers.Default的一个不同之处,用一个parallelism来限制正在运行的Worker数量。

如果正在运行阻塞任务的Worker大于等于可用CPU数的话,就不会执行DefaultScheduler的分发,也就不会重新创建Worker,所以Dispatchers.IO中Worker的最大数量就是parallelism(当然还受maxPoolSize的限制)。

总结

通过上面可以看到Dispatchers.IO与Dispatchers.Default实际上使用的是一个线程池,corePoolSize是非阻塞Worker的核心数量,而阻塞Worker数量最大不超过parallelism,同时这两种Worker总数量不能超过maxPoolSize。

在分发方面,任务会先尝试放入本地队列,如果失败会放入全局队列,并且新建Worker进行处理。当一个Worker处理完自己的任务后会从全局队列获取任务处理,如果全局队列也空了就会尝试从其他Worker的队列中偷取任务来处理,这样就保证了每个任务都能尽快被执行。

在任务方面,分成了阻塞和非阻塞两种,并且有不同的处理逻辑,让非阻塞任务能够尽快的得到执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BennuCTech

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值