Kotlin进阶之协程从入门到放弃

公众号「稀有猿诉」        原文链接 Kotlin进阶之协程从入门到放弃

协程Coroutine是最新式的并发编程范式,它是纯编程语言层面的东西,不受制于操作系统,轻量级,易于控制,结构严谨,不易出错,易于测试,工具和配套设施都比较完备。在新生代编程语言(如Kotlin和Swift)中支持良好,在Kotlin中有着非常友好的支持,并且是写异步和并发程序的推荐方式。为了彻底学会使用协程和理解协程背后的原理,计划用三篇文章专注来学习协程。

  • 第一篇:主要介绍协程的基本概念,以及如何使用协程,目标就是讲清基本概念,并快速上手。
  • 第二篇:协程的高级用法,如结构化协程,Scope,Context,Exception handling,在框架中使用(如在Compose和Jetpack中),与Flow一起使用。目标就是进一步发挥协程的威力,写出专业健壮的协程代码 。
  • 第三篇:理解协程的核心原理,以及协程的实现机制,以及在其他编程语言中的支持情况。目标是深刻理解协程的原理的实现机制,做到心中无剑,以及尝试在不支持协程的语言中实现协程

注意:在任何一个编程语言中异步和并发编程总是略微复杂的话题,Kotlin中的协程也不例外,因此需要先有一定的前置知识,也就是说要大概弄懂操作系统中的进程与线程, 以及要有一些Java中的线程和并发编程经验,否则是没有办法很好理解和使用Kotlin协程的。

Hello, coroutines

每当学习一门新的技术,最喜欢的方式就是快速的上手,比如先弄个『Hello, world!』之类的,而不是上来就讲什么概念,原理,范式和方法论。编程是门实践性很强的学科,要快速上手快速体验,当有了一定的感觉之后,再去研究它的概念和原理。

我们也要从一个『Hello, coroutines!』开始我们的Kotlin协程之旅。

fun main() = runBlocking {
    launch {
        delay(1000)
        println(", coroutines!")
    }
    print("Hello")
}
// Hello, coroutines!

以常规的方式来思考,写在前面的语句会先执行,写在后面的语句会后执行,这就是同步的意思,似乎应该输出:

, coroutines!
Hello

但我们得到了期望的输出『Hello, coroutines!』,这就是协程的作用,它可以实现异步。这里launch是一个函数,后面的lambda是它的参数,它的作用就是启动一个协程来运行传入的代码块。这个代码块很简单,它先delay了1秒,然后再输出语句。因为启动了协程,并且协程里的代码等了1秒再执行余下的语句,因此,主函数中的输出语句先执行了,这样就得到了我们期望的输出顺序。

配置协程运行环境

注意,注意,协程并不是Kotlin标准库的一部分,它属于官方扩展库的一部分,有自己单独的版本号,要想使用协程还需要单独配置依赖。协程模块的名字是kotlinx.coroutines,有自已独立的版本号,需要注意的是,要注意Kotlin版本与协程版本之间的匹配关系,协程库对它所支持的Kotlin有最低版本要求。目前协程库最新版本是1.8.0-RC2,它对应的Kotlin版本是1.9.21。

配置协程库依赖:

Maven
<dependency>
    <groupId>org.jetbrains.kotlinx</groupId>
    <artifactId>kotlinx-coroutines-core</artifactId>
    <version>1.8.0-RC2</version>
</dependency>

<properties>
    <kotlin.version>1.9.21</kotlin.version>
</properties>
Gradle
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0-RC2")
}

plugins {
    // For build.gradle.kts (Kotlin DSL)
    kotlin("jvm") version "1.9.21"

    // For build.gradle (Groovy DSL)
    id "org.jetbrains.kotlin.jvm" version "1.9.21"
}

repositories {
    mavenCentral()
}
Android
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0-RC2")

协程是啥

