Kennem's Blog
  • 🏠主页
  • 🔍搜索
  • 📚文章
  • ⏱时间轴
  • 🔖标签
  • 🗂️分类
  • 🙋🏻‍♂️关于
主页 📚文章

💻技术

MIT6.S081(9)-Interrupts

MIT6.S081(9)-Interrupts 6.S081 2020 Lecture 9: Interrupts Interrupts hardware wants attention now! e.g., pkt arrived, clock interrupt software must set aside current work and respond on RISC-V use same trap mechanism as for syscalls and exceptions new issues/complications: asynchronous interrupts running process interrupt handler may not run in context of process who caused interrupt concurrency devices and process run in parallel programming devices device can be difficult to program Where do device interrupts come from? diagram: Fig 1 of SiFive board in FU540-C000-v1.0.pdf CPUs, CLINT, PLIC, devices Fig 3 has more detail for interrupts Section 8 and 9 of FU540-C000-v1.0.pdf UART (universal asynchronous receiver/transmitter) See Section 13 of FU540-C000-v1.0.pdf Although the QEMU version is slightly different Follows http://byterunner.com/16550.html the interrupt tells the kernel the device hardware wants attention the driver (in the kernel) knows how to tell the device to do things often the interrupt handler calls the relevant driver but other arrangements are possible (schedule a thread; poll) [diagram: top-half/bottom-half] ...

2024-09-18 · 3 分钟 · 1303 字 · updated: 2024-09-18 · ShowGuan

MIT6.S081(8)-Page faults

MIT6.S081(8)-Page faults plan: cool things you can do with vm Better performance/efficiency e.g., one zero-filled page e.g., copy-on-write fork New features e.g., memory-mapped files virtual memory: several views primary purpose: isolation each process has its own address space Virtual memory provides a level-of-indirection provides kernel with opportunity to do cool stuff already some examples: shared trampoline page guard page but more possible… Key idea: change page tables on page fault Page fault is a form of a trap (like a system call) Xv6 panics on page fault But you don’t have to panic! Instead: update page table instead of panic restart instruction (see userret() from traps lecture) Combination of page faults and updating page table is powerful! ...

2024-09-17 · 8 分钟 · 3585 字 · updated: 2024-09-17 · ShowGuan

LeetCode周赛415(250915)

周赛240915 时隔多日再才重新开启周赛。这场DP居多。 T2-3290. 最高乘法得分 题目大意: 给定两个数组 a 和 b,数组 a 长度为 4,数组 b 长度至少为 4。需要从 b 中选择 4 个递增下标 i0 < i1 < i2 < i3,计算 a[0] * b[i0] + a[1] * b[i1] + a[2] * b[i2] + a[3] * b[i3],并返回最大得分。 ...

2024-09-17 · 2 分钟 · 997 字 · updated: 2024-09-17 · ShowGuan

MIT6.S081(7)-Q&A

MIT6.S081(7)-Q&A Plan: answering your questions Approach: walk through staff solutions start with pgtbl lab because it was the hardest your questions are at bottom of this file Pgtbl lab comments few lines of code, but difficult-to-debug bugs worst case: qemu/xv6 stops running “best” case: kernel panic hard to debug for staff too there are so many possible reasons why you discovered once we hadn’t seen yet likely to be the most challenging lab historically the first VM lab is hard this year too, even though we made a new lab to provide a gentler intro to VM Part 1 of pgtbl lab Explain vm output in terms of fig 3-4 ...

2024-09-12 · 6 分钟 · 2628 字 · updated: 2024-09-12 · ShowGuan

MIT6.S081(6)-System Call Entry/Exit

MIT6.S081(6)-System Call Entry/Exit Today: user -> kernel transition system calls, faults, interrupts enter the kernel in the same way important for isolation and performance lots of careful design and important detail What needs to happen when a program makes a system call, e.g. write()? [CPU | user/kernel diagram] CPU resources are set up for user execution (not kernel) 32 registers, sp, pc, privilege mode, satp, stvec, sepc, … what needs to happen? save 32 user registers and pc switch to supervisor mode switch to kernel page table switch to kernel stack jump to kernel C code high-level goals don’t let user code interfere with user->kernel transition e.g. don’t execute user code in supervisor mode! transparent to user code – resume without disturbing ...

2024-09-10 · 7 分钟 · 3444 字 · updated: 2024-09-10 · ShowGuan

