go进阶(2) -深入理解Channel实现原理
Go的并发模型已经在https://guisu.blog.csdn.net/article/details/129107148 详细说明。
1、channel使用详解
1、channel概述
Go的CSP并发模型,是通过goroutine
和channel
来实现的。
channel
是Go语言中各个并发结构体(goroutine
)之前的通信机制。 通俗的讲,就是各个goroutine
之间通信的”管道“,有点类似于Linux中的管道。- Go并发的核心哲学是不要通过共享内存进行通信; 相反,通过沟通分享记忆。
channel是Go提供goroutine间的通信方式,使用channel可以使多个goroutine之间通信。channel是进程内的通信方式,通过channel传递对象的过程和调用函数时的参数传递行为比较一致,比如也可以传递指针等。
如需跨进程通信,Go建议用分布式系统的方法来解决,如使用Socket或者HTTP等通信协议,Go语言在网络方面也有非常完善的支持。
主要应用场景:
- 数据交流:当作并发的 buffer 或者 queue,解决生产者 - 消费者问题。多个 goroutine 可以并发当作生产者(Producer)和消费者(Consumer)。
- 数据传递:一个goroutine将数据交给另一个goroutine,相当于把数据的拥有权托付出去。
- 信号通知:一个goroutine可以将信号(closing,closed,data ready等)传递给另一个或者另一组goroutine。
- 任务编排:可以让一组goroutine按照一定的顺序并发或者串行的执行,这就是编排功能。
- 锁机制:利用channel实现互斥机制。
2、channel基本语法
每个channel都有一个特殊的类型,也就是channels可发送数据的类型。一个可以发送int类型数据的channel一般写为chan int。
声明通道:var 通道变量 chan 通道类型:var channame chan ElementType
创建通道:make(chan 数据类型, [缓冲大小]):
channel跟map类似的在使用之前都需要使用make进行初始化ch1 := make(chan int, 5)
未初始化的channel零值默认为nil,是一种特殊的 chan,对值是 nil 的 chan 的发送接收调用者总是会阻塞。
var ch chan int
fmt.Println(ch) // <nil>
channel的基本用法非常简单,它提供了三种类型,分别为只能接收,只能发送,既能接收也能发送这三种类型:
var channame chan <- ElementType //只写:只能发送ElementType
var channame <- chan ElementType //只读:只能从chan里接收ElementType
var channame chan ElementType //能读能写:既能接收也能发送
我们把既能发送也能接收的chan被称为双向chan,把只能接收或者只能发送的chan称为单向。
而对于close方法只能是发送通道拥有。
箭头总是射向左边的,元素类型总在最右边。
如果箭头指向 chan,就表示可以往 chan 中发送(写)数据;
如果箭头远离 chan,就表示 chan 会往外吐数据,即能从chan里接收(读)数据
channel <- 1 //向channel添加一个值为1
<- channel //从channel取出一个值
a := <- channel //从channel取出一个值并赋值给a
a,b := <- channel //从channel取出一个值赋值给a,如果channel已经关闭或channel没有值,b为false
3、通信机制:
- 成对出现:在通信过程中,传数据
channel <- data
和取数据<-channel
必然会成对出现,因为这边传,那边取,两个goroutine
之间才会实现通信。 - 阻塞:不管传还是取,必阻塞,直到另外的
goroutine
传或者取为止。 - channel仅允许被一个goroutine读写。
1)只能接收数据的chan
package main import "fmt"// a 表示只能接收数据的chan
func goChanA(a <-chan int) {b := <-afmt.Println("只能接收数据的channal[a]接收到的数据值为", b)
}func main() {ch := make(chan int, 2)go goChanA(ch)// 往ch中写入数据值ch <- 2time.Sleep(time.Second)
}
结果:只能接收数据的channal[a]接收到的数据值为 2
2)只能发送数据的chan
package main import "fmtfunc main() {ch := make(chan<- int, 2)ch <- 200
}
往 chan
中发送一个数据使用“ch<-
”。
这里的 ch
是 chan int
类型或者是 chan <-int
。
3)同步,主协程和子协程之间通信:
func main(){ch := make(chan int)go func() {ch <- 996 //向ch添加元素}()a := <- chfmt.Println(a)fmt.Println("程序结束!")
}
4)、两个子协程的通信
使用channel实现两个goroutine之间通信。
func two() {tc := make(chan string)ch := make(chan int)// 第一个协程go func() {tc <- "协程A,我在添加数据"ch <- 1}()// 第二个协程go func() {content := <- tcfmt.Printf("协程B,我在读取数据:%s\n",content)ch <- 2}()<- ch<- chfmt.Println("程序结素!")
}
func main(){two()
}
5)、channel仅允许被一个goroutine读写。
package main
import ("fmt""time"
)
func goRoutineA(a <-chan int) {val := <-afmt.Println("goRoutineA received the data", val)
}
func goRoutineB(b chan int) {val := <-bfmt.Println("goRoutineB received the data", val)
}
func main() {ch := make(chan int, 3)go goRoutineA(ch)go goRoutineB(ch)ch <- 3time.Sleep(time.Second * 1)
}
6)、一直阻塞的情况
- 如果当前协程正在从一个没有任何值的通道中读取数据,那么当前协程会阻塞并且等待其他协程往此通道写入值。
- 因此,读操作将被阻塞。类似的,如果你发送数据到一个通道,它将阻塞当前协程直到有其他协程从通道中读取数据。此时写操作将阻塞 。
主线程在进行通道操作的时候造成死锁:
package mainimport "fmt"func main() {fmt.Println("mainGo start")channel := make(chan string)// 给通道 channel 传入一个数据GoLang.channel <- "GoLang"//此时主线程将阻塞直到有协程接收这个数据. Go的调度器开始调度协程接收通道 channel 的数据// 但是由于没有协程接受,没有协程是可被调度的。所有协程都进入休眠状态,即是主程序阻塞了。fmt.Println("mainGo stop")
}/*
报错
mainGo go start
fatal error: all goroutines are asleep - deadlock! //所有协程都进入休眠状态,死锁goroutine 1 [chan send]:
main.main()
*/
4、channel缓冲区
无缓冲通道,make(chan int),指在接收前没有能力保存任何值的通道,这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。
有缓冲通道,make(chan int, 2),指在被接收前能存储一个或者多个值的通道,这种类型的通道并不强制要求goroutine之间必须同时完成发送和接收。
例子:
package mainimport "fmt"func main() {ch1 := make(chan int)ch1 <- 5rec := <-ch1fmt.Println("ch1被接受,程序结束:rec:,", rec)
}
//fatal error: all goroutines are asleep - deadlock!
由于ch1没有缓冲区,channel没有缓冲区的话:
只有在有接收方能够接收值的时候才能发送成功,否则会一直处于等待发送的阶段。同理,如果对一个无缓冲通道执行接收操作时,没有任何向通道中发送值的操作那么也会导致接收操作阻塞。
如果想要运行成功那么在发送信息前就应该有另外的协程等待着接收
package mainimport ("fmt""time"
)func main() {ch1 := make(chan int)go receive(ch1)ch1 <- 5time.Sleep(time.Second)}
func receive(ch1 chan int) {for {select {case rec2 := <-ch1:fmt.Println("ch1被接受,程序结束:rec:,", rec2)}}
}
//ch1被接受,程序结束:rec:, 5
但是如果有缓冲区就能避免程序阻塞,可以将发送的channel放在缓冲区直至有接收方将它接收
向channel添加数据超过缓存,会出现死锁:
func main() {ch := make(chan int,3)ch <- 1//<- chch <- 1ch <- 1ch <- 1fmt.Println("ok")
}
-----
5、 阻塞的 gorutinue 与资源泄露
在 2012 年 Google I/O 大会上,Rob Pike 的 Go Concurrency Patterns 演讲讨论 Go 的几种基本并发模式,如 完整代码 中从数据集中获取第一条数据的函数:
func First(query string, replicas []Search) Result {c := make(chan Result)replicaSearch := func(i int) { c <- replicas[i](query) }for i := range replicas {go replicaSearch(i)}return <-c}
在搜索重复时依旧每次都起一个 goroutine 去处理,每个 goroutine 都把它的搜索结果发送到结果 channel 中,channel 中收到的第一条数据会直接返回。
返回完第一条数据后,其他 goroutine 的搜索结果怎么处理?他们自己的协程如何处理?
在 First() 中的结果 channel 是无缓冲的,这意味着只有第一个 goroutine 能返回,由于没有 receiver,其他的 goroutine 会在发送上一直阻塞。如果你大量调用,则可能造成资源泄露。
为避免泄露,你应该确保所有的 goroutine 都能正确退出,有 2 个解决方法:
使用带缓冲的 channel,确保能接收全部 goroutine 的返回结果:
func First(query string, replicas ...Search) Result { c := make(chan Result,len(replicas)) searchReplica := func(i int) { c <- replicas[i](query) }for i := range replicas {go searchReplica(i)}return <-c}
使用 select 语句,配合能保存一个缓冲值的 channel default 语句:
default 的缓冲 channel 保证了即使结果 channel 收不到数据,也不会阻塞 goroutine
func First(query string, replicas ...Search) Result { c := make(chan Result,1)searchReplica := func(i int) {select {case c <- replicas[i](query):default:}}for i := range replicas {go searchReplica(i)}return <-c}
使用特殊的废弃(cancellation) channel 来中断剩余 goroutine 的执行:
func First(query string, replicas ...Search) Result { c := make(chan Result)done := make(chan struct{})defer close(done)searchReplica := func(i int) {select {case c <- replicas[i](query):case <- done:}}for i := range replicas {go searchReplica(i)}return <-c}
Rob Pike 为了简化演示,没有提及演讲代码中存在的这些问题。不过对于新手来说,可能会不加思考直接使用。
二、使用Select来进行调度
select就是用来监听和channel有关的IO操作,当 IO 操作发生时,触发相应的动作。
Select 和 swith结构很像,但是select中的case的条件只能是I/O。
Select 的使用方式类似于 switch 语句,它也有一系列 case 分支和一个默认的分支。
每个 case分支会对应一个通道的通信(接收或发送)过程。select 会一直等待,直到其中的某个 case 的通信操作完成时,就会执行该 case分支对应的语句。
具体格式如下:
select {
case <-ch1://...
case rec := <-ch2://...
case ch3 <- 10://...
default://默认操作
}
select里面case是随机执行的,如果都不满足条件,那么就执行default
select总结:
- 每个case必须是一个I/O操作
- case是随机执行的:如果多个 case 同时满足,select 会随机选择一个执行。
- 如果所有case不能执行,那么会执行default
- 如果所有case不能执行,且没有default,会出现阻塞
- 对于没有 case 的 select 会一直阻塞,可用于阻塞 main 函数,防止退出
实现一个一直接收消息:
func main() {ch := make(chan int)for i := 1; i <= 10; i++ {go func(j int) {ch <- j}(i)}for {select {case a1 := <- ch:fmt.Println(a1)default:}}
}
示例2:
package mainimport ("fmt""time"
)func goRoutineD(ch chan int, i int) {time.Sleep(time.Second * 3)ch <- i
}
func goRoutineE(chs chan string, i string) {time.Sleep(time.Second * 3)chs <- i}func main() {ch := make(chan int, 5)chs := make(chan string, 5)go goRoutineD(ch, 5)go goRoutineE(chs, "ok")select {case msg := <-ch:fmt.Println(" received the data ", msg)case msgs := <-chs:fmt.Println(" received the data ", msgs)default:fmt.Println("no data received ")time.Sleep(time.Second * 1)}}
运行程序,因为当前时间没有到3s,所以select 选择defult
no data received
修改程序,我们注释掉default,并多执行几次结果为
received the data 5
received the data ok
received the data ok
received the data ok
select语句会阻塞,直到监测到一个可以执行的IO操作为止,而这里goRoutineD和goRoutineE睡眠时间是相同的,都是3s,从输出可看出,从channel中读出数据的顺序是随机的。
再修改代码,goRoutineD睡眠时间改成4s
func goRoutineD(ch chan int, i int) {time.Sleep(time.Second * 4)ch <- i }
此时会先执行goRoutineE,select 选择case msgs := <-chs。
三、死锁(deadlock)
指两个或两个以上的协程的执行过程中,由于竞争资源或由于彼此通信而造成的一种阻塞的现象。
在非缓冲信道若发生只流入不流出,或只流出不流入,就会发生死锁。
下面是一些死锁的例子
package mainfunc main() {ch := make(chan int)ch <- 3
}
上面情况,向非缓冲通道写数据会发生阻塞,导致死锁。解决办法创建缓冲区 ch := make(chan int,3)
package main
import ("fmt"
)func main() {ch := make(chan int)fmt.Println(<-ch)
}
向非缓冲通道读取数据会发生阻塞,导致死锁。 解决办法开启缓冲区,先向channel写入数据。
package mainfunc main() {ch := make(chan int, 3)ch <- 3ch <- 4ch <- 5ch <- 6
}
写入数据超过缓冲区数量也会发生死锁。解决办法将写入数据取走。
死锁的情况有很多这里不再赘述。
还有一种情况,向关闭的channel写入数据,不会产生死锁,产生panic。
package mainfunc main() {ch := make(chan int, 3)ch <- 1close(ch)ch <- 2
}
解决办法别向关闭的channel写入数据。
四、channel实现原理
1、channel数据结构
channel一个类型管道,通过它可以在goroutine之间发送消息和接收消息。它是golang在语言层面提供的goroutine间的通信方式。通过源代码分析程序执行过程,源码src/runtime/chan.go:
channel结构体hchan:
type hchan struct {qcount uint // 当前队列列中剩余元素个数dataqsiz uint // 环形队列长度,即可以存放的元素个数即缓冲区的大小,即make(chan T,N),N.buf unsafe.Pointer // 环形队列列指针elemsize uint16 // 每个元素的⼤⼩closed uint32 // 标识关闭状态:表示当前通道是否处于关闭状态。创建通道后,该字段设置为0,即通道打开; 通过调用close将其设置为1,通道关闭。elemtype *_type // 元素类型:用于数据传递过程中的赋值;sendx uint // 队列下标,指示元素写⼊入时存放到队列列中的位置 xrecvx uint // 队列下标,指示元素从队列列的该位置读出 recvq waitq // 等待读消息的goroutine队列sendq waitq // 等待写消息的goroutine队列lock mutex // 互斥锁,chan不允许并发读写
} type waitq struct {first *sudoglast *sudog
}
从数据结构可以看出channel由队列、类型信息、goroutine等待队列组成。
2、实现方式
创建channel 有两种,一种是带缓冲的channel,一种是不带缓冲的channel
// 带缓冲 ch := make(chan Task, 6) // 不带缓冲 ch := make(chan int)
下图展示了可缓存6个元素的channel底层的数据模型如下图:
func makechan(t *chantype, size int) *hchan {elem := t.elem...
}
- dataqsiz:指向队列的长度为6,即可缓存6个元素
- buf:指向队列的内存,队列中还剩余两个元素
- qcount:当前队列中剩余的元素个数
- sendx:指后续写入元素的位置
- recvx:指从该位置读取数据
等待队列
从channel中读数据,如果channel缓冲区为空或者没有缓冲区,当前goroutine会被阻塞;向channel中写数据,如果channel缓冲区已满或者没有缓冲区,当前goroutine会被阻塞。
被阻塞的goroutine将会被挂在channel的等待队列中:
- 因读阻塞的goroutine会被向channel写入数据的goroutine唤醒
- 因写阻塞的goroutine会被从channel读数据的goroutine唤醒
下面展示了一个没有缓冲区的channel,有几个goroutine阻塞等待数据:
注意,一般情况下recvq和sendq至少有一个为空。只有一个例外,那就是同一个goroutine使用select语句向channel一边写数据一边读数据。
3、向channel写数据
ch := make(chan int, 3)
创建通道后的缓冲通道结构:
hchan struct {qcount uint : 0 dataqsiz uint : 3 buf unsafe.Pointer : 0xc00007e0e0 elemsize uint16 : 8 closed uint32 : 0 elemtype *runtime._type : &{size:8 ptrdata:0 hash:4149441018 tflag:7 align:8 fieldalign:8 kind:130 alg:0x55cdf0 gcdata:0x4d61b4 str:1055 ptrToThis:45152}sendx uint : 0 recvx uint : 0 recvq runtime.waitq : {first:<nil> last:<nil>}sendq runtime.waitq : {first:<nil> last:<nil>}lock runtime.mutex : {key:0}
}
写入数据:ch <- 3,底层hchan数据流程如图
1、锁定整个通道结构。
2、确定写入:如果recvq队列不为空,说明缓冲区没有数据或者没有缓冲区,此时直接从recvq等待队列中取出一个G(goroutine),并把数据写入,最后把该G唤醒,结束发送过程;
3、如果recvq为Empty,则确定缓冲区是否可用。如果可用,从当前goroutine复制数据写入缓冲区,结束发送过程。
4、如果缓冲区已满,则要写入的元素将保存在当前正在执行的goroutine的结构中,并且当前goroutine将在sendq中排队并从运行时挂起(进入休眠,等待被读goroutine唤醒)。
5、写入完成释放锁。
这里我们要注意几个属性buf、sendx、lock的变化。
3、从channel读取操作
几乎和写入操作相同
func goRoutineA(a <-chan int) {val := <-afmt.Println("goRoutineA received the data", val) }
底层hchan数据流程如图:
1、先获取channel全局锁
2、如果等待发送队列sendq不为空(有等待的goroutine):
1)若没有缓冲区,直接从sendq队列中取出G(goroutine),直接取出goroutine并读取数据,然后唤醒这个goroutine,结束读取释放锁,结束读取过程;
2)若有缓冲区(说明此时缓冲区已满),从缓冲队列中首部读取数据,再从sendq等待发送队列中取出G,把G中的数据写入缓冲区buf队尾,结束读取释放锁;
3、如果等待发送队列sendq为空(没有等待的goroutine):
1)若缓冲区有数据,直接读取缓冲区数据,结束读取释放锁。
2)没有缓冲区或缓冲区为空,将当前的goroutine加入recvq排队,进入睡眠,等待被写goroutine唤醒。结束读取释放锁。
流程图:
ecvq和
recvq和sendq 结构
recvq和sendq基本上是链表,看起来基本如下:
相关文章:

go进阶(2) -深入理解Channel实现原理
Go的并发模型已经在https://guisu.blog.csdn.net/article/details/129107148 详细说明。 1、channel使用详解 1、channel概述 Go的CSP并发模型,是通过goroutine和channel来实现的。 channel是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲…...

数组(二)-- LeetCode[303][304] 区域和检索 - 数组不可变
1 区域和检索 - 数组不可变 1.1 题目描述 题目链接:https://leetcode.cn/problems/range-sum-query-immutable/ 1.2 思路分析 最朴素的想法是存储数组 nums 的值,每次调用 sumRange 时,通过循环的方法计算数组 nums 从下标 iii 到下标 jjj …...

22-基于分时电价条件下家庭能量管理策略研究MATLAB程序
参考文献:《基于分时电价和蓄电池实时控制策略的家庭能量系统优化》参考部分模型《计及舒适度的家庭能量管理系统优化控制策略》参考部分模型主要内容:主要做的是家庭能量管理模型,首先构建了电动汽车、空调、热水器以及烘干机等若干家庭用户…...

“XXX.app 已损坏,打不开。您应该将它移到废纸篓”,Mac应用程序无法打开或文件损坏的处理方法(2)
1. 检查状态 在sip系统完整性关闭前,我们先检查是否启用了SIP系统完整性保护。打开终端输入以下命令【csrutil status】并回车: 你会看到以下信息中的一个,用来指示SIP状态。已关闭 disabled: System Integrity Protection status: disabl…...

