Android IO 框架 Okio 的实现原理,如何检测超时?
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问。
前言
大家好,我是小彭。
在上一篇文章里,我们聊到了 Square 开源的 I/O 框架 Okio 的三个优势:精简且全面的 API、基于共享的缓冲区设计以及超时机制。前两个优势已经分析过了,今天我们来分析 Okio 的超时检测机制。
本文源码基于 Okio v3.2.0。
思维导图:
1. 认识 Okio 的超时机制
超时机制是一项通用的系统设计,能够避免系统长时间阻塞在某些任务上。例如网络请求在超时时间内没有响应,客户端就会提前中断请求,并提示用户某些功能不可用。
1.1 说一下 Okio 超时机制的优势
先思考一个问题,相比于传统 IO 的超时有什么优势呢?我认为主要体现在 2 个方面:
- 优势 1 - Okio 弥补了部分 IO 操作不支持超时检测的缺陷:
Java 原生 IO 操作是否支持超时,完全取决于底层的系统调用是否支持。例如,网络 Socket 支持通过 setSoTimeout
API 设置单次 IO 操作的超时时间,而文件 IO 操作就不支持,使用原生文件 IO 就无法实现超时。
而 Okio 是统一在应用层实现超时检测,不管系统调用是否支持超时,都能提供统一的超时检测机制。
- 优势 2 - Okio 不仅支持单次 IO 操作的超时检测,还支持包含多次 IO 操作的复合任务超时检测:
Java 原生 IO 操作只能实现对单次 IO 操作的超时检测,无法实现对包含多次 IO 操作的复合任务超时检测。例如,OkHttp 支持配置单次 connect、read 或 write 操作的超时检测,还支持对一次完整 Call 请求的超时检测,有时候单个操作没有超时,但串联起来的完整 call 却超时了。
而 Okio 超时机制和 IO 操作没有强耦合,不仅支持对 IO 操作的超时检测,还支持非 IO 操作的超时检测,所以这种复合任务的超时检测也是可以实现的。
1.2 Timeout 类的作用
Timeout 类是 Okio 超时机制的核心类,Okio 对 Source 输入流和 Sink 输出流都提供了超时机制,我们在构造 InputStreamSource 和 OutputStreamSink 这些流的实现类时,都需要携带 Timeout 对象:
Source.kt
interface Source : Closeable {// 返回超时控制对象fun timeout(): Timeout...
}
Sink.kt
actual interface Sink : Closeable, Flushable {// 返回超时控制对象actual fun timeout(): Timeout...
}
Timeout 类提供了两种配置超时时间的方式(如果两种方式同时存在的话,Timeout 会优先采用更早的截止时间):
- 1、timeoutNanos 任务处理时间: 设置处理单次任务的超时时间,
最终触发超时的截止时间是任务的 startTime + timeoutNanos
;
- 2、deadlineNanoTime 截止时间: 直接设置未来的某个时间点,多个任务整体的超时时间点。
Timeout.kt
// hasDeadline 这个属性显得没必要
private var hasDeadline = false // 是否设置了截止时间点
private var deadlineNanoTime = 0L // 截止时间点(单位纳秒)
private var timeoutNanos = 0L // 处理单次任务的超时时间(单位纳秒)
创建 Source 和 Sink 对象时,都需要携带 Timeout 对象:
JvmOkio.kt
// ----------------------------------------------------------------------------
// 输入流
// ----------------------------------------------------------------------------fun InputStream.source(): Source = InputStreamSource(this, Timeout() /*Timeout 对象*/)// 文件输入流
fun File.source(): Source = InputStreamSource(inputStream(), Timeout.NONE)// Socket 输入流
fun Socket.source(): Source {val timeout = SocketAsyncTimeout(this)val source = InputStreamSource(getInputStream(), timeout /*携带 Timeout 对象*/)// 包装为异步超时return timeout.source(source)
}// ----------------------------------------------------------------------------
// 输出流
// ----------------------------------------------------------------------------fun OutputStream.sink(): Sink = OutputStreamSink(this, Timeout() /*Timeout 对象*/)// 文件输出流
fun File.sink(append: Boolean = false): Sink = FileOutputStream(this, append).sink()// Socket 输出流
fun Socket.sink(): Sink {val timeout = SocketAsyncTimeout(this)val sink = OutputStreamSink(getOutputStream(), timeout /*携带 Timeout 对象*/)// 包装为异步超时return timeout.sink(sink)
}
在 Timeout 类的基础上,Okio 提供了 2 种超时机制:
- Timeout 是同步超时
- AsyncTimeout 是异步超时
Okio 框架
2. Timeout 同步超时
Timeout 同步超时依赖于 Timeout#throwIfReached() 方法。
同步超时在每次执行任务之前,都需要先调用 Timeout#throwIfReached()
检查当前时间是否到达超时截止时间。如果超时则会直接抛出超时异常,不会再执行任务。
JvmOkio.kt
private class InputStreamSource(// 输入流private val input: InputStream,// 超时控制private val timeout: Timeout
) : Source {override fun read(sink: Buffer, byteCount: Long): Long {// 1、参数校验if (byteCount == 0L) return 0require(byteCount >= 0) { "byteCount < 0: $byteCount" }// 2、检查超时时间timeout.throwIfReached()// 3、执行输入任务(已简化)val bytesRead = input.read(...)return bytesRead.toLong()}...
}private class OutputStreamSink(// 输出流private val out: OutputStream,// 超时控制private val timeout: Timeout
) : Sink {override fun write(source: Buffer, byteCount: Long) {// 1、参数校验checkOffsetAndCount(source.size, 0, byteCount)// 2、检查超时时间timeout.throwIfReached()// 3、执行输入任务(已简化)out.write(...)...}...
}
看一眼 Timeout#throwIfReached 的源码。 可以看到,同步超时只考虑 “deadlineNanoTime 截止时间”,如果只设置 “timeoutNanos 任务处理时间” 是无效的,我觉得这个设计容易让开发者出错。
Timeout.kt
@Throws(IOException::class)
open fun throwIfReached() {if (Thread.interrupted()) {// 传递中断状态Thread.currentThread().interrupt() // Retain interrupted status.throw InterruptedIOException("interrupted")}if (hasDeadline && deadlineNanoTime - System.nanoTime() <= 0) {// 抛出超时异常throw InterruptedIOException("deadline reached")}
}
有必要解释所谓 “同步” 的意思:
同步超时就是指任务的 “执行” 和 “超时检查” 是同步的。当任务超时时,Okio 同步超时不会直接中断任务执行,而是需要检主动查超时时间(Timeout#throwIfReached)来判断是否发生超时,再决定是否中断任务执行。
这其实与 Java 的中断机制是非常相似的:
当 Java 线程的中断标记位置位时,并不是真的会直接中断线程执行,而是主动需要检查中断标记位(Thread.interrupted)来判断是否发生中断,再决定是否中断线程任务。所以说 Java 的线程中断机制是一种 “同步中断”。
可以看出,同步超时存在 “滞后性”:
因为同步超时需要主动检查,所以即使在任务执行过程中发生超时,也必须等到检查时才会发现超时,无法及时触发超时异常。因此,就需要异步超时机制。
同步超时示意图
3. AsyncTimeout 异步超时
-
异步超时监控进入: 异步超时在每次执行任务之前,都需要先调用
AsyncTimeout#enter()
方法将 AsyncTimeout 挂载到超时队列中,并根据超时截止时间的先后顺序排序,队列头部的节点就是会最先超时的任务; -
异步超时监控退出: 在每次任务执行结束之后,都需要再调用
AsyncTimeout#exit()
方法将 AsyncTimeout 从超时队列中移除。
注意: enter() 方法和 eixt() 方法必须成对存在。
AsyncTimeout.kt
open class AsyncTimeout : Timeout() {// 是否在等待队列中private var inQueue = false// 后续指针private var next: AsyncTimeout? = null// 超时截止时间private var timeoutAt = 0L// 异步超时监控进入fun enter() {check(!inQueue) { "Unbalanced enter/exit" }val timeoutNanos = timeoutNanos()val hasDeadline = hasDeadline()if (timeoutNanos == 0L && !hasDeadline) {return}inQueue = truescheduleTimeout(this, timeoutNanos, hasDeadline)}// 异步超时监控退出// 返回值:是否发生超时(如果节点不存在,说明被 WatchDog 线程移除,即发生超时)fun exit(): Boolean {if (!inQueue) return falseinQueue = falsereturn cancelScheduledTimeout(this)}// 在 WatchDog 线程调用protected open fun timedOut() {}companion object {// 超时队列头节点(哨兵节点)private var head: AsyncTimeout? = null// 分发超时监控任务private fun scheduleTimeout(node: AsyncTimeout, timeoutNanos: Long, hasDeadline: Boolean) {synchronized(AsyncTimeout::class.java) {// 首次添加监控时,需要启动 Watchdog 线程if (head == null) {// 哨兵节点head = AsyncTimeout()Watchdog().start()}// now:当前时间val now = System.nanoTime()// timeoutAt 超时截止时间:计算 now + timeoutNanos 和 deadlineNanoTime 的较小值if (timeoutNanos != 0L && hasDeadline) {node.timeoutAt = now + minOf(timeoutNanos, node.deadlineNanoTime() - now)} else if (timeoutNanos != 0L) {node.timeoutAt = now + timeoutNanos} else if (hasDeadline) {node.timeoutAt = node.deadlineNanoTime()} else {throw AssertionError()}// remainingNanos 超时剩余时间:当前时间距离超时发生的时间val remainingNanos = node.remainingNanos(now)var prev = head!!// 线性遍历超时队列,按照超时截止时间将 node 节点插入超时队列while (true) {if (prev.next == null || remainingNanos < prev.next!!.remainingNanos(now)) {node.next = prev.nextprev.next = node// 如果插入到队列头部,需要唤醒 WatchDog 线程if (prev === head) {(AsyncTimeout::class.java as Object).notify()}break}prev = prev.next!!}}}// 取消超时监控任务// 返回值:是否超时private fun cancelScheduledTimeout(node: AsyncTimeout): Boolean {synchronized(AsyncTimeout::class.java) {// 线性遍历超时队列,将 node 节点移除var prev = headwhile (prev != null) {if (prev.next === node) {prev.next = node.nextnode.next = nullreturn false}prev = prev.next}// 如果节点不存在,说明被 WatchDog 线程移除,即发生超时return true}}}
}
同时,在首次添加异步超时监控时,AsyncTimeout 内部会开启一个 WatchDog
守护线程,按照 “检测 - 等待” 模型观察超时队列的头节点:
-
如果发生超时,则将头节点移除,并回调
AsyncTimeout#timeOut()
方法。这是一个空方法,需要由子类实现来主动取消任务; -
如果未发生超时,则 WatchDog 线程会计算距离超时发生的时间间隔,调用
Object#wait(时间间隔)
进入限时等待。
需要注意的是: AsyncTimeout#timeOut() 回调中不能执行耗时操作,否则会影响后续检测的及时性。
有意思的是:我们会发现 Okio 的超时检测机制和 Android ANR 的超时检测机制非常类似,所以我们可以说 ANR 也是一种异步超时机制。
AsyncTimeout.kt
private class Watchdog internal constructor() : Thread("Okio Watchdog") {init {// 守护线程isDaemon = true}override fun run() {// 死循环while (true) {try {var timedOut: AsyncTimeout? = nullsynchronized(AsyncTimeout::class.java) {// 取头节点(Maybe wait)timedOut = awaitTimeout()// 超时队列为空,退出线程if (timedOut === head) {head = nullreturn}}// 超时发生,触发 AsyncTimeout#timedOut 回调timedOut?.timedOut()} catch (ignored: InterruptedException) {}}}
}companion object {// 超时队列为空时,再等待一轮的时间private val IDLE_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60)private val IDLE_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(IDLE_TIMEOUT_MILLIS)@Throws(InterruptedException::class)internal fun awaitTimeout(): AsyncTimeout? {// Get the next eligible node.val node = head!!.next// 如果超时队列为空if (node == null) {// 需要再等待 60s 后再判断(例如在首次添加监控时)val startNanos = System.nanoTime()(AsyncTimeout::class.java as Object).wait(IDLE_TIMEOUT_MILLIS)return if (head!!.next == null && System.nanoTime() - startNanos >= IDLE_TIMEOUT_NANOS) {// 退出 WatchDog 线程head} else {// WatchDog 线程重新取一次null}}// 计算当前时间距离超时发生的时间var waitNanos = node.remainingNanos(System.nanoTime())// 未超时,进入限时等待if (waitNanos > 0) {// Waiting is made complicated by the fact that we work in nanoseconds,// but the API wants (millis, nanos) in two arguments.val waitMillis = waitNanos / 1000000LwaitNanos -= waitMillis * 1000000L(AsyncTimeout::class.java as Object).wait(waitMillis, waitNanos.toInt())return null}// 超时,将头节点移除head!!.next = node.nextnode.next = nullreturn node}
}
异步超时示意图
直接看代码不好理解,我们来举个例子:
4. 举例:OkHttp Call 的异步超时监控
在 OkHttp 中,支持配置一次完整的 Call 请求上的操作时间 callTimeout。一次 Call 请求包含多个 IO 操作的复合任务,使用传统 IO 是不可能监控超时的,所以需要使用 AsyncTimeout 异步超时。
在 OkHttp 的 RealCall 请求类中,就使用了 AsyncTimeout 异步超时:
-
1、开始任务: 在 execute() 方法中,调用
AsyncTimeout#enter()
进入异步超时监控,再执行请求; -
2、结束任务: 在 callDone() 方法中,调用
AsyncTimeout#exit()
退出异步超时监控。分析源码发现:callDone() 不仅在请求正常时会调用,在取消请求时也会回调,保证了 enter() 和 exit() 成对存在; -
3、超时回调: 在
AsyncTimeout#timeOut
超时回调中,调用了 Call#cancel() 提前取消请求。Call#cancel() 会调用到 Socket#close(),让阻塞中的 IO 操作抛出 SocketException 异常,以达到提前中断的目的,最终也会走到 callDone() 执行 exit() 退出异步监控。
Call 超时监控示意图
RealCall
class RealCall(val client: OkHttpClient,/** The application's original request unadulterated by redirects or auth headers. */val originalRequest: Request,val forWebSocket: Boolean
) : Call {// 3、AsyncTimeout 超时监控private val timeout = object : AsyncTimeout() {override fun timedOut() {// 取消请求cancel()}}.apply {timeout(client.callTimeoutMillis.toLong(), MILLISECONDS)}// 取消请求override fun cancel() {if (canceled) return // Already canceled.canceled = trueexchange?.cancel()// 最终会调用 Socket#close()connectionToCancel?.cancel()eventListener.canceled(this)}// 1、请求开始(由业务层调用)override fun execute(): Response {// 1.1 异步超时监控进入timeout.enter()// 1.2 执行请求client.dispatcher.executed(this)return getResponseWithInterceptorChain()}// 2、请求结束(由 OkHttp 引擎层调用,包含正常和异常情况)// 除了 IO 操作在抛出异常后会走到 callDone(),在取消请求时也会走到 callDone()internal fun <E : IOException?> messageDone(exchange: Exchange,requestDone: Boolean, // 请求正常结束responseDone: Boolean, // 响应正常结束e: E): E {...if (callDone) {return callDone(e)}return e}private fun <E : IOException?> callDone(e: E): E {...// 检查是否超时val result = timeoutExit(e)if (e != null) {// 请求异常(包含超时异常)eventListener.callFailed(this, result!!)} else {// 请求正常结束eventListener.callEnd(this)}return result}private fun <E : IOException?> timeoutExit(cause: E): E {if (timeoutEarlyExit) return cause// 2.1 异步超时监控退出if (!timeout.exit()) return cause// 2.2 包装超时异常val e = InterruptedIOException("timeout")if (cause != null) e.initCause(cause)return e as E}
}
调用 Socket#close() 会让阻塞中的 IO 操作抛出 SocketException 异常:
Socket.java
// Any thread currently blocked in an I/O operation upon this socket will throw a {@link SocketException}.
public synchronized void close() throws IOException {synchronized(closeLock) {if (isClosed())return;if (created)impl.close();closed = true;}
}
Exchange 中会捕获 Socket#close() 抛出的 SocketException 异常:
Exchange.kt
private inner class RequestBodySink(delegate: Sink,/** The exact number of bytes to be written, or -1L if that is unknown. */private val contentLength: Long
) : ForwardingSink(delegate) {@Throws(IOException::class)override fun write(source: Buffer, byteCount: Long) {...try {super.write(source, byteCount)this.bytesReceived += byteCount} catch (e: IOException) {// Socket#close() 会抛出异常,被这里拦截throw complete(e)}}private fun <E : IOException?> complete(e: E): E {if (completed) return ecompleted = truereturn bodyComplete(bytesReceived, responseDone = false, requestDone = true, e = e)}
}fun <E : IOException?> bodyComplete(bytesRead: Long,responseDone: Boolean,requestDone: Boolean,e: E
): E {...// 回调到上面的 RealCall#messageDonereturn call.messageDone(this, requestDone, responseDone, e)
}
5. OkHttp 超时检测总结
先说一下 Okhttp 定义的 2 种颗粒度的超时:
- 第 1 种是在单次 connect、read 或 write 操作上的超时;
- 第 2 种是在一次完整的 call 请求上的超时,有时候单个操作没有超时,但连接起来的完整 call 却超时。
其实 Socket 支持通过 setSoTimeout
API 设置单次操作的超时时间,但这个 API 无法满足需求,比如说 Call 超时是包含多个 IO 操作的复合任务,而且不管是 HTTP/1 并行请求还是 HTTP/2 多路复用,都会存在一个 Socket 连接上同时承载多个请求的情况,无法区分是哪个请求超时。
因此,OkHttp 采用了两种超时监测:
- 对于 connect 操作,OkHttp 继续使用 Socket 级别的超时,没有问题;
- 对于 call、read 和 write 的超时,OkHttp 使用一个 Okio 的异步超时机制来监测超时。
参考资料
- Github · Okio
- Okio 官网
相关文章:

Android IO 框架 Okio 的实现原理,如何检测超时?
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问。 前言 大家好,我是小彭。 在上一篇文章里,我们聊到了 Square 开源的 I/O 框架 Okio 的三个优势:精简且全面的 API、基于共享的缓冲区设计以…...
简单介绍反射
1.定义Java的反射机制是在运行状态中,对于任意一个类,都知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意方法和属性,既然能拿到,我们就可以修改部分类型信息;这种动态获取信息的…...

PyTorch学习笔记:nn.MSELoss——MSE损失
PyTorch学习笔记:nn.MSELoss——MSE损失 torch.nn.MSELoss(size_average None,reduce None,reduction mean)功能:创建一个平方误差(MSE)损失函数,又称为L2损失: l(x,y)L{l1,…,lN}T,ln(xn−yn)2l(x,y)L…...

apache和nginx的TLS1.0和TLS1.1禁用处理方案
1、TLS1.0和TLS1.1是什么? TLS协议其实就是网络安全传输层协议,用于在两个通信应用程序之间提供保密性和数据完整性,TLS 1. 0 和TLS 1. 1 是分别是96 年和 06 年发布的老版协议。 2、为什么要禁用TLS1.0和TLS1.1传输协议 TLS1.0和TLS1.1协…...

K_A12_002 基于STM32等单片机采集光敏电阻传感器参数串口与OLED0.96双显示
K_A12_002 基于STM32等单片机采集光敏电阻传感器参数串口与OLED0.96双显示一、资源说明二、基本参数参数引脚说明三、驱动说明IIC地址/采集通道选择/时序对应程序:四、部分代码说明1、接线引脚定义1.1、STC89C52RC光敏电阻传感器模块1.2、STM32F103C8T6光敏电阻传感器模块五、基…...

《机器学习》学习笔记
第 2 章 模型评估与选择 2.1 经验误差与过拟合 精度:精度1-错误率。如果在 mmm 个样本中有 aaa 个样本分类错误,则错误率 Ea/mEa/mEa/m,精度 1−a/m1-a/m1−a/m。误差:一般我们把学习器的实际预测输出与样本的真实输出之间的差…...

前端卷算法系列(一)
前端卷算法系列(一) 两数之和 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同…...

【机器学习】聚类算法(理论)
聚类算法(理论) 目录一、概论1、聚类算法的分类2、欧氏空间的引入二、K-Means算法1、算法思路2、算法总结三、DBSCAN算法1、相关概念2、算法思路3、算法总结四、实战部分一、概论 聚类分析,即聚类(Clustering)…...

Docker-用Jenkins发版Java项目-(1)Docke安装Jenkins
文章目录前言环境背景操作流程docker安装及jenkins软件安装jenkins配置登录配置安装插件及创建账号前言 学海无涯,旅“途”漫漫,“途”中小记,如有错误,敬请指出,在此拜谢! 最近新购得了M2的MAC,…...

java集合框架内容整理
主要内容集合框架体系ArrayListLinkedListHashSetTreeSetLinkedHashSet内部比较器和外部比较器哈希表的原理List集合List集合的主要实现类有ArrayList和LinkedList,分别是数据结构中顺序表和链表的实现。另外还包括栈和队列的实现类:Deque和Queue。• Li…...

win10系统安装Nginx
Nginx是一款自由的、开源的、高性能的HTTP服务器和反向代理服务器,同时也提供了IMAP/POP3/SMTP服务。 Nginx可以进行反向代理、负载均衡、HTTP服务器(动静分离)、正向代理等操作。因为最近在公司使用到了Nginx 第一步:下载Nginx …...

数据库学习笔记(2)——workbench和SQL语言
1、workbench简介: 登录客户端的两种方法 在cmd中,只能通过sql语句控制数据库;workbench其实就是一种图形化数据库管理工具,在workbench中既可以通过sql语句控制数据库,也可以通过图形化界面控制数据库。通过workbenc…...
测量学期末考试之名词解释总结
仅供自己参考,且范围不全面.大地水准面与处于静止平衡状态的平均海水面重合,并延伸通过陆地的水准面高程地面点到大地水准面的铅锤距离水准面处于静止状态的水面就是水准面高差两点的水准面之间的铅锤距离垂直角在铅锤面上,瞄准目标的倾斜视线…...

TDengine时序数据库的简单使用
最近学习了TDengine数据库,因为我们公司有硬件设备,设备按照每分钟,每十分钟,每小时上传数据,存入数据库。而这些数据会经过sql查询,统计返回展示到前端。但时间积累后现在数据达到了百万级数据,…...
记录每日LeetCode 2335.装满被子需要的最短总时长 Java实现
题目描述: 现有一台饮水机,可以制备冷水、温水和热水。每秒钟,可以装满 2 杯 不同 类型的水或者 1 杯任意类型的水。 给你一个下标从 0 开始、长度为 3 的整数数组 amount ,其中 amount[0]、amount[1] 和 amount[2] 分别表示需要…...

了解线程池newFixedTheadPool
什么是线程池 操作系统 能够进行运算 调度 的最小单位。线程池是一种多线程处理形式。 为什么引入线程池的概念 解决处理短时间任务时创建和销毁线程代价较大的弊端,可以使用线程池技术。 复用 饭店只有一个服务员和饭店有10个服务员 线程池的种类 newFixedThea…...
IP分片和TCP分段解析--之IP分片
本文目录什么是IP分片为什么会产生IP分片为什么要避免IP分片如何避免IP分片什么是IP分片 IP协议栈将TCP/UDP传输层要求它发送的,但长度大于发送端口MTU的一个数据包,分割成多个IP报文后分多次发送。这些分成多次发送的多个IP报文就是IP分片。 为什么会…...
物联网方向常见通信方式有哪些?
常用的有线通信方式有串口、以太网等。 1、串口 串口通信普及率高、成本低,但是组网能力差,只适合低速率和小数据量的通信 2、以太网接口(网线) 以太网(Ethernet)是目前最普遍的一种局域网 通信技术,它规定了包括 物理层的连线、电子信号和介质访问层协议的内容。 以太…...

windows wireshark抓到未加入组的组播消息
现象 在Windows上开启wireshark,抓到了大量地址为239.255.255.251的组播包。 同时,根据组播相关命令,调用netsh interface ipv4 show joins,显示当前并没加入 239.255.255.251 组播组。 解决 根据IGMP Snooping,I…...
【PTA Advanced】1156 Sexy Primes(C++)
目录 题目 Input Specification: Output Specification: Sample Input 1: Sample Output 1: Sample Input 2: Sample Output 2: 思路 代码 题目 Sexy primes are pairs of primes of the form (p, p6), so-named since "sex" is the Latin word for "…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...

听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...