MIT6.S081(5)-RISC-V calling convention, stack frames, and gdb

MIT6.S081(5)-RISC-V calling convention, stack frames, and gdb C code is compiled to machine instructions. How does the machine work at a lower level? How does this translation work? How to interact between C and asm Why this matters: sometimes need to write code not expressible in C And you need this for the syscall lab! RISC-V abstract machine No C-like control flow, no concept of variables, types … Base ISA: Program counter, 32 general-purpose registers (x0–x31) ...

2024-09-04 · 5 分钟 · 2064 字 · updated: 2024-09-04 · ShowGuan

C 语言指针

C 语言指针 C语言中的内存 静态内存(Static Memory) Global variables, accessible throughout the whole program. Defined with static keyword, as well as variables defined in global scope. 栈内存(Stack Memory) Local variables with functions. Destroyed after function exits. 堆内存(Heap Memory) ...

2024-09-03 · 7 分钟 · 3405 字 · updated: 2024-09-03 · ShowGuan

MIT6.S081(4)-Virtual Memory

MIT6.S081(4)-Virtual Memory Plan: Address spaces Paging hardware xv6 VM code Virtual memory overview Today’s problem: [user/kernel diagram] [memory view: diagram with user processes and kernel in memory] Suppose the shell has a bug: sometimes it writes to a random memory address how can we keep it from wrecking the kernel? and from wrecking other processes? we want isolated address spaces each process has its own memory it can read and write its own memory it cannot read or write anything else ...

2024-09-02 · 12 分钟 · 5742 字 · updated: 2024-09-02 · ShowGuan

MIT6.S081(1)-O/S overview

MIT6.S081(1)-O/S overview Class Page Overview 6.S081 goals Understand operating system (O/S) design and implementation Hands-on experience extending a small O/S Hands-on experience writing systems software What is the purpose of an O/S? Abstract the hardware for convenience and portability Multiplex the hardware among many applications Isolate applications in order to contain bugs Allow sharing among cooperating applications Control sharing for security Don’t get in the way of high performance Support a wide range of applications Organization: layered picture user applications: vi, gcc, DB, &c kernel services h/w: CPU, RAM, disk, net, &c we care a lot about the interfaces and internal kernel structure ...

2024-08-31 · 12 分钟 · 5896 字 · updated: 2024-08-31 · ShowGuan

MIT6.S081(3)-OS organization

MIT6.S081(3)-OS organization Lecture Topic: OS design ​ system calls ​ micro/monolithic kernel First system call in xv6 OS picture apps: sh, echo, … system call interface (open, close,…) OS Goal of OS run multiple applications isolate them multiplex them share Strawman design: No OS Application directly interacts with hardware CPU cores & registers DRAM chips Disk blocks … OS library perhaps abstracts some of it Strawman design not conducive to multiplexing each app periodically must give up hardware BUT, weak isolation app forgets to give up, no other app runs apps has end-less loop, no other app runs you cannot even kill the badly app from another app but used by real-time OSes “cooperative scheduling” ...

2024-08-31 · 4 分钟 · 1800 字 · updated: 2024-08-31 · ShowGuan

Kotlin协程(4)

