当前位置: 首页 > news >正文

Kotlin协程解析

目录

一、协程的使用

二、协程的执行原理

2.1、挂起函数的反编译代码及执行分析

2.2、协程执行流程分析

2.2.1、createCoroutineUnintercepted方法

2.2.2、intercepted方法

2.2.3、resumeCancellableWith方法

2.3、Dispatcher----分发器的实现

2.3.1、Main 分发器的实现

2.3.2、Default、IO分发器的实现

三、Delay挂起函数的实现

3.1、Delay函数的定义

3.2、主线程的Delay函数实现

3.3、子线程的Delay函数实现

四、总结

参考


本文的介绍内容如下:1、协程和线程的区别;2、协程是如何执行的;3、协程是如何挂起的;4、协程挂起后是如何恢复的。

协程可以看作是一个通过“状态机”执行的代码块,还是在线程上执行,并没有新的机器指令的支持。

一、协程的使用

下面给出一个简介的demo。在挂起函数test1中,先挂起30ms,然后执行挂起函数test2。

class TestSuspend {private val TAG = "TestSuspend"suspend fun test1(): Int {delay(30)Log.i(TAG, "test1")test2()return 1}suspend fun test2() {Log.i(TAG, "test2")}@OptIn(DelicateCoroutinesApi::class)fun startTest() {GlobalScope.launch {test1()test2()}}
}

二、协程的执行原理

2.1、挂起函数的反编译代码及执行分析

以挂起函数test1为例,为了更好的分析协程是如何执行的,需要查看上述demo的反编译代码,如下:

public final class TestSuspend {@NotNullprivate final String TAG = "TestSuspend";public static final int $stable;// 如果是第二次以后的执行,传入的参数就是内部创建的那个协程对象$continuation@Nullablepublic final Object test1(@NotNull Continuation $completion) {Continuation $continuation;label27: {if ($completion instanceof <undefinedtype>) {$continuation = (<undefinedtype>)$completion;if (($continuation.label & Integer.MIN_VALUE) != 0) {$continuation.label -= Integer.MIN_VALUE;break label27;}}// 注1:创建一个协程对象,此协程对象的作用是在存储当前协程的状态及返回$continuation = new ContinuationImpl($completion) {Object L$0;// $FF: synthetic fieldObject result;int label;// 注3 调用协程的resume方法去恢复协程执行时,会调用到这里@Nullablepublic final Object invokeSuspend(@NotNull Object $result) {this.result = $result;this.label |= Integer.MIN_VALUE;return TestSuspend.this.test1((Continuation)this);}};}Object $result = $continuation.result;// var4是协程挂起状态常量Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();// 通过switch的方式,实现状态机switch ($continuation.label) {case 0:ResultKt.throwOnFailure($result);$continuation.L$0 = this;$continuation.label = 1;// 注2:delay方法会返回一个协程挂起状态常量,与var4相同。这时直接返回的方式,挂起当前协程if (DelayKt.delay(30L, $continuation) == var4) {return var4;}break;case 1:this = (TestSuspend)$continuation.L$0;ResultKt.throwOnFailure($result);break;case 2:ResultKt.throwOnFailure($result);return Boxing.boxInt(1);default:throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");}Log.i(this.TAG, "test1");$continuation.L$0 = null;$continuation.label = 2;// 执行挂起函数test2,因为test2的返回值是Unit.INSTANCE,所以会返回1,当前协程就执行结束了if (this.test2($continuation) == var4) {return var4;} else {return Boxing.boxInt(1);}}@Nullablepublic final Object test2(@NotNull Continuation $completion) {Log.i(this.TAG, "test2");return Unit.INSTANCE;}public final void startTest() {// 在default线程执行BuildersKt.launch$default((CoroutineScope)GlobalScope.INSTANCE, (CoroutineContext)null, (CoroutineStart)null, new Function2((Continuation)null) {int label;public final Object invokeSuspend(Object $result) {Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();switch (this.label) {case 0:ResultKt.throwOnFailure($result);TestSuspend var10000 = TestSuspend.this;Continuation var10001 = (Continuation)this;this.label = 1;// 调用test1方法,因为test1内部会调用delay,所以这里第一次调用时会挂起if (var10000.test1(var10001) == var2) {return var2;}break;case 1:ResultKt.throwOnFailure($result);break;case 2:ResultKt.throwOnFailure($result);return Unit.INSTANCE;default:throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");}TestSuspend var3 = TestSuspend.this;Continuation var4 = (Continuation)this;this.label = 2;if (var3.test2(var4) == var2) {return var2;} else {return Unit.INSTANCE;}}public final Continuation create(Object value, Continuation $completion) {return (Continuation)(new <anonymous constructor>($completion));}// 调用invokeSuspend开始执行协程public final Object invoke(CoroutineScope p1, Continuation p2) {return ((<undefinedtype>)this.create(p1, p2)).invokeSuspend(Unit.INSTANCE);}// $FF: synthetic method// $FF: bridge methodpublic Object invoke(Object p1, Object p2) {return this.invoke((CoroutineScope)p1, (Continuation)p2);}}, 3, (Object)null);}
}

kotlin中协程的执行方式与状态机相同,在如果发生了挂起,就更新、记录内部的状态(代码中的label)。然后挂起协程,等待下一次的执行恢复回调。当再次执行时,根据内部状态,跳转到对应的逻辑处执行,直至协程执行结束。

根据上面的字节码,可以看出:

首先,挂起函数test1会在内部创建一个协程对象(注1),这个协程对象会保存协程的状态(即label)、下次恢复执行协程时需要的参数(此demo中未给出)、当前协程自身(即L$0)。

然后,协程基于switch实现状态机,作为协程实现挂起和恢复的基础。在注2处,test1方法调用了另一个挂起函数delay,此函数会返回一个COROUTINE_SUSPENDED对象,告诉当前协程需要挂起,当前方法就会通过return方式,实现协程挂起等待。在delay结束后,会调用协程的resume方法,继而调用到invokeSuspend方法,在此方法中重新调用test1方法去继续执行协程。delay函数的实现方式与线程有关,针对主线程,通过postDelay的方式实现,在延迟结束后,回去执行协程的resume方法。针对非主线程,会通过pack方式实现,调用此方法当线程阻塞一段时间。

最后,在延迟结束后,会调用协程的resume方法,恢复协程的执行。在之前调用delay时,将状态label改成了1,这时会直接跳转到label=1的地方进行执行。因为label=1的地方没有挂起函数,会继续往下执行,调用挂起函数test2,因为test2方法没有返回COROUTINE_SUSPENDED,所以返回test1方法的返回值,执行结束。

以上介绍了挂起函数test1的执行流程和协程挂起的实现方式----通过return实现挂起。下面分析test1是如何被调用。

2.2、协程执行流程分析

在demo中,通过GlobalScope.launch方式启动了一个协程,在协程内部执行了挂起函数test1和test2。此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) elseStandaloneCoroutine(newContext, active = true)coroutine.start(start, coroutine, block)return coroutine
}

可以看到此方法首先会调用newCoroutineContext方法创建一个协程上下文,如下:

public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {val combined = foldCopies(coroutineContext, context, true)val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combinedreturn if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)debug + Dispatchers.Default else debug
}

在此方法中会将Dispatchers.Default添加到上下文中,即协程会执行在默认分发器Default上。

然后,会创建一个StandaloneCoroutine对象,调用此对象的start方法启动协程,并将我们自定义的挂起函数作为block参数传入,此方法如下:

public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) 
{start(block, receiver, this)
}public enum class CoroutineStart {DEFAULT,LAZY,ATOMIC,UNDISPATCHED;public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit =when (this) {// 注4DEFAULT -> block.startCoroutineCancellable(receiver, completion)ATOMIC -> block.startCoroutine(receiver, completion)UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)LAZY -> Unit // will start lazily}
}

start方法会去调用CoroutineStart的invoke方法,因为传入的DEFAULT类型的CoroutineStart,所以会调用到注4处,如下:

internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation<T>,
) = runSafely(completion) {createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit))
}

此方法首先,会调用createCoroutineUnintercepted方法创建一个协程对象,此协程对象内部会调用我们自定义的挂起函数。然后,通过intercepted方法对创建的协程对象进行包装,以控制协程对象的执行;最后,调用resumeCancellableWith方法将协程抛到分发器(dispatcher)中去执行。

接下来,依次对上面3个步骤进行分析:

2.2.1、createCoroutineUnintercepted方法
public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(receiver: R,completion: Continuation<T>
): Continuation<Unit> {val probeCompletion = probeCoroutineCreated(completion)return if (this is BaseContinuationImpl)create(receiver, probeCompletion)else {createCoroutineFromSuspendFunction(probeCompletion) {(this as Function2<R, Continuation<T>, Any?>).invoke(receiver, it)}}
}

通过查看反编译代码,发现我们传入的是Function2类型的对象,所以会执行else中的代码,如下:

private inline fun <T> createCoroutineFromSuspendFunction(completion: Continuation<T>,crossinline block: (Continuation<T>) -> Any?
): Continuation<Unit> {val context = completion.context// label == 0 when coroutine is not started yet (initially) or label == 1 when it wasreturn if (context === EmptyCoroutineContext)object : RestrictedContinuationImpl(completion as Continuation<Any?>) {......}elseobject : ContinuationImpl(completion as Continuation<Any?>, context) {private var label = 0override fun invokeSuspend(result: Result<Any?>): Any? =when (label) {0 -> {label = 1result.getOrThrow() // Rethrow exception if trying to start with exception (will be caught by BaseContinuationImpl.resumeWith// block是我们自定义的挂起函数block(this) // run the block, may return or suspend}1 -> {label = 2result.getOrThrow() // this is the result if the block had suspended}// 上面将label设置为1,此处直接抛Error。表示此协程只能在此执行一次;多次执行此处会报错else -> error("This coroutine had already completed")}}
}

此方法最后会创建一个匿名ContinuationImpl对象,将我们传入的自定义挂起函数封装到一个协程对象中。