flask入门-3.Flask操作数据库
3. Flask操作数据库 1. 连接数据库 首先下载 MySQL数据库 其次下载对应的包: pip install pymysql pip install flask-sqlalchemy在 app.py 中进行连接测试 from flask import Flask, request, render_template from flask_sqlalchemy import SQLAlchemyhostname "1…...

STM32 使用microros与ROS2通信
本文主要介绍如何在STM32中使用microros与ROS2进行通信,在ROS1中标准的库是rosserial,在ROS2中则是microros,目前网上的资料也有一部分了,但是都没有提供完整可验证的demo,本文将根据提供的demo一步步给大家进行演示。1、首先如果你用的不是S…...
51单片机入门 - 测试:SDCC / Keil C51 会让没有调用的函数参与编译吗?
Small Device C Compiler(SDCC)是一款免费 C 编译器,适用于 8 位微控制器。 不想看测试过程的话可以直接划到最下面看结论:) 关于软硬件环境的信息: Windows 10STC89C52RCSDCC (构建HEX文件&…...

【计算机网络】计算机网络
目录一、概述计算机网络体系结构二、应用层DNS应用文件传输应用DHCP 应用电子邮件应用Web应用当访问一个网页的时候,都会发生什么三、传输层UDP 和 TCP 的特点UDP 首部格式TCP 首部格式TCP 的三次握手TCP 的四次挥手TCP 流量控制TCP 拥塞控制三、网络层IP 数据报格式…...

【java web篇】项目管理构建工具Maven简介以及安装配置
📋 个人简介 💖 作者简介:大家好,我是阿牛,全栈领域优质创作者。😜📝 个人主页:馆主阿牛🔥🎉 支持我:点赞👍收藏⭐️留言Ὅ…...

springboot笔记
微服务架构 微服务是一种架构风格,开发构建应用的时候把应用的业务构建成一个个的小服务(这就类似于把我们的应用程序构建成了一个个小小的盒子,它们在一个大的容器中运行,这种一个个的小盒子我们把它叫做服务)&#…...

【多线程与高并发】- 浅谈volatile
浅谈volatile简介JMM概述volatile的特性1、可见性举个例子总结2、无法保证原子性举个例子分析使用volatile对原子性测试使用锁的机制总结3、禁止指令重排什么是指令重排序重排序怎么提高执行速度重排序的问题所在volatile禁止指令重排序内存屏障(Memory Barrier)作用volatile内…...

avro格式详解
【Avro介绍】Apache Avro是hadoop中的一个子项目,也是一个数据序列化系统,其数据最终以二进制格式,采用行式存储的方式进行存储。Avro提供了:丰富的数据结构可压缩、快速的二进制数据格式一个用来存储持久化数据的容器文件远程过程…...

【涨薪技术】0到1学会性能测试 —— LR录制回放事务检查点
前言 上一次推文我们分享了性能测试分类和应用领域,今天带大家学习性能测试工作原理、事务、检查点!后续文章都会系统分享干货,带大家从0到1学会性能测试,另外还有教程等同步资料,文末免费获取~ 01、LR工作原理 通常…...
卡尔曼滤波原理及代码实战
目录简介1.原理介绍场景假设(1).下一时刻的状态(2).增加系统的内部控制(3).考虑运动系统外部的影响(4).后验估计:预测结果与观测结果的融合卡尔曼增益K2.卡尔曼滤波计算过程(1).预测阶段(先验估计阶段)(2).更新阶段(后验估计阶段&…...

Jmeter使用教程
目录一,简介二,Jmeter安装1,下载2,安装三,创建测试1,创建线程组2,创建HTTP请求默认值3,创建HTTP请求4,添加HTTP请求头5,添加断言6,添加查看结果树…...

论文笔记|固定效应的解释和使用
DeHaan E. Using and interpreting fixed effects models[J]. Available at SSRN 3699777, 2021. 虽然固定效应在金融经济学研究中无处不在,但许多研究人员对作用的了解有限。这篇论文解释了固定效应如何消除遗漏变量偏差并影响标准误差,并讨论了使用固…...

数据集市与数据仓库的区别
数据仓库是企业级的,能为整个企业各个部门的运作提供决策支持;而数据集市则是部门级的,一般只能为某个局部范围内的管理人员服务,因此也称之为部门级数据仓库。 1、两种数据集市结构 数据集市按数据的来源分为以下两种 &#x…...

Golang学习Day3
😋 大家好,我是YAy_17,是一枚爱好网安的小白。 本人水平有限,欢迎各位师傅指点,欢迎关注 😁,一起学习 💗 ,一起进步 ⭐ 。 ⭐ 此后如竟没有炬火,我便是唯一的…...

Python并发编程-事件驱动模型
一、事件驱动模型介绍 1、传统的编程模式 例如:线性模式大致流程 开始--->代码块A--->代码块B--->代码块C--->代码块D--->......---&…...

构建系统发育树简述
1. 要点 系统发育树代表了关于一组生物之间的进化关系的假设。可以使用物种或其他群体的形态学(体型)、生化、行为或分子特征来构建系统发育树。在构建树时,我们根据共享的派生特征(不同于该组祖先的特征)将物种组织成…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...

Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...