Kotlin协程(4) Kotlin实践部分 Flow与文件下载应用 DownloadFragment.kt // DownloadFragment 是一个 Fragment 类,用于处理文件下载任务 class DownloadFragment : Fragment() { // 定义下载文件的 URL 地址,这是一个静态的常量 val URL = "https://ts1.cn.mm.bing.net/th/id/R-C.56ab5704680b6574c1b3c0a52643d8b5?rik=P8OAzrJEZS%2biuw&riu=http%3a%2f%2fjourneyz.co%2fwp-content%2fuploads%2f2019%2f09%2fGolden-Gate-Bridge.jpg&ehk=WX9eb2rUUjWBLLrsG2MQZLOMk2wtreV%2bT1Qq1tARk4s%3d&risl=&pid=ImgRaw&r=0" // 延迟初始化 mBinding,这是一个 FragmentDownloadBinding 对象,用于绑定 XML 布局文件 private val mBinding: FragmentDownloadBinding by lazy { FragmentDownloadBinding.inflate(layoutInflater) } // 重写 onCreateView 方法,在 Fragment 创建视图时调用 override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // 返回绑定的根视图作为 Fragment 的界面 return mBinding.root } // 重写 onActivityCreated 方法,当与 Fragment 相关的活动的 onCreate 方法完成时调用 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) // 使用 lifecycleScope 启动协程,当 Fragment 的生命周期处于 Created 状态时执行 lifecycleScope.launchWhenCreated { // 获取上下文并执行下载操作 context?.apply { // 指定下载文件的存储位置,保存在应用的外部文件目录下 val file = File(getExternalFilesDir(null)?.path, "pic.jpg") // 开始下载文件,并收集下载状态 DownloadManager.download(URL, file).collect { status -> when (status) { // 如果下载过程中有进度更新 is DownloadStatus.Progress -> { mBinding.apply { // 更新进度条和进度文本 progressBar.progress = status.value tvProgress.text = "${status.value}%" } } // 如果下载过程中出现错误 is DownloadStatus.Error -> { Toast.makeText(context, "下载错误", Toast.LENGTH_SHORT).show() } // 如果下载完成 is DownloadStatus.Done -> { mBinding.apply { // 设置进度条到 100%,并更新文本为 100% progressBar.progress = 100 tvProgress.text = "100%" } Toast.makeText(context, "下载完成", Toast.LENGTH_SHORT).show() } // 处理其他可能的下载状态 else -> { Log.d("Kennem", "下载失败") } } } } } } } DownloadManager.kt object DownloadManager { /** * 下载指定的文件并返回下载状态的流(Flow)。 * * @param url 文件下载的 URL 地址。 * @param file 下载后保存的本地文件。 * @return 下载状态的 Flow,包含下载进度、完成状态或错误信息。 */ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) fun download(url: String, file: File): Flow<DownloadStatus> { return flow { // 构建 HTTP 请求对象 val request = Request.Builder().url(url).get().build() // 执行 HTTP 请求,获取响应 val response = OkHttpClient.Builder().build().newCall(request).execute() // 检查响应是否成功 if (response.isSuccessful) { response.body()!!.let { body -> // 获取内容的总长度(字节数) val total = body.contentLength() // 使用输出流将下载的数据写入本地文件 file.outputStream().use { output -> val input = body.byteStream() // 获取输入流 var emittedProgress = 0L // 记录已发出的进度 // 将输入流中的数据复制到输出流,同时跟踪复制的字节数 input.copyTo(output) { bytesCopied -> // 计算当前进度百分比 val progress = bytesCopied * 100 / total // 每当进度比上次更新多5%以上时,发出进度更新 if (progress - emittedProgress > 5) { delay(100) // 模拟网络延迟(可选) emit(DownloadStatus.Progress(progress.toInt())) // 发出进度状态 emittedProgress = progress // 更新已发出的进度 } } } } // 下载完成,发出完成状态 emit(DownloadStatus.Done(file)) } else { // 如果响应失败,抛出异常 throw IOException(response.toString()) } }.catch { // 如果发生错误,删除部分下载的文件并发出错误状态 file.delete() emit(DownloadStatus.Error(it)) }.flowOn(Dispatchers.IO) // 在 IO 线程上运行下载流程 } } DownloadStatus.kt /** * 封闭类,用于表示下载过程中的不同状态。 * 封闭类允许定义一个有限的类型集合,在使用 `when` 表达式时提供全面的类型检查。 */ sealed class DownloadStatus { /** * 表示初始状态,即尚未开始下载。 * 可以用于初始化或重置状态。 */ object None : DownloadStatus() /** * 表示正在进行的下载的进度。 * @param value 一个整数,表示当前的进度百分比(0-100)。 */ data class Progress(val value: Int) : DownloadStatus() /** * 表示下载过程中发生的错误。 * @param throwable 引发失败的异常或错误。 */ data class Error(val throwable: Throwable) : DownloadStatus() /** * 表示下载成功完成。 * @param file 下载完成的文件。 */ data class Done(val file: File) : DownloadStatus() } Flow与Room的应用 ...

2024-08-25 · 10 分钟 · 4536 字 · updated: 2024-08-27 · ShowGuan

Git

Git git 由 linus 开发。 Git流程图 clone(克隆): 从远程仓库中克隆代码到本地仓库 checkout(检出):从本地仓库中检出一个仓库分支然后进行修订 add(添加):在提交前先将代码提交到暂存区 commit(提交):提交到本地仓库,本地仓库中保存修改的各个历史版本 fetch(抓取):从远程库,抓取到本地仓库,不进行任何的合并动作,一般操作比较少。 pull(拉取):从远程库拉到本地库,自动进行合并(merge),然后放到工作区,相当于fetch + merge push(推送):修改完成后,需要和团队成员共享代码时,将代码推送到远程仓库 Git基本配置 git config --global user.name "xxx" git config --global user.email "xxx@gmail.com" git config --global user.name git config --global user.email 给一些长命令起别名: ...