那么协程是啥呢?协程就是一个子例程,或者说一个函数,与常规的函数其实也没啥区别,只不过它可以异步地执行,可以挂起,当然不同的协程也可以并行的执行(这就是并发了)。协程是没有阻塞的,协程只会挂起,一旦协程挂起,就交出CPU的控制权,就可以去执行其他协程了。协程是一种轻量级的线程,但它并不是线程,跟线程也没有直接关系,当然它跟其他函数一样,也是要运行在某一个线程里面的。

在Kotlin中协程的关键字是suspend,它用以修饰一个函数,suspend函数只能被另一个suspend函数调用,或者运行在一个协程内。另外就是delay函数了,它是将协程挂起一定时间。launch/async函数则是创建并启动一个协程,await函数是等待一个协程执行结束并返回结果。runBlocking函数则是创建一个可以使用协程的作用域,叫作CoroutineScope,协程只能在协程作用域内启动,作用域的目的就是为了管理在其内启动的协程。不理解或者记不住这些关键字和函数也没有关系,这里只需要先有一个印象就够了。

动动手,折腾一下

对于我们的『Hello, coroutines!』程序,可以尝试进行一些修改,比如改一下delay的值,去掉runBlocking,或者去掉launch看看会发生什么!

创建协程

在继续之前,我们把之前的代码重构一下,把协程代码块抽象成一个函数:

fun main() = runBlocking { // this: CoroutineScope
    launch { doWorld() }
    println("Hello")
}
// Hello, coroutines!

// this is your first suspending function
suspend fun doWorld() {
    delay(1000L)
    println(", coroutines!!")
}

功能没变仍是输出『Hello, coroutines!』只不过代码块变成了一个suspend函数,被suspend修饰的函数只能运行在协程之中,或者被另一个suspend函数调用,当然 最终仍是要运行在某一个协程之中的。

创建协程的函数是launch()和async(),它们都是函数,参数都是一个代码块,它们的作用是创建一个协程并让代码块参数运行在此协程内。把上面的launch换成async得到的结果是一模一样的:

fun main() = runBlocking { // this: CoroutineScope
    async { doWorld() }
    println("Hello")
}
// Hello, coroutines!

当然了,它们之间肯定是区别的,要不然何必费事弄两个函数呢,我们后面再讲它们的具体区别。

到现在我们知道了如何创建协程了,但如我们手动把runBlocking删除掉,就会有编译错误,说launch/async找不到,那是因为这两个函数是扩展函数,它们是CoroutineScope类的扩展函数。前面说了,所有的协程必须运行在一个CoroutineScope内,前面的runBlocking函数的作用就是创建一个CoroutineScope,下面我们重点来看看啥是CoroutineScope。

协程作用域

作用域(CoroutineScope)是用于管理协程的,所有的协程必须运行在某个作用域内,这样通过作用域就可以更好的管理协程,比如控制它们的生命周期。这里面的概念就是结构化并发(structured concurrency),也就是让所有的协程以一种结构化的方式来组织和管理,以让整体的并发更为有秩序和可控。

这与人类社会是类似的,比如军队,要把士兵编为不同的组织结构(如团,旅,师,军,集团军),目的就是增强整体的执行效率,进而增强战斗力,试想一个军队,如果没有组织结构,那就会是一盘散沙,战斗力可想而知。

如何创建作用域

有很多构造器方法可以用于创建作用域,基本上不会直接创建作用域对象。最常见的就是用coroutineScope函数,它的作用是创建一个CoroutineScope,执行里面的协程,并等待所有的协程执行完毕后再退出(返回),我们可以继续改造我们的例子,自己为我们的协程创建一个作用域:

fun main() = runBlocking {
    doWorld()
}

suspend fun doWorld() = coroutineScope {  // this: CoroutineScope
    launch {
        delay(1000L)
        println(", coroutines!!")
    }
    println("Hello")
}

还有一些其他的作用域生成方法如runBlocking和GlobalScope,GlobalScope是一个全局的作用域,也就是Kotlin提供的一个在整个Kotlin中都可以直接使用的协程作用域,显然,我们不应该使用它,因为作用域的目的在于组织和管理协程,如果把所有的协程都放在一个全局作用域下面了,那跟没有使用域也没有啥区别了。就好比一个军队,只有一个将军,下面直辖一万个士兵,这跟没有将军是没有分别的。

至于runBlocking,它是创建一个作用域,执行其里面创建的协程,等待所有协程执行完毕后退出,但它还有一个重要的功能就是,在等待协程执行的过程中它会阻塞线程,以保证调用者的线程一定比协程晚些退出。因此,只应该在一个地方使用runBlocking,那就是在主函数中使用,其他地方都不应该使用它。

虽然说协程必须运行在某一个CoroutineScope中,但是不是说在每个要创建协程的地方都使用coroutineScope创建一个新的作用域呢?这显然是滥用了。作用域的目的在于组织和管理协程,因此作用域应该符合架构设计的原则,比如为一个模块或者同一类功能创建一个作用域,以方便管理其内部分的协程。并且CoroutineScope是树形结构的,也就是说作用域本身也可以管理其他作用域,这才能形成完整的结构,体现结构化并发的思想。

使用框架中的CoroutineScope

如前所述作用域更多的要从架构角度来考虑。实际上大多数时候,我们并不需要自己创建作用域,因为框架会为我们准备好。就好比Jetpack中的ViewModel,它的作用是把UI操作的逻辑封装起来,那么ViewModel中的所有协程都应该运行在viewModelScope之中,而这是框架已经为我们创建好了的,它会结合系统组件生命周期来管理协程。

运行上下文

协程不是什么神密的东西,也不是什么银弹,它就是一个普通的函数(例程routine),只不过它可以异步执行,也就是说launch了一个协程后,这条语句很快就执行完了,马上去执行launch {…}下面的语句了,协程代码块的执行是在协程里面,它什么时候返回结果是不知道的。也可以挂起,协程挂起后就释放了运行它的线程,并不会阻塞运行它的线程,那么其他协程就有机会运行。

这就涉及另一个重要的东西,就是协程运行的上下文,或者说协程运行的线程环境。协程它就是一个函数,它当然需要运行在某个线程里面。除非特别指定以切换运行的线程,否则所有的协程是运行在主线程中的。

协程的运行环境由CoroutineContext来定义,但其实基本上不会直接创建这个对象,都是通过参数或者其他构建函数来指定协程的运行上下文环境。

创建协程时指定上下文

创建协程的函数launch和async是有多个参数,一共有三个参数,最后一个当然是代码块,前面两个都是有默认值的参数,因此大部分时候可以省略,它们的完整函数签名是:

fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job

fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T>

第一个参数便是指定协程运行的上下文。现在可以为我们的协程加上线程环境了:

fun main() = runBlocking {
    doWorld()
}

suspend fun doWorld() = coroutineScope {  // this: CoroutineScope
    launch(Dispatchers.Default) {
        delay(1000L)
        println(", coroutines!!")
    }
    println("Hello")
}

使用扩展函数withContext

另外一种方式就是使用扩展函数withContext,在其参数指定的上下文环境中调用代码块中的协程,等待其执行完,并返回结果。

suspend fun <T> withContext(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T

上面的例子也可以这样写:

fun main() = runBlocking {
    doWorld()
}

suspend fun doWorld() = coroutineScope {  // this: CoroutineScope
    launch {
        withContext(Dispatchers.Default) {
            delay(1000L)
            println(", coroutines!!")
        }
    }
    println("Hello")
}

但这并不是好的用法,withContext应该用在一些suspend方法中,并且这些方法想自己指定执行环境,并且执行环境对调用方是透明的。比如说,一个负责用户操作的UesrRepository,它只向外部暴露一些suspend方法,在这些suspend方法内部通过withContext来指定它自己运行的上下文环境,从而不用管调用者的执行环境,不也需要调用者知道repo的执行环境:

class UserRepository(
    val dispatcher: Dispatcher = Dispathers.IO
) {
    suspend fun login() {
        withContext(dispatcher) {
            // Do login
        }
    }
}

让每一个架构层次或者模块自己管理好自己运行的上下文,还有一个好处在于,可以方便的通过依赖注入来进行Mock或者测试

使用框架中的上下文环境

虽然我们可以指定协程运行的上下文环境,那是不是意味着要自己创建很多的context呢?非也,非也。框架中也预定义好了很多context,可以直接拿来用,比如Dispatchers.Default,这是Kotlin中的默认线程适合做计算密集类任务;Dispatchers.IO,这适合做IO密集的操作,如文件读写,网络等;Dispatchers.Main,这是Kotlin中的主线程(即main函数运行的线程),UI中的主线程(如Swing和安卓的主线程);等等,当然了,也可以自己创建一个context。

到这里我们可以发现,现代化的并发框架较以前是是非常的完备,从创建,到管理,再到运行环境都考虑的非常全面。比如RxJava或者我们现在正在学习的协程,都是如此。在Java中,其实也有类似的东西,其实就是ExecutorService,它就是异步和并发任务运行的环境。只不过,它的API设计的还是太过原始,你仍然 需要自己去实现一个Executor,并没有像RxJava中的Schedulers以及Kotlin中的Dispatchers一样,有一些功能明确的预定义的对象可以直接使用。

并发性

并发就是代码『同时运行』,当然 有真并发,那就是并行,比如两台电脑同时都在运行不同的或者相同的应用程序,类似于两个人同时都在干活儿,这是并行(真并发);大多数并发都是假的,只不过操作系统以粒度非常小的时间片在不同的代码间来回切换,让人感觉起来好像所有的代码都在同时运行,但真到了CPU的指令周期里面,其实同一个周期只能执行一个命令。当然了,现代处理器都具有多核心,每个核心可以执行一个指令,因此多核心可以真的同时运行多个线程,也可以实现真并发。

并发的前提是要能异步,也就是像我们的launch {…}一样,它很快就执行完了,这样后面可以继续执行,因此,协程是可以实现并发的,也就是让多个协程『同时运行』:

fun main() = runBlocking {
    doWorld()
}

// Concurrently executes both sections
suspend fun doWorld() = coroutineScope { // this: CoroutineScope
    launch {
        delay(2000L)
        println(", coroutine #2, comes later!")
    }
    launch {
        delay(1000L)
        println(", coroutine #1, here I am!")
    }
    print("Hello")
}
//Hello, coroutine #2, here I am!
//, coroutine #1, comes later!

注意,我们这里是假并发,我们没有指定线程,两个协程都是运行在主线程里面的,但它们没有相互影响,更没有阻塞发生,它们确实是『同时运行的』。

当然了,在实际开发过程中呢,肯定还是要指定协程的运行线程,以实现真的并发,原因在于真实的软件代码是比较复杂,主线程,以及每个协程都有大量的代码要执行,都去揩主线程的油,肯定 很快就被榨干了,所以必然要上Dispatchers.IO之类的多线程以实现真正的并发。

可控制性

好的并发框架一定是可控的,也就是说对于异步任务来说要能很好的开启等待终止。Kotlin中的协程是可以做到这一点的。前面说到launch和async都可以创建一个协程,那它俩到底 啥区别?我们从前面它们的函数签名可以看出它俩的返回值是不一样的,launch返回一个Job对象,而async返回一个Deferred对象。

Job对象可以理解为协程的一个句柄,可以用来控制协程,比如终止它(取消它cancel),『同步等待』它执行完(join())。

suspend fun doWorld() = coroutineScope {
    val job = launch {
        delay(2000L)
        println(" and coroutine #2")
    }
    launch {
        delay(1000L)
        println("from coroutine #1 !")
    }
    println("Hello")
    job.join()
    println("All jobs done.")
}

fun main() = runBlocking {
    doWorld()
}

上面的例子输出是符合期望的:

Hello
from coroutine #1 !
 and coroutine #2
All jobs done.

而如果,把 job.join()去掉的话,因为launch {…}创建的协程是异步执行,很快就返回了,最后的语句println(“All jobs done.”)会得到执行,因为协程都有delay,所以『All jobs done.』要先于协程中的语句输出:

Hello
All jobs done?
from coroutine #1 !
 and coroutine #2

而Deferred是Job的一个子类,它特有的功能是取得协程的返回结果,通过其await函数可以『同步的等待』协程结果返回,launch可以通过Job来等待协程执行完成,但是拿不到协程的返回结果,这就是launch与async的最大的区别。

fun main() = runBlocking {
    val one = async { computeOne() }
    val two = async { computeTwo() }

    println(" Finally we got: ${one.await() + two.await()}")
}

private suspend fun computeOne(): Int {
    return withContext(Dispatchers.IO) {
        print("Coroutine #1: Calculating ...")
        delay(2400)
        val res = 12
        println(", got $res")
        return@withContext res
    }
}

private suspend fun computeTwo(): Int {
    return withContext(Dispatchers.IO) {
        print("Coroutine #2: Calculating ...")
        delay(2200)
        val res = 20
        println(", got $res")
        return@withContext res
    }
}
//Coroutine #1: Calculating ...Coroutine #2: Calculating ..., got 20
//, got 12
// Finally we got: 32

注意,注意:前面说Job#join()和Deferred#await()都可以『同步地等待』协程执行完成,但这里的『同步等待』是非阻塞式的,它只是把当前协程挂起,虽然说join和await后面的语句在协程返回前不会得到执行,但这并不是像join/sleep/wait之于Thread那种阻塞式的。协程的join和await只是挂起,把运行环境中的线程释放,在此期间其他协程是可以得到CPU资源(即线程)继续运行的。

总结

本文主要介绍了Kotlin中的协程基本使用方法:在一个协程作用域中,通过launch/async来创建一个协程,通过context来切换协程的运行上下文(线程环境),并可以通过Job/Deferred对象来控制协程。

到此,我们可以总结出协程的一些特点:

  • 轻量级,它是纯编程语言层面的东西,不涉及操作系统支持的进程和线程的创建,因此它占用的资源非常少,是轻量级的异步和并发利器。
  • 非阻塞式,协程最重要的特点是非阻塞,它的等待虽然会让其后面的语句延迟执行,但此时运行的线程已被释放,其他协程可以得到运行。
  • 设施完备,管理协程的作用域,切换运行环境的context,协程的可控,可以非常优雅的实现结构化并发编程,从而减少出错,并且完全可测。

其实,可以看出协程是一种代码执行上的操作框架,它能让代码挂起,交出真实的CPU控制权(可以想像为一个大的switch语句,在不同的函数之间跳转切换)。进程和线程都是操作系统直接支持的,操作硬件资源的方法,一个运行中的线程必须占有一个CPU核心,线程只能被阻塞,无法挂起,因为操作系统切换线程就意味着让CPU去运行另外一个线程,那么前一个线程就进入了阻塞状态(Blocked),等操作系统再切换回这个线程时,它才得以继续运行,从阻塞状态转为运行状态。而协程是纯的编程语言层面实现的东西,视线程为透明,一旦挂起,就可以去执行另一坨代码,它全靠程序员自己来控制,协程,即一起协作的子例程,这也是协程,作为新一代并发编程范式最大的优势。

书籍推荐

《Kotlin编程实战》是推荐的书籍,这本书比较厚实,把Kotlin的每个特性都论述的十分详细。

实践

强烈推荐官方的一个实战教程,非常适合入门,难度也不大,并且有答案,可以一步一步的学会使用协程,并理解它。

参考资料

  • Coroutines guide
  • Coroutines basics
  • Coroutines and channels − tutorial

欢迎搜索并关注 公众号「稀有猿诉」 获取更多的优质文章!

原创不易,「打赏」「点赞」「在看」「收藏」「分享」 总要有一个吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/463632.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入浅出理解 AI 生图模型

目录 引言 一、Stable Diffusion原理 首先 随后 最后 二、DDPM模型 1 资料 2 原理 扩散过程 反向过程 3 公式结论 三、优缺点 优点&#xff1a; 缺点&#xff1a; 四、改进与完事 LDM代表作 原理概括 Latent Space&#xff08;潜空间&#xff09; 五、总结 引…

提高安全投资回报:威胁建模和OPEN FAIR™风险分析

对大多数人和企业来说&#xff0c;安全意味着一种成本。但重要的是如何获得适合的量&#xff0c;而不是越多越好。然而&#xff0c;你如何决定什么时候可以有足够的安全性&#xff0c;以及你如何获得它&#xff1f;则完全是另一回事。 该篇文章是由The Open Group安全论坛主办&…

【PyTorch】进阶学习:一文详细介绍 load_state_dict() 的应用场景、实战代码示例

【PyTorch】进阶学习&#xff1a;一文详细介绍 load_state_dict() 的应用场景、实战代码示例 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入…

vb.net+zxing.net随机彩色二维码、条形码

需要zxing库支持ZXing.NET Generate QR Code & Barcode in C# Alternatives | IronBarcode 效果图&#xff1a; 思路&#xff1a;先生成1个单位的二维码&#xff0c;然后再通过像素填充颜色&#xff0c;颜色数组要通过洗牌算法 洗牌算法 Dim shuffledCards As New List(…

C#控制台贪吃蛇

Console.Write("");// 第一次生成食物位置 // 随机生成一个食物的位置 // 食物生成完成后判断食物生成的位置与现在的蛇的身体或者障碍物有冲突 // 食物的位置与蛇的身体或者障碍物冲突了&#xff0c;那么一直重新生成食物&#xff0c;直到生成不冲突…

GenAI开源公司汇总

主要分类如下&#xff1a; 1. 基础模型&#xff1a;这些是机器学习和AI的核心模型提供商&#xff0c;它们提供基础的算法和技术支持。 2. 模型部署与推断&#xff1a;提供云服务和计算资源&#xff0c;帮助用户部署和运行AI模型。 3. 开发者工具&#xff1a;支持AI/ML的开发…

【网络原理】TCP 协议中比较重要的一些特性(三)

目录 1、拥塞控制 2、延时应答 3、捎带应答 4、面向字节流 5、异常情况处理 5.1、其中一方出现了进程崩溃 5.2、其中一方出现关机&#xff08;正常流程的关机&#xff09; 5.3、其中一方出现断电&#xff08;直接拔电源&#xff0c;也是关机&#xff0c;更突然的关机&am…

拜占庭将军问题相关问题

1、拜占庭将军问题基本描述 问题 当我们讨论区块链共识时&#xff0c;为什么会讨论拜占庭将军问题&#xff1f; 区块链网络的本质是一个分布式系统&#xff0c;在存在恶意节点的情况下&#xff0c;希望 整个系统当中的善良节点能够对于重要的信息达成一致&#xff0c;这个机…

Python语言基础与应用-北京大学-陈斌-P40-39-基本扩展模块/上机练习:计时和文件处理-给算法计时-上机代码

Python语言基础与应用-北京大学-陈斌-P40-39-基本扩展模块/上机练习&#xff1a;计时和文件处理-给算法计时-上机代码 上机代码&#xff1a; # 基本扩展模块训练 给算法计时 def factorial(number): # 自定义一个计算阶乘的函数i 1result 1 # 变量 result 用来存储每个数的阶…

第十三篇:复习Java面向对象

文章目录 一、面向对象的概念二、类和对象1. 如何定义/使用类2. 定义类的补充注意事项 三、面向对象三大特征1. 封装2. 继承2.1 例子2.2 继承类型2.3 继承的特性2.4 继承中的关键字2.4.1 extend2.4.2 implements2.4.3 super/this2.4.4 final 3. 多态4. 抽象类4.1 抽象类4.2 抽象…

微信小程序关闭首页广告

由于之前微信小程序默认开启了首页广告位。导致很多老人误入广告页的内容&#xff0c;所以想着怎么屏蔽广告。好家伙&#xff0c;搜索一圈&#xff0c;要么是用户版本的屏蔽广告&#xff0c;或者是以下一个模棱两可的答案&#xff0c;要开发者设置一下什么参数的&#xff0c;如…

ZK vs FHE

1. 引言 近期ZAMA获得7300万美金的投资&#xff0c;使得FHE获得更多关注。FHE仍处于萌芽阶段&#xff0c;是未来隐私游戏规则的改变者。FHE需与ZK和MPC一起结合&#xff0c;以发挥最大效用。如&#xff1a; Threshold FHE&#xff1a;将FHE与MPC结合&#xff0c;实现信任最小…

Kafka MQ 生产者

Kafka MQ 生产者 生产者概览 尽管生产者 API 使用起来很简单&#xff0c;但消息的发送过程还是有点复杂的。图 3-1 展示了向 Kafka 发送消息的主要步骤。 我们从创建一个 ProducerRecord 对象开始&#xff0c;ProducerRecord 对象需要包含目标主题和要发送的内容。我们还可以…

Python基础(七)之数值类型集合

Python基础&#xff08;七&#xff09;之数值类型集合 1、简介 集合&#xff0c;英文set。 集合&#xff08;set&#xff09;是由一个或多个元素组成&#xff0c;是一个无序且不可重复的序列。 集合&#xff08;set&#xff09;只存储不可变的数据类型&#xff0c;如Number、…

高德 Android 地图SDK 去除logo

问题 高德 Android 地图SDK 去除logo 详细问题 笔者进行Android 项目开发&#xff0c;接入高德地图SDK。但是默认在地图左下角有高德地图logo&#xff0c;现需要去除该logo 期望效果 解决方案 import com.amap.api.maps.UiSettings; UiSettings settingsmMapView.getMap(…

CSS-DAY3

CSS-DAY3 2024/2/7 盒子模型 页面布局要学习三大核心, 盒子模型, 浮动 和 定位. 学习好盒子模型能非常好的帮助我们布局页面 1.1 看透网页布局的本质 网页布局过程&#xff1a; 先准备好相关的网页元素&#xff0c;网页元素基本都是盒子 Box 。利用 CSS 设置好盒子样式&a…

c++之旅第七弹——继承

大家好啊&#xff0c;这里是c之旅第七弹&#xff0c;跟随我的步伐来开始这一篇的学习吧&#xff01; 如果有知识性错误&#xff0c;欢迎各位指正&#xff01;&#xff01;一起加油&#xff01;&#xff01; 创作不易&#xff0c;希望大家多多支持哦&#xff01; 一.继承和派生…

夜间8点到12点能干点啥副业?

们放松和追求个人兴趣的时候&#xff0c;也是一段时间可以用来开展副业的机会。以下是一些适合晚上从事的副业的建议。 1.【千金宝库】软件做任务赚钱 【千金宝库】任务平台是为那些没有资源和人脉的人准备的。它非常适合那些没有时间限制、没有门槛的学生&#xff0c;平时玩…

以太网传输图片工程出现的问题总结(含源码)

本文对以太网传输图片的工程曾经出现过的问题及解决思路进行整理&#xff0c;便于日后出现类似问题能够快速处理。也指出为什么前文在FIFO IP设计时为啥强调深度的重要性。 1、问题 当工程综合完毕之后&#xff0c;下载到板子&#xff0c;连接以太网口&#xff0c;相关硬件如下…

0G联合创始人MICHAEL HEINRICH确认出席Hack.Summit() 2024区块链开发者大会

随着区块链技术的不断发展和应用&#xff0c;全球开发者瞩目的Hack.Summit() 2024区块链开发者大会即将于2024年4月9日至10日在香港数码港盛大举行。此次大会由Hack VC主办&#xff0c;并得到AltLayer和Berachain的协办&#xff0c;同时汇聚了Solana、The Graph、Blockchain Ac…