2.2.2、intercepted方法
public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =(this as? ContinuationImpl)?.intercepted() ?: thisinternal abstract class ContinuationImpl(completion: Continuation<Any?>?,private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {// 注5public fun intercepted(): Continuation<Any?> =intercepted?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this).also { intercepted = it }}public abstract class CoroutineDispatcher :AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {// 返回一个包裹原协程的协程对象,用于拦截原协程的resume调用public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =DispatchedContinuation(this, continuation)}

此方法会查找协程的上下文context,从中读取实现了ContinuationInterceptor接口的对象。从上面看到,CoroutineDispatcher实现了ContinuationInterceptor接口,所以注5处返回的是抽象类CoroutineDispatcher的一个子类对象,而这子类对象就是协程中的分发器Dispatcher。比较常见的分发器对应的类如下表格:

协程中的分发器对应类(CoroutineDispatcher的子类)
Dispatchers.DefaultDefaultScheduler
Dispatchers.MainMainCoroutineDispatcher
Dispatchers.IODefaultIoScheduler

在注5处得到CoroutineDispatcher对象后,会调用interceptContinuation方法创建一个DispatchedContinuation对象,此对象会在协程resume之前,把协程分发到对应的Dispatcher中去恢复执行。具体实现方式在下一小节介绍。

2.2.3、resumeCancellableWith方法
internal class DispatchedContinuation<in T>(@JvmField internal val dispatcher: CoroutineDispatcher,@JvmField val continuation: Continuation<T>
) : DispatchedTask<T>(MODE_UNINITIALIZED), CoroutineStackFrame, Continuation<T> by continuation {override fun resumeWith(result: Result<T>) {val state = result.toState()if (dispatcher.safeIsDispatchNeeded(context)) {_state = stateresumeMode = MODE_ATOMICdispatcher.safeDispatch(context, this)} else {executeUnconfined(state, MODE_ATOMIC) {withCoroutineContext(context, countOrElement) {continuation.resumeWith(result)}}}}// We inline it to save an entry on the stack in cases where it shows (unconfined dispatcher)// It is used only in Continuation<T>.resumeCancellableWith@Suppress("NOTHING_TO_INLINE")internal inline fun resumeCancellableWith(result: Result<T>) {val state = result.toState()// 先检查是否可以在dispatcher上执行当前协程。if (dispatcher.safeIsDispatchNeeded(context)) {_state = stateresumeMode = MODE_CANCELLABLE1)、检查通过,放到dispatcher执行dispatcher.safeDispatch(context, this)} else {2)、检查不通过,直接在当前线程执行executeUnconfined(state, MODE_CANCELLABLE) {if (!resumeCancelled(state)) {resumeUndispatchedWith(result)}}}}}

在协程执行前,会调用safeIsDispatchNeeded方法,检查协程是否可以在dispatcher上执行。此方法默认值会true,所以一般情况会分发到dispatcher上执行。dispatcher的类型是CoroutineDispatcher,来源于context[ContinuationInterceptor],大多数情况下,是Dispatchers.Default、Dispatchers.Main、Dispatchers.IO这3个对象中的一个。

在调用dispatcher的safeDispatch方法时,它的第二个参数是一个Runnable对象(亦是当前对象this),表示安排这个Runnable对象在分发器上执行。这个Runnable接口是由父类DispatchedTask实现的,如下:

internal abstract class DispatchedTask<in T> internal constructor(@JvmField var resumeMode: Int
) : SchedulerTask() {// 在分发器上执行此run方法final override fun run() {assert { resumeMode != MODE_UNINITIALIZED } // should have been set before dispatchingtry {val delegate = delegate as DispatchedContinuation<T>// 获取当前的协程对象val continuation = delegate.continuationwithContinuationContext(continuation, delegate.countOrElement) {val context = continuation.contextval state = takeState() // NOTE: Must take state in any case, even if cancelledval exception = getExceptionalResult(state)/** Check whether continuation was originally resumed with an exception.* If so, it dominates cancellation, otherwise the original exception* will be silently lost.*/val job = if (exception == null && resumeMode.isCancellableMode) context[Job] else nullif (job != null && !job.isActive) {val cause = job.getCancellationException()cancelCompletedResult(state, cause)// 恢复协程执行continuation.resumeWithStackTrace(cause)} else {// 让挂起过的协程,恢复执行if (exception != null) {continuation.resumeWithException(exception)} else {continuation.resume(getSuccessfulResult(state))}}}} catch (e: DispatchException) {handleCoroutineException(delegate.context, e.cause)} catch (e: Throwable) {handleFatalException(e)}}
}

综合上面代码可以看出,在调用safeDispatch方法之后,会把协程安排到对应的分发器上恢复执行,即执行一个含有协程对象的Runnable对象,然后在执行Runnable对象时,调用协程对象的resumeWithException方法,此方法最终会调用invokeSuspend方法,而是invokeSuspend方法是在编译时生成的(见注3处),其内部会调用我们自定义的挂起函数。ps: 在2.2.1节中也有一个invokeSuspend方法,但是这个invokeSuspend方法只会在协程启动时执行,而非协程resume恢复时执行,且在协程整个生命周期只会执行1次,多了就会报错。从resumeWithException方法调用invokeSuspend方法的具体流程如下:

public inline fun <T> Continuation<T>.resume(value: T): Unit =resumeWith(Result.success(value))public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =resumeWith(Result.failure(exception))internal abstract class BaseContinuationImpl(// completion是外层的协程对象,见2.1节反编译代码,// 在startTest方法调用test1方法时,会给test1传入一个外部协程对象。public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {public final override fun resumeWith(result: Result<Any?>) {// This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resumevar current = thisvar param = result// 从内层到外层遍历协程,然后resume协程while (true) {probeCoroutineResumed(current)// 在便利协程对象,通过with函数,来更新协程对象,// 从而可以调用外层协程对象的invokeSuspendwith(current) {val completion = completion!! // fail fast when trying to resume continuation without completionval outcome: Result<Any?> =try {// invokeSuspend的实现详见 注3 ,其内部会调用我们自定义的挂起方法val outcome = invokeSuspend(param)// 如果遇到挂起函数,则return 挂起if (outcome === COROUTINE_SUSPENDED) returnResult.success(outcome)} catch (exception: Throwable) {Result.failure(exception)}releaseIntercepted() // this state machine instance is terminating// 检查是否还有外层协程,如果有,则重置current和param,继续恢复外层协程if (completion is BaseContinuationImpl) {// unrolling recursion via loopcurrent = completionparam = outcome} else {// top-level completion reached -- invoke and returncompletion.resumeWith(outcome)return}}}}// 1)、编译时实现,如 注3;// 2)、协程首次启动时实现(不会在resume时执行,只在首次启动执行)如 2.2.1protected abstract fun invokeSuspend(result: Result<Any?>): Any?
}

上面给出了协程在resume时会执行的操作。首先、发生挂起协程会执行resume方法,在此方法内部调用invokeSuspend方法,继续执行之前的协程逻辑。然后、在执行完内部协程逻辑后(假设不再发生挂起),会获取外层的协程,继续去执行外层协程的逻辑,直到全部协程执行结束,或有协程再次发生挂起。

从以上可以看出:

1)、针对“挂起”操作,当存在挂起函数嵌套调用时(相当于有多个协程体互相嵌套),内部的挂起函数发生挂起后,外部的挂起函数也会暂停执行。因为当内部的挂起函数挂起后,即返回“挂起标识常量”后,此常量会被一层一层的传递到最外层挂起函数,每一层挂起函数检测到此常量后,都会return,终止当前的执行。

2)、针对“恢复”操作,当挂起函数被恢复时,会从内到外依次遍历外层的协程体对象(每一个协程体对象代表一个外层嵌套的挂起函数),然后调用协程体对象的invokeSuspend方法,以继续执行外层挂起函数。

2.3、Dispatcher----分发器的实现

在前面几节,介绍了协程如何挂起和协程的恢复操作。这个恢复操作是在一个Runnable对象(即DispatchedTask)中去发起的。那么,接下来将介绍,这个Runnable对象是如何被调用的。Kotlin的分发器虽然分为Default、Main、IO,但可以依据他们执行所在的线程分成2类:主线程(Main)、子线程(Default、Main)。

2.3.1、Main 分发器的实现

在kotlin中,Main分发器的定义如下:

public actual object Dispatchers {public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
}internal object MainDispatcherLoader {private val FAST_SERVICE_LOADER_ENABLED = systemProp(FAST_SERVICE_LOADER_PROPERTY_NAME, true)@JvmFieldval dispatcher: MainCoroutineDispatcher = loadMainDispatcher()private fun loadMainDispatcher(): MainCoroutineDispatcher {return try {// FAST_SERVICE_LOADER_ENABLED的定义在上面,默认值是trueval factories = if (FAST_SERVICE_LOADER_ENABLED) {// 返回AndroidDispatcherFactoryFastServiceLoader.loadMainDispatcherFactory()} else {// We are explicitly using the// `ServiceLoader.load(MyClass::class.java, MyClass::class.java.classLoader).iterator()`// form of the ServiceLoader call to enable R8 optimization when compiled on Android.ServiceLoader.load(MainDispatcherFactory::class.java,MainDispatcherFactory::class.java.classLoader).iterator().asSequence().toList()}@Suppress("ConstantConditionIf")factories.maxByOrNull { it.loadPriority }?.tryCreateDispatcher(factories)?: createMissingDispatcher()} catch (e: Throwable) {// Service loader can throw an exception as wellcreateMissingDispatcher(e)}}
}

从上面代码可以看出,Main分发器的类型是MainCoroutineDispatcher,是由FastServiceLoader.loadMainDispatcherFactory()方法,返回的AndroidDispatcherFactory对象,通过调用tryCreateDispatcher方法创建。具体代码如下:

public fun MainDispatcherFactory.tryCreateDispatcher(factories: List<MainDispatcherFactory>): MainCoroutineDispatcher =try {createDispatcher(factories)} catch (cause: Throwable) {createMissingDispatcher(cause, hintOnError())}internal class AndroidDispatcherFactory : MainDispatcherFactory {override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher {// 创建Main Dispatcher,并把主线程的Looper作为参数传入。val mainLooper = Looper.getMainLooper() ?: throw IllegalStateException("The main looper is not available")return HandlerContext(mainLooper.asHandler(async = true))}override fun hintOnError(): String = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used"override val loadPriority: Intget() = Int.MAX_VALUE / 2
}

从上面代码看出,最终Main分发器的实现类是HandlerContext,其代码如下:

internal class HandlerContext private constructor(private val handler: Handler,private val name: String?,private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {constructor(handler: Handler,name: String? = null) : this(handler, name, false)override fun isDispatchNeeded(context: CoroutineContext): Boolean {return !invokeImmediately || Looper.myLooper() != handler.looper}override fun dispatch(context: CoroutineContext, block: Runnable) {// 执行DispatchedTaskif (!handler.post(block)) {cancelOnRejection(context, block)}}
}

在上面的dispatch方法,会把DispatchedTask抛到handler中去执行,而这个handler又是在主线程执行的,基于此Main分发器就实现了把协程体抛到主线程执行。

2.3.2、Default、IO分发器的实现

Default和IO分发器都是在子线程执行的,且IO分发器也是基于Default分发器下的类实现的,只是参数不同,所以在此只分析Default的分发器的实现。

在kotlin中,Default分发器的定义如下:

public actual object Dispatchers {@JvmStaticpublic actual val Default: CoroutineDispatcher = DefaultScheduler
}internal object DefaultScheduler : SchedulerCoroutineDispatcher(CORE_POOL_SIZE, MAX_POOL_SIZE,IDLE_WORKER_KEEP_ALIVE_NS, DEFAULT_SCHEDULER_NAME
) {......override fun toString(): String = "Dispatchers.Default"
}internal open class SchedulerCoroutineDispatcher(private val corePoolSize: Int = CORE_POOL_SIZE,private val maxPoolSize: Int = MAX_POOL_SIZE,private val idleWorkerKeepAliveNs: Long = IDLE_WORKER_KEEP_ALIVE_NS,private val schedulerName: String = "CoroutineScheduler",
) : ExecutorCoroutineDispatcher() {private var coroutineScheduler = createScheduler()private fun createScheduler() =CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName)override fun dispatch(context: CoroutineContext, block: Runnable): Unit = coroutineScheduler.dispatch(block)
}

从上面的代码可知,Default分发器最终会由SchedulerCoroutineDispatcher对象来实现,其内部会创建一个CoroutineScheduler对象,用于执行Runnable对象,通过调用dispatch方法去实现,其代码如下:

fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, fair: Boolean = false) {trackTask() // 将Runnable对象封装成一个task(TaskImpl类型)val task = createTask(block, taskContext)// 因为构造task的taskContext是NonBlockingContext,所以这里是falseval isBlockingTask = task.isBlocking// Invariant: we increment counter **before** publishing the task// so executing thread can safely decrement the number of blocking tasksval stateSnapshot = if (isBlockingTask) incrementBlockingTasks() else 0// try to submit the task to the local queue and act depending on the result// 获取一个当前正在执行的worker,这个worker继承了Thread,是一个正在执行的线程val currentWorker = currentWorker()// 把Runnable对象添加到worker的待执行队列中。// 若未添加成功,会把task原封返回val notAdded = currentWorker.submitToLocalQueue(task, fair)if (notAdded != null) {// 注6 把task添加到一个全局待执行队列if (!addToGlobalQueue(notAdded)) {// Global queue is closed in the last step of close/shutdown -- no more tasks should be acceptedthrow RejectedExecutionException("$schedulerName was terminated")}}// Checking 'task' instead of 'notAdded' is completely okayif (isBlockingTask) {// Use state snapshot to better estimate the number of running threadssignalBlockingWork(stateSnapshot)} else {// 执行了 注6 处的代码才会到这里// (若没有则先创建)启动一个Worker去拉取全局待执行队列中的task 去执行signalCpuWork()}}

dispatch方法执行Runnable的流程为:

首先,将Runnable封装成一个Task对象,给这个Runnable对象扩展一些执行的参数,要不要阻塞之类的。

然后,尝试获取一个Worker,Woker继承了Thread,并在内部维护了一个task队列。会在内部启动一个循环去读取task队列,并执行task(即Runnable)。当所有task都执行完毕时,会阻塞自己(通过LockSupport.parkNanos方法)或直接结束自己。当有task添加进来时,会通过LockSupport.unpark方法,唤醒一个正在阻塞的Worker。Worker的代码如下:

internal inner class Worker private constructor() : Thread() {val localQueue: WorkQueue = WorkQueue()override fun run() = runWorker()private fun runWorker() {var rescanned = falsewhile (!isTerminated && state != WorkerState.TERMINATED) {// 从本地队列(localQueue)或全局队列中读取一个taskval task = findTask(mayHaveLocalTasks)// Task found. Execute and repeatif (task != null) {rescanned = falseminDelayUntilStealableTaskNs = 0L// 执行此task后,继续读取执行下个taskexecuteTask(task)continue} else {mayHaveLocalTasks = false}......// 没有可用task了,阻塞当前worker,即阻塞当前线程tryPark()}tryReleaseCpu(WorkerState.TERMINATED)}
}

最后,如果找不到正在执行的Worker,则先会调用addToGlobalQueue方法,将task添加到全局队列中。然后,调用signalCpuWork方法尝试唤醒一个worker或创建一个worker(如果当前一个worker都没有)。代码如下

// 添加task到全局队列中
private fun addToGlobalQueue(task: Task): Boolean {return if (task.isBlocking) {globalBlockingQueue.addLast(task)} else {globalCpuQueue.addLast(task)}}fun signalCpuWork() {// 先尝试唤醒一个阻塞的Worker(通过LockSupport.unpark方法)if (tryUnpark()) return// 如果没有worker可用,则创建一个新的workerif (tryCreateWorker()) returntryUnpark()}// 创建并启动一个Worker
private fun createNewWorker(): Int {val worker: Workerreturn synchronized(workers) {// Make sure we're not trying to resurrect terminated schedulerif (isTerminated) return -1val state = controlState.valueval created = createdWorkers(state)val blocking = blockingTasks(state)val cpuWorkers = (created - blocking).coerceAtLeast(0)// Double check for overprovisionif (cpuWorkers >= corePoolSize) return 0if (created >= maxPoolSize) return 0// start & register new worker, commit index only after successful creationval newIndex = createdWorkers + 1require(newIndex > 0 && workers[newIndex] == null)worker = Worker(newIndex)workers.setSynchronized(newIndex, worker)require(newIndex == incrementCreatedWorkers())cpuWorkers + 1}.also { worker.start() } // 启动Worker}

综上所述,Default分发器首先,会将Runnable对象封装成task,并添加到task队列中。然后启动Thread去循环读取task队列中的task,并执行。

三、Delay挂起函数的实现

之前部分分析了1)、协程如何挂起----通过返回挂起标识常量,return方法执行;2)协程恢复的流程----从内层协程到外层协程依次恢复执行;3)、协程是如何执行的----实现了Runnable接口,放到Thread中去执行。但是还缺少一点,那就是从协程挂起如何转为协程恢复执行,即在何时、如何resume协程。下面将对Delay方法进行分析来查看协程是如何恢复的。

因为协程可以在不同的分发器上执行(既可在主线程执行,也可以在子线程执行),所以delay方法的实现也与线程有关。

3.1、Delay函数的定义

在kotlin中,delay函数定义如下:

public interface Delay {......public fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>)public fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle =DefaultDelay.invokeOnTimeout(timeMillis, block, context)
}public suspend fun delay(timeMillis: Long) {if (timeMillis <= 0) return // don't delayreturn suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->// if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.if (timeMillis < Long.MAX_VALUE) {// 注7 先获取协程上下文中的delay对象,然后调用delay对象的scheduleResumeAfterDelay方法cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)}}
}
3.2、主线程的Delay函数实现

在主线程,Delay函数的实现简单明了,如下所示:

internal class HandlerContext private constructor(private val handler: Handler,private val name: String?,private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {// 生成一个Runnable对象,调用协程的resume方法,恢复协程val block = Runnable {with(continuation) { resumeUndispatched(Unit) }}// 将上述Runnable对象延迟执行,实现delay的效果if (handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))) {continuation.invokeOnCancellation { handler.removeCallbacks(block) }} else {cancelOnRejection(continuation.context, block)}}override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {if (handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))) {return DisposableHandle { handler.removeCallbacks(block) }}cancelOnRejection(context, block)return NonDisposableHandle}
}

可以看到,在主线程中,HandlerContext不仅作为Main分发器使用,还实现了Delay接口。所以在注7处返回的就是HandlerContext对象。在调用delay方法,直接往主线程抛一个恢复协程执行的Runnable对象,并让它延迟执行。

3.3、子线程的Delay函数实现

以Default分发器为例,因为DefaultScheduler并没有实现delay接口,所以在Default分发器中使用delay方法时,在注7处返回的Delay对象为DefaultExecutor,如下所示:

internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultDelayinternal actual val DefaultDelay: Delay = initializeDefaultDelay()private val defaultMainDelayOptIn = systemProp("kotlinx.coroutines.main.delay", false)
private fun initializeDefaultDelay(): Delay {// 默认值为false,返回DefaultExecutor// Opt-out flagif (!defaultMainDelayOptIn) return DefaultExecutorval main = Dispatchers.Main/** When we already are working with UI and Main threads, it makes* no sense to create a separate thread with timer that cannot be controller* by the UI runtime.*/return if (main.isMissing() || main !is Delay) DefaultExecutor else main
}

在Default分发器中,会使用DefaultExecutor作为Delay接口的实现类,其代码如下:

internal actual object DefaultExecutor : EventLoopImplBase(), Runnable {private var _thread: Thread? = null// 创建一个新的Thread对象,以执行run方法,因为run方法中有循环,可能会发生阻塞override val thread: Threadget() = _thread ?: createThreadSync()private val _delayed = atomic<DelayedTaskQueue?>(null)override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {val timeNanos = delayToNanos(timeMillis)if (timeNanos < MAX_DELAY_NS) {val now = nanoTime()// 封装成DelayedResumeTask对象,// 此task对象有协程需要恢复执行的时间,即now + timeNanosDelayedResumeTask(now + timeNanos, continuation).also { task ->// 把task添加到_delayed队列中schedule(now, task)continuation.disposeOnCancellation(task)}}}override fun run() {ThreadLocalEventLoop.setEventLoop(this)registerTimeLoopThread()try {var shutdownNanos = Long.MAX_VALUEif (!notifyStartup()) returnwhile (true) {Thread.interrupted() // just reset interruption flag// 读取下个task,检查此task需要何时执行,如需立刻执行则执行并返回0// 如不需立刻执行,则会返回下个task的执行时间点与现在时刻的差值parkNanos,// 并在之后让线程阻塞parkNanos时长。var parkNanos = processNextEvent()if (parkNanos == Long.MAX_VALUE) {// nothing to do, initialize shutdown timeoutval now = nanoTime()if (shutdownNanos == Long.MAX_VALUE) shutdownNanos = now + KEEP_ALIVE_NANOSval tillShutdown = shutdownNanos - nowif (tillShutdown <= 0) return // shut thread downparkNanos = parkNanos.coerceAtMost(tillShutdown)} elseshutdownNanos = Long.MAX_VALUEif (parkNanos > 0) {// check if shutdown was requested and bail out in this caseif (isShutdownRequested) return// 当前没有task需要立刻执行,需要让当前线程阻塞parkNanos时长parkNanos(this, parkNanos)}}} finally {_thread = null // this thread is deadacknowledgeShutdownIfNeeded()unregisterTimeLoopThread()// recheck if queues are empty after _thread reference was set to null (!!!)if (!isEmpty) thread // recreate thread if it is needed}}override fun processNextEvent(): Long {// unconfined events take priorityif (processUnconfinedEvent()) return 0// queue all delayed tasks that are due to be executedenqueueDelayedTasks()// then process one event from queue// 读取一个需要立刻执行的taskval task = dequeue()if (task != null) {// 执行此taskplatformAutoreleasePool { task.run() }return 0}return nextTime}private fun createThreadSync(): Thread {return _thread ?: Thread(this, THREAD_NAME).apply {_thread = thiscontextClassLoader = this@DefaultExecutor.javaClass.classLoaderisDaemon = truestart()}}
}

上面代码表明,在Default分发器(子线程)执行delay操作,会先把恢复协程操作封装一个task;然后,把这个task加入到task队列中;接着,在一个新的线程中重复读取task队列,若task的时间点到了,则立刻执行,即立刻调用协程的resume方法。若task的时间点未到,则计算这个时间点与现在的时长,当线程阻塞这个时长,然后在去重复检查task。

四、总结

1)、协程的挂起是通过return 挂起函数执行实现;2)、在协程恢复时,会从内层协程到外层协程依次恢复执行。3)、协程代码依旧是执行在线程上;4)、delay挂起函数在恢复协程时,是把协程resume操作封装到了Runnable对象,然后延迟执行此Runnable对象实现的。

协程UML图

参考

1、协程源码github: https://github.com/Kotlin/kotlinx.coroutines.git 

2、kotlin源码 github: GitHub - JetBrains/kotlin: The Kotlin Programming Language.

3、delay方法解析:Kotlin delay方法解析_kotlin none of the following candidates is applica-CSDN博客

相关文章:

Kotlin协程解析

目录 一、协程的使用 二、协程的执行原理 2.1、挂起函数的反编译代码及执行分析 2.2、协程执行流程分析 2.2.1、createCoroutineUnintercepted方法 2.2.2、intercepted方法 2.2.3、resumeCancellableWith方法 2.3、Dispatcher----分发器的实现 2.3.1、Main 分发器的实…...

Nginx核心功能 02

目录 Nginx代理技术核心概念 &#xff08;一&#xff09;正向代理&#xff08;Forward Proxy&#xff09; 1. 基本定义 2. 技术原理 3. 应用场景 &#xff08;二&#xff09;反向代理&#xff08;Reverse Proxy&#xff09; 1. 基本定义 2. 技术原理 3. 应用场景 一、…...

聊聊对Mysql的理解

目录 1、Sql介绍 1.1、SQL的分类 1.2、数据库的三大范式 1.3、数据表的约束 1.4、约束的添加与删除 2、核心特性 3、主要组件 4、数据结构原理 5、索引失效 6、常用问题 7、优势与局限 前言 MySQL是一个开源的关系型数据库管理系统(RDBMS)&#xff0c;由瑞典MySQL A…...

「Mac畅玩AIGC与多模态17」开发篇13 - 条件判断与分支跳转工作流示例

一、概述 本篇在多节点串联的基础上,进一步引入条件判断与分支跳转机制,实现根据用户输入内容动态走不同执行路径。开发人员将学习如何配置判断节点、定义分支规则,以及如何在工作流中引导执行方向,完成基础的逻辑控制。 二、环境准备 macOS 系统Dify 平台已部署并可访问…...

pycharm terminal 窗口打不开了

参考添加链接描述powershell.exe改为cmd.exe发现有一个小正方形&#xff0c;最大化可以看见了。...

JAVA:使用 MapStruct 实现高效对象映射的技术指南

1、简述 在 Java 开发中,对象之间的转换是一个常见的需求,尤其是在 DTO(数据传输对象)和实体类之间的转换过程中。手动编写转换代码既耗时又容易出错,而 MapStruct 是一个优秀的对象映射框架,可以通过注解生成高效的对象转换代码,从而大大提升开发效率。 本文将介绍 M…...

Linux线程深度解析:从基础到实践

Linux线程深度解析&#xff1a;从基础到实践 一、线程基础概念 1. 进程与线程定义 进程&#xff1a;一个正在运行的程序&#xff0c;是操作系统资源分配的最小单位&#xff08;拥有独立的地址空间、文件描述符等资源&#xff09;&#xff0c;状态包括就绪、运行、阻塞。线程…...

【ROS2】launch启动文件如何集成到ROS2(Python版本)

一、简单实操 1.创建/打开一个功能包 mkdir -p my_ws/src cd my_ws/src ros2 pkg create my_pkg_example --build-type ament_python 2.创建Launch文件的存放目录 将所有启动文件都存储在launch包内的目录中。 目录结构如下所示&#xff1a; src/my_pkg_example/launch/…...

用 PyTorch 轻松实现 MNIST 手写数字识别

用 PyTorch 轻松实现 MNIST 手写数字识别 引言 在深度学习领域&#xff0c;MNIST 数据集就像是 “Hello World” 级别的经典入门项目。它包含大量手写数字图像及对应标签&#xff0c;非常适合新手学习如何搭建和训练神经网络模型。本文将基于 PyTorch 框架&#xff0c;详细拆…...

碰撞检测学习笔记

目录 SUMO 模拟碰撞 LimSim pygame模拟碰撞检测 SUMO 模拟碰撞 LimSim 多模态大语言模型&#xff08;M&#xff09;LLM的出现为人工智能开辟了新的途径&#xff0c;特别是提供增强的理解和推理能力&#xff0c;为自动驾驶开辟了新途径。本文介绍LimSim&#xff0c;LimSim的…...

Sway初体验

Sway&#xff08;缩写自 SirCmpwn’s Wayland compositor[1]&#xff09;是一款专为 Wayland 设计的合成器&#xff0c;旨在与 i3 完全兼容。根据官网所述&#xff1a; Sway 是 Wayland 的合成器&#xff0c;也是 x11 的 i3 窗口管理器的替代品。它可以根据您现有的 i3 配置工作…...

《工业社会的诞生》章节

工业革命的技术前奏 早期工业技术双引擎&#xff1a; 【火药武器】&#xff1a;重塑战争形态与经济地理 新式青铜炮助力殖民扩张&#xff0c;开辟全球贸易网络 高桅帆船&#xff08;西班牙大帆船&#xff09;实现洲际航行 战争规模化倒逼中央集权&#xff0c;催生国家-商人…...

消息队列MQ

参考资料&#xff1a;https://cloud.tencent.com/developer/article/2335397 https://www.cnblogs.com/hahaha111122222/p/18457859 消息队列是大型分布式系统不可缺少的中间件&#xff0c;也是高并发系统的基石中间件 消息队列 消息队列 Message Queue 消息队列是利用高效可…...

LangChain4J-XiaozhiAI 项目分析报告

LangChain4J-XiaozhiAI 项目分析报告 GitHub 链接 1. 项目概述 本项目名为 “硅谷小智&#xff08;医疗版&#xff09;”&#xff0c;是一个基于 Java 技术栈和 LangChain4J 框架构建的 AI 聊天助手应用。其核心目标是利用大型语言模型&#xff08;LLM&#xff09;的能力&am…...

学习spring boot-拦截器Interceptor,过滤器Filter

目录 拦截器Interceptor 过滤器Filter 关于过滤器的前置知识可以参考&#xff1a; 过滤器在springboot项目的应用 一&#xff0c;使用WebfilterServletComponentScan 注解 1 创建过滤器类实现Filter接口 2 在启动类中添加 ServletComponentScan 注解 二&#xff0c;创建…...

【程序+论文】大规模新能源并网下的火电机组深度调峰经济调度

目录 1 主要内容 讲解重点 2 讲解视频及代码 1 主要内容 该视频为《大规模新能源并网下的火电机组深度调峰经济调度》代码讲解内容&#xff0c;该程序有完全对照的论文&#xff0c;以改进IEEE30节点作为研究对象&#xff0c;系统包括5个火电机组和2个新能源机组&#xff0c;…...

【win11 】win11 键盘测试

我的键盘是支持mac和win的&#xff0c;fn tab 就能切换&#xff0c;有可能是用错了模式&#xff0c;导致 我alt a 就会弹出 win11的 wifi 等菜单控制 键盘测试网站 https://keyboard.bmcx.com/ 识别到我按下的是alt...

再识动静态库

动静态库 1 手动制作静态库2 手动调用静态库方式一&#xff1a;&#xff08;安装到系统&#xff09;方式二&#xff1a;&#xff08;和源文件一起&#xff09;方式三&#xff1a;&#xff08;使用带路径的库&#xff09; 3 动态库制作与使用方式一&#xff1a;拷贝到系统方式二…...

前端 uni-app 初步使用指南

在数字化浪潮下&#xff0c;实现应用多端适配成为开发者的刚需。uni-app 凭借 “一次编写&#xff0c;多端运行” 的特性&#xff0c;极大提升了开发效率&#xff0c;成为前端开发的热门选择。如果你是首次接触 uni-app&#xff0c;这篇文章将带你开启 uni-app 的使用之旅&…...

尼卡音乐 1.1.1 | 免费畅听全网音乐,支持无损下载,无广告无需注册登录

尼卡音乐是一款可以免费畅听全网音乐的应用程序&#xff0c;支持免费下载无损高品质音源&#xff0c;并且没有任何广告&#xff0c;无需注册登录。用户可以轻松搜索全网无损音质音源&#xff0c;并可将其他音乐APP的歌单导入&#xff0c;让音乐陪你开心一整天。该应用彻底拒绝臃…...

33.降速提高EMC能力

降速提高EMC能力 1. 电磁兼容问题的错误累积效应2. 降速减少累积效应的机理分析 1. 电磁兼容问题的错误累积效应 2. 降速减少累积效应的机理分析 降速之后&#xff0c;信号的波形更完整&#xff0c;容错空间更大&#xff1b;另外边沿变缓&#xff0c;对外干扰也会减小。...

【赵渝强老师】TiDB的MVCC机制

TiDB是一款开源的国产分布式关系型数据库。TiKV是TiDB的行存引擎&#xff0c;它支持多版本并发控制(Multi-Version Concurrency Control,MVCC)。假设有这样一种场景&#xff1a;某客户端A在写一个Key&#xff0c;另一个客户端B同时在对这个Key进行读操作。如果没有数据的多版本…...

数电填空题整理(适用期末考试)

在下列门电路中&#xff0c;OC门能实现“线与”逻辑功能&#xff1b; 三态门能用于总线结构的数 据传输&#xff1b;传输门 能实现模拟信号的双向传输。 并联比较型A/D转换器的转换速度最快&#xff0c; 双积分型A/D转换器的稳定性和抗干扰能力最好 TTL与非门多余的输入端应该…...

node核心学习

目录 1-1node概述 1-2全局对象 1-3Node的模块化细节 1-4Node中的ES模块化 1-5基本内置模块 OS模块&#xff1a; path模块&#xff1a; url模块&#xff1a; util模块&#xff1a; 1-6文件IO I/O&#xff1a;input output fs模块的方法 代码示例&#xff1a; 练习…...

基于 PyQt 的YOLO目标检测可视化界面+ nuitka 打包

在人工智能和计算机视觉领域&#xff0c;YOLO&#xff08;You Only Look Once&#xff09;是一种广泛使用的实时目标检测算法。为了直观地展示YOLO算法的检测效果&#xff0c;我们使用Pyqt框架进行检测结果的可视化&#xff0c;同时为了使其能够脱离Python环境&#xff0c;我们…...

234树和红黑树

首先&#xff0c;把目光聚集在234树中 以下是234的三种节点&#xff08;可以有更多这里使用以下的三个&#xff09;&#xff1a; 右侧是节点转换成红黑树节点的样子。 接下来会用以下序列进行1234树的搭建和红黑树的搭建&#xff1a; 首先是234树 2-3-4树&#xff08;234树&…...

GenCLS++:通过联合优化SFT和RL,提升生成式大模型的分类效果

摘要&#xff1a;作为机器学习中的一个基础任务&#xff0c;文本分类在许多领域都发挥着至关重要的作用。随着大型语言模型&#xff08;LLMs&#xff09;的快速扩展&#xff0c;特别是通过强化学习&#xff08;RL&#xff09;的推动&#xff0c;对于更强大的分类器的需求也在不…...

maven坐标导入jar包时剔除不需要的内容

maven坐标导入jar包时剔除不需要的内容 问题描述解决方案 问题描述 maven坐标导入jar包时剔除不需要的内容 解决方案 Spring Boot 默认使用 Logback&#xff0c;需在 pom.xml 中排除其依赖&#xff1a; <dependency><groupId>org.springframework.boot</gro…...

Oracle OCP认证考试考点详解083系列06

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 26. 第26题&#xff1a; 题目 解析及答案&#xff1a; 关于块介质恢复&#xff0c;以下哪三项是正确的&#xff1f; A) 需恢复一个或多个…...

llfc项目分布式服务笔记

一、系统整体架构流程图(简明版) 复制代码 +---------------+ +------------------+ +----------------+ | 客户端 (Client) |--------->| GateServer |----------| StatusServer |<--+ +---------------+ +--------------…...

“链式前向星”等三种存图方式分别输出“无向无权图”的“DFS序列”

【DFS序列】 DFS序列&#xff08;深度优先搜索序列&#xff09;&#xff0c;是树或图结构在深度优先遍历过程中生成的节点访问顺序记录。 下面三段代码&#xff0c;分别采用链式前向星、邻接表、邻接矩阵存图&#xff0c;输出图的“DFS序列”。 【DFS&#xff1a;链式前向星】…...

Lesson 16 A polite request

Lesson 16 A polite request 词汇 park n. 公园&#xff0c;停车场&#xff0c;庄园 v. 停车&#xff0c;泊车 例句&#xff1a;让我来停车。    Let me park. 相关&#xff1a;spot n. 车位 区别&#xff1a;garden n. 花园 [小&#xff0c;私家的] 例句&#xff1a;我们…...

【IP101】边缘检测技术全解析:从Sobel到Canny的进阶之路

&#x1f31f; 边缘检测的艺术 &#x1f3a8; 在图像处理的世界里&#xff0c;边缘检测就像是给图像画眉毛 —— 没有它&#xff0c;你的图像就像一只没有轮廓的熊猫&#x1f43c;。让我们一起来探索这个神奇的"美妆"技术&#xff01; &#x1f4da; 目录 基础概念 …...

Nx 智能分发机制(Nx Agents + Nx Cloud)

Nx 智能分发机制&#xff08;Nx Agents  Nx Cloud&#xff09; 阶段关键做的事作用1. 收集信息- Project Graph&#xff1a;解析整个 workspace 依赖关系&#xff08;谁依赖谁&#xff09;- 历史统计&#xff1a;每次 CI 结束后将每个任务的实际用时与缓存命中情况上传…...

《“昊龙一号”:开启中国航天货运新时代》

中国航天新力量:昊龙一号登场 在 2024 年 10 月 29 日上午,神舟十九号载人飞行任务新闻发布会如一颗重磅炸弹,在航天领域激起千层浪。发布会上,一系列关乎中国载人航天工程未来走向的重要信息被披露,其中,“昊龙一号” 货运航天飞机入围空间站低成本货物运输系统总体方案…...

C++ 多态:原理、实现与应用

目录 引言 一、多态的概念 二、多态的定义及实现 &#xff08;一&#xff09;构成条件 &#xff08;二&#xff09;虚函数的深入理解 &#xff08;三&#xff09;虚函数的重写&#xff08;覆盖&#xff09; 三、抽象类 &#xff08;一&#xff09;概念 &#xff08;二&…...

多模态大语言模型arxiv论文略读(五十八)

How Does the Textual Information Affect the Retrieval of Multimodal In-Context Learning? ➡️ 论文标题&#xff1a;How Does the Textual Information Affect the Retrieval of Multimodal In-Context Learning? ➡️ 论文作者&#xff1a;Yang Luo, Zangwei Zheng, …...

TS 枚举类型

枚举 参数为枚举成员中的一个 数字枚举 字符串枚举 枚举特点 、 缺点&#xff1a;转为JS代码时会编译成JS代码&#xff0c;增大开销...

Python容器与循环:数据处理的双剑合璧

Python作为一门简洁强大的编程语言&#xff0c;其容器类型和循环结构的完美结合为数据处理提供了极大的便利。本文将带领初学者深入理解Python中的四大容器&#xff08;列表、元组、字典、集合&#xff09;以及它们与循环结构的配合使用&#xff0c;助你掌握数据处理的核心技能…...

ST-LINKV2仿真器下载

ST-LINKV2仿真器 — 正点原子资料下载中心 1.0.0 文档...

RAGFlow 接入企业微信应用实现原理剖析与最佳实践

背景 近期有医美行业客户咨询我们智能客服产品&#xff0c;期望将自己企业的产品、服务以及报价信息以企微应用的方式给到客户进行体验互动&#xff0c;提升企业运营效率。关于企业微信对接&#xff0c;我们分享下最佳实践&#xff0c;抛砖引玉。效果图如下&#xff1a; 这里也…...

大模型实践:图文解锁Ollama在个人笔记本上部署llm

使用在线模型服务时&#xff0c;我们常常需要支付API调用费用&#xff0c;这对于个人开发者或小型组织来说可能是一笔不小的开支。那么&#xff0c;有没有方法可以在本地免费使用这些强大的模型呢&#xff1f;答案是肯定的——Ollama就是这样一个工具。 当然如果是比较大的组织…...

如何提高情商?(优化版)

引言 提高情商&#xff08;EQ&#xff09;是一个需要长期练习和自我反思的过程&#xff0c;核心在于理解自己、管理情绪、共情他人并有效沟通。以下是一些具体且可操作的方法&#xff0c;结合理论和实际场景&#xff0c;帮助你逐步提升&#xff1a; 一、核心方法&#xff1a;…...

学习黑客Linux权限

在 Linux 的王国里&#xff0c;“权限”就是装备与技能加成&#xff1a;决定谁能拔剑&#xff08;读 r&#xff09;、挥剑&#xff08;写 w&#xff09;、进入房间&#xff08;执行 x&#xff09;。本文用“闯关升级”视角&#xff0c;把常见 rwx、八进制数字、SUID/SGID/Stick…...

信息系统监理师第二版教材模拟题第二组(含解析)

信息系统监理师模拟题第二组(30题) 监理理论与法规 根据《信息系统工程监理暂行规定》,监理单位应当独立于( ) A. 建设单位和承建单位 B. 政府监管部门 C. 行业组织 D. 最终用户答案:A 解析:监理单位应当保持独立性,不得与建设单位和承建单位有隶属关系或其他利害关系…...

C与指针——输入输出

错误定位 当一个库函数出错时&#xff0c;errno会被重置 perror(const char* s);\\输出s: errno 对应的错误信息 \\如果单独想要错误信息可以 char* e strerror(errno);\\系统错误码转换为对应的错误信息字符串输出缓冲区 一般输出缓冲区满的时候才刷新&#xff0c;也就是…...

RR(Repeatable Read)级别如何防止幻读

在 MySQL 数据库事务隔离级别中&#xff0c;RR&#xff08;可重复读&#xff09; 通过 MVCC&#xff08;多版本并发控制&#xff09; 和 锁机制 的组合策略来避免幻读问题。 一、MVCC机制&#xff1a;快照读与版本控制 快照读&#xff08;Snapshot Read&#xff09; 每个事务启…...

Python之学习笔记(六)

文章目录 1. 字典&#xff08;Dictionary&#xff09;2. 集合&#xff08;Set&#xff09;3. 字典 vs 集合4. 应用场景5. 注意事项 Python中的字典&#xff08; dict&#xff09;和集合&#xff08; set&#xff09;是两种高效且常用的数据结构&#xff0c;适用于不同的场景。…...

Easy云盘总结篇-文件上传02

说在前面&#xff1a;此项目是跟着B站一位大佬写的&#xff0c;不分享源码&#xff0c;支持项目付费 文件预览 主要分视频和其他文件预览&#xff0c;但实现逻辑相同&#xff0c;只是请求路径有区别。 这段逻辑&#xff1a; 拿视频预览举例&#xff1a; 视频开始时&#xff…...

window-docker的容器使用宿主机音频设备

文章目录 前言操作配置 前言 你有没有遇到过这种情况&#xff1f; 你兴冲冲地在Windows上用Docker搭了个语音识别项目&#xff0c;准备让容器高歌一曲&#xff0c;或者至少"Hey Docker"一下。结果——静音。 Docker Desktop一脸无辜&#xff1a;“亲&#xff0c;默…...