2024-08-20 · 9 分钟 · 4240 字 · updated: 2024-08-20 · ShowGuan

Android 八股

Android 八股 你准备的面经已经很全面且详细了,不过为了更全面地覆盖面试中可能涉及的问题,以下是一些补充和调整: 问题 1: 什么是垃圾回收机制? 解答: 垃圾回收(GC)是由Java虚拟机(JVM)垃圾回收器提供的一种对内存回收的机制。它会在内存空间不足或者内存占用过高的时候,自动回收那些没有引用的对象,以释放内存资源。垃圾回收的主要目标是自动管理内存,避免内存泄漏和内存溢出。 ...

2024-08-14 · 4 分钟 · 1617 字 · updated: 2026-03-22 · ShowGuan

Android学习计划

Android学习计划 6.24 - 6.30 学完Android基础,复习完Java基础,JavaWeb基础, 要做到极其熟练才行 7.1 - 7.5 把小米的笔记完完全全背下来,不能有半点差错 7.6 - 7.20 小米训练营, 上课尽量回答问题, 最后的机会了, 不能浪费 ...

2024-08-14 · 1 分钟 · 277 字 · updated: 2026-05-04 · ShowGuan

Kotlin协程(3)

Kotlin协程(3) 操作符 过渡流操作符 可以使用操作符转换符,就像使用集合与序列一样 过渡操作符应用于上游流,并返回下游流。 这些操作符也是冷操作符,就像流一样。这类操作符本身不是挂起函数。 它运行的速度很快,返回新的转换流的定义。 transform() // 定义一个挂起函数,模拟发送请求并返回响应 suspend fun performRequest(request: Int): String { delay(1000) // 模拟网络延迟,延迟 1000 毫秒(1 秒) return "response $request" // 返回一个响应字符串 } // 测试函数,使用 JUnit 测试框架 @Test fun `test transform flow operator`() = runBlocking { // 将整数范围转换为 Flow (1..3).asFlow() .transform { request -> // 使用 transform 运算符转换流的元素 emit("Making request $request") // 发射一个字符串,表示正在发送请求 emit(performRequest(request)) // 发射 performRequest 的返回值 } .collect { v -> // 收集流的元素 println(v) // 打印收集到的元素 } } take() // 定义一个简单的 Flow,产生 Int 类型的值 fun numbers() = flow<Int> { try { emit(1) // 发射第一个整数 1 emit(2) // 发射第二个整数 2 println("This line will not execute") // 这一行代码不会被执行,因为 collect 操作会提前中止 Flow emit(3) // 发射第三个整数 3(不会执行) } finally { // 在 Flow 被收集完成或取消时执行 println("Finally in numbers") // 打印 "Finally in numbers",表示 finally 块的执行 } } // 测试函数,使用 JUnit 测试框架 @Test fun `test limit length operator`() = runBlocking { // 调用 numbers 函数,限制收集的元素数量为 2 numbers() .take(2) // 使用 take 运算符,只收集前 2 个元素 .collect { v -> // 收集 Flow 中的元素 println(v) // 打印每个收集到的元素 } } 末端操作符 末端操作符是在流上用于启动流收集的挂起函数。collect是最基础的末端操作符,但是还有另外一些更方便使用的末端操作符 转化为各种集合,例如toList和toSet 获取第一个(first)值与确保流发射单个(single)值的操作符 使用reduce与fold将流规约到单个值 组合多个流 就像Kotlin标准库中的Sequence.zip拓展函数一样,流拥有一个zip操作符用于组合两个流中的相关值 @Test fun `test zip2`() = runBlocking { // 创建一个整数流,发射 1 到 3 的数字,每个数字发射之间延迟 300 毫秒 val numbs = (1..3).asFlow().onEach { delay(300) // 模拟延迟 } // 创建一个字符串流,发射 "one"、"two" 和 "three" 的字符串,每个字符串发射之间延迟 400 毫秒 val strs = flowOf("one", "two", "three").onEach { delay(400) // 模拟延迟 } // 记录测试开始时间 val startTime = System.currentTimeMillis() // 使用 zip 操作符将两个流的元素配对 numbs.zip(strs) { a, b -> "$a -> $b" } // 将 numbs 和 strs 的每对元素组合成字符串 .collect { v -> // 收集配对后的元素 // 打印配对后的元素和从测试开始到当前时间的时间差 println("$v, consume time : ${System.currentTimeMillis() - startTime} ms ") } } 展平流 流表示异步接收的值序列,所以很容易遇到这样的情况:每个值都会触发对另一个值序列的请求,然而,由于流具有异步的性质,因此需要不同的展平模式,为此,存在一系列的流展平操作符: flatMapConcat 连接模式 flatMapMerge 合并模式 flatMapLatest 最新展平模式 // 使用 flatMapConcat 操作符的测试函数 @Test fun `test flatMapConcat`() = runBlocking<Unit> { val startTime = System.currentTimeMillis() // 记录开始时间 // 创建一个流,包含数字 1 到 3 (1..3) .asFlow() // 将数字转换为流 .onEach { delay(100) } // 在每个元素上施加 100 毫秒的延迟 // 使用 flatMapConcat 将每个元素转换为新的流并串联 .flatMapConcat { requestFlow(it) } .collect { v -> // 收集并打印每个元素的值以及从开始到现在的消耗时间 println("$v, consume time : ${System.currentTimeMillis() - startTime} ms ") } } // 使用 flatMapMerge 操作符的测试函数 @Test fun `test flatMapMerge`() = runBlocking<Unit> { val startTime = System.currentTimeMillis() // 记录开始时间 // 创建一个流,包含数字 1 到 3 (1..3) .asFlow() // 将数字转换为流 .onEach { delay(100) } // 在每个元素上施加 100 毫秒的延迟 // 使用 flatMapMerge 将每个元素转换为新的流并并发执行 .flatMapMerge { requestFlow(it) } .collect { v -> // 收集并打印每个元素的值以及从开始到现在的消耗时间 println("$v, consume time : ${System.currentTimeMillis() - startTime} ms ") } } // 使用 flatMapLatest 操作符的测试函数 @Test fun `test flatMapLatest`() = runBlocking<Unit> { val startTime = System.currentTimeMillis() // 记录开始时间 // 创建一个流,包含数字 1 到 3 (1..3) .asFlow() // 将数字转换为流 .onEach { delay(100) } // 在每个元素上施加 100 毫秒的延迟 // 使用 flatMapLatest 将每个元素转换为新的流,仅保留最新流 .flatMapLatest { requestFlow(it) } .collect { v -> // 收集并打印每个元素的值以及从开始到现在的消耗时间 println("$v, consume time : ${System.currentTimeMillis() - startTime} ms ") } } 流的异常处理 当运算符中的发射器或代码抛出异常时,有几种处理异常的方法: try/catch块 catch函数 /** * 测试用例 `test exception1`: * 使用 runBlocking 启动协程,收集 simpleFlow 的值。 * 如果值大于 1,则抛出异常并捕获。 */ @Test fun `test exception1`() = runBlocking<Unit> { try { simpleFlow().collect { v -> println(v) check(v <= 1) { "Collected $v" } } } catch (e: Throwable) { println("Caught $e") } } /** * 测试用例 `test exception2`: * 创建一个流,发射一个值后抛出异常。 * 使用 catch 操作符处理异常。 */ @Test fun `test exception2`() = runBlocking<Unit> { flow { emit(1) throw ArithmeticException("Div 0") } .catch { e: Throwable -> println("Caught: $e") } .flowOn(Dispatchers.IO) .collect { println(it) } } 流的完成 当流收集完成时(普通情况或异常情况),它可能需要执行一个动作 命令是finally块 onCompletion声明式处理 // 定义一个函数,返回1到3的值作为 Flow fun simpleFlow2() = (1..3).asFlow() @Test // 测试在 finally 块中完成流的处理 fun `test flow complete in finally`() = runBlocking { try { // 收集 simpleFlow2 流中的每个值 simpleFlow2().collect { println(it) } } finally { // 无论流的收集过程是否成功,这条消息都会在最后打印 println("Done!") } } // 定义一个函数,返回一个 Flow<Int> 类型的流 fun simpleFlow3() = flow<Int> { // 发射(emit)第一个值 emit(1) // 发射一个异常来中断流 throw RuntimeException() } @Test // 测试使用 onCompletion 操作符处理流完成 fun `test flow complete in onCompletion`() = runBlocking { simpleFlow3() // 在流完成时调用的操作符 .onCompletion { exception -> if (exception != null) { // 如果有异常,打印流以异常方式完成 println("Flow completed exceptionally") } } // 捕获流中的异常 .catch { exception -> println("Caught $exception") } // 收集 simpleFlow3 流中的每个值 .collect { println(it) } } 通道-多路复用-并发安全 Channel Channel是一个并发安全的队列,它可以用来连接协程,实现不同协程的通信。 @Test fun `test know channel`() = runBlocking<Unit> { // 创建一个整数类型的通道,用于在不同的协程之间传递数据 val channel = Channel<Int>() // 在全局范围内启动一个生产者协程,负责向通道发送数据 val producer = GlobalScope.launch { var i = 0 while (true) { // 每次发送前暂停1秒钟 delay(1000) // 递增计数器并将值发送到通道 channel.send(++i) println("send $i") } } // 在全局范围内启动一个消费者协程,负责从通道接收数据 val consumer = GlobalScope.launch { while (true) { // 接收通道中的数据 val element = channel.receive() println("receive $element") } } // 等待生产者和消费者协程结束 joinAll(producer, consumer) } Channel的容量 Channel实际上就是一个队列,队列中一定存在缓冲区,一旦这个缓冲区满了,并且一直没有人调用receive并取走函数,send就需要挂起。故意让接收端的节奏放慢,发现send总是会挂起,直到receive之后才会继续往下执行。 迭代channel produce 与 actor 构造生产者与消费者的便捷方法 可以通过produce方法启动一个生产者协程,并返回一个ReceiveChannel, 其他协程就可以用这个Channel来接收数据了。反之,可以用actor启动一个消费者协程。 @OptIn(DelicateCoroutinesApi::class) @Test fun `test fast consumer channel`() = runBlocking { // 创建一个发送通道,它通过actor协程进行数据接收和处理 val sendChannel: SendChannel<Int> = GlobalScope.actor { while (true) { // 接收并处理通道中的数据 val element = receive() println(element) } } // 在全局范围内启动一个生产者协程,负责向通道发送数据 val producer = GlobalScope.launch { for (i in 0..3) { // 向通道发送数据 sendChannel.send(i) } } // 等待生产者协程完成 producer.join() } Channel的关闭 produce和actor返回的Channel都会随着对应的协程执行完毕而关闭,也正是这样,Channel才被称为热数据流。 对于Channel,如果我们调用了它的close方法,它会立即停止接收新元素,也就是说这是它的isClosedForSend会立即返回true。而由于Channel缓冲区的存在,这时候可能还有一些元素没有被处理完,因此要等所有的元素都被读取之后isClosedForReceive才会返回true。 Channel的生命周期最好由主导方来维护,建议由主导的一方实现关闭。 @Test fun `test close channel`() = runBlocking { // 创建一个无界限的整数类型的通道,用于在不同的协程之间传递数据 val channel = Channel<Int>(Channel.UNLIMITED) // 在全局范围内启动一个生产者协程,负责向通道发送数据 val producer = GlobalScope.launch { List(3) { channel.send(it) println("send $it") } // 发送完成后关闭通道 channel.close() // 打印通道的关闭状态 println( """close channel. | -ClosedForSend : ${channel.isClosedForSend} | -ClosedForReceive: ${channel.isClosedForReceive} """.trimMargin() ) } // 在全局范围内启动一个消费者协程,负责从通道接收数据 val consumer = GlobalScope.launch { for (element in channel) { println("receive $element") delay(1000) } // 消费完成后打印通道的关闭状态 println( """close channel. | -ClosedForSend : ${channel.isClosedForSend} | -ClosedForReceive: ${channel.isClosedForReceive} """.trimMargin() ) } // 等待生产者和消费者协程结束 joinAll(producer, consumer) } BroadcastChannel 发送端和接收端在Channel中存在一对多的情形,从数据处理本身来讲,虽然有多个接收端,但是同一个元素只会被一个接收端读到。广播则不然,多个接收端不存在互斥行为。 ...

2024-08-13 · 10 分钟 · 4552 字 · updated: 2024-08-13 · ShowGuan
« 上一页  下一页  »
© 2026 Kennem's Blog · Powered by Hugo & PaperMod
👤 Visitors: 👀 Views: