go的通信Channel
一、channel是什么
1.一种通信机制
channel是goroutine与goroutine之间数据通信的一种通信机制。一般都是2个g及以上一起工作。
channel与关键字range和select紧密相关。
二、channel的结构
go源码:GitHub - golang/go: The Go programming language
src/runtime/chan.go
type hchan struct {qcount uint // total data in the queue 队列中当前元素计数,满了就=dataqsizdataqsiz uint // size of the circular queue 环形队列大小(缓存大小)buf unsafe.Pointer // points to an array of dataqsiz elements 指向任意类型的指针elemsize uint16 //元素大小closed uint32 //是否关闭,0-未关闭,1-已关闭timer *timer // timer feeding this chan //定时器elemtype *_type // element type //元素类型sendx uint // send index //发送索引recvx uint // receive index //结束索引recvq waitq // list of recv waiters //接收等待队列(<-ch)sendq waitq // list of send waiters //发送等待队列(ch<-)// lock protects all fields in hchan, as well as several// fields in sudogs blocked on this channel.//// Do not change another G's status while holding this lock// (in particular, do not ready a G), as this can deadlock// with stack shrinking.lock mutex //锁,保护hchan中的所有字段
}type waitq struct { //等待队列,sudog双向链表结构first *sudog //(伪g)表示等待列表中的g,例如在一个通道上用于发送/接收的glast *sudog //用acquireSudog分配,releaseSudog释放
1.特点
结构体上可以记住channel的一些比如说
(1)lock 锁:操作channel是互斥的。先获取锁,操作channel,释放锁
(2)elemtype类型:创建的时候必须指定类型(大小可指定,如ch := make(chan int,10))
(3)waitq队列:FIFO先进先出队列,即通道,能通过任意类型(unsafe.Pointer)的数据,(sudog)双向链表的g
(4)dataqsiz通道容量:有值=有缓冲通道,没值=无缓冲通道
(5)qcount通道元素计数:当前通道内的元素个数总数
(6)接受和发送:通信,有人发还要有人收,意味必须2个g及以上的成员一起工作
(7)timer定时器:定时可对channel做特殊操作
(8)closed关闭:写(发送)已关闭通道会panic,读(接收)已关闭通道立刻返回:true,false
三、channel的创建
1.make、var
make(chan类型 元素类型,缓冲容量大小),make初始化channel的值是nil
var chan类型 元素类型:var 只是声明,没初始化,直接操作会报错
func Test_2(t *testing.T) {ch1 := make(chan int) //双向ch11 := make(chan int, 10) //双向,带缓冲容量10ch2 := make(chan<- int) //只写ch22 := make(chan int, 10) //只写,带缓冲容量10ch3 := make(<-chan float64) //只读ch33 := make(chan int, 10) //只读,带缓冲容量10//go1.17_spec.html//chan T // can be used to send and receive values of type T//chan<- float64 // can only be used to send float64s//<-chan int // can only be used to receive intsvar ch4 chan int//通道是引用类型,通道类型的空值是nil。g.Dump(ch1, ch11, ch2, ch22, ch3, ch33, ch4)//打印//<chan int>//<chan int>//<chan<- int>//<chan int>//<<-chan float64>//<chan int>//<chan int>
}
2.chan类型:
chan类型分3种:双向读写、单向写、单向读
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" )=(双向|单向发送(写)|单向接收(读))
单向只写通道:操作读会报错。
如使用场景,上下文:src/context/context.go的Context,
type Context interface {
...// a Done channel for cancellation.Done() <-chan struct{}
...}
单向只读通道:操作写会报错
如使用场景,信号量:src/os/signal/signal.go的handlers,
var handlers struct {sync.Mutex// Map a channel to the signals that should be sent to it.m map[chan<- os.Signal]*handler...
}type stopping struct {c chan<- os.Signalh *handler
}
3.元素类型:
任意数据类型,如:int,float64,bool,map...
4.缓冲容量
第二个参数给定数量,如10
无缓冲通道:ch := make(chan int);
有缓冲通道:ch := make(chan int,10)
四、向channel写数据
channel的发送要注意区分【有缓冲容量】和【无缓冲容量】
1.正常发送
默认情况下,发送和接收会一直阻塞着,直到另一方准备好。使用:ch <- i
不管是【有缓冲容量】和【无缓冲容量】的通道,只有有g在接收,其他g就能正常发
func Test_send1(t *testing.T) {ch := make(chan int) //双向//开启goroutine将1~5的数发送到ch中go func() {for i := 1; i <= 5; i++ {fmt.Println("写入ch 元素:", i)ch <- i}close(ch) //写完,关闭通道}()//在主goroutine中从ch中接收值打印for i := range ch {fmt.Println("读取ch 结果:", i)}//写入ch 元素: 1//写入ch 元素: 2//读取ch 结果: 1//读取ch 结果: 2//写入ch 元素: 3//写入ch 元素: 4//读取ch 结果: 3//读取ch 结果: 4//写入ch 元素: 5//读取ch 结果: 5//主goroutine和goroutine读写互斥,相互竞争锁。直到通道关闭主程结束
}
2.异常发送【无缓冲】
没g在接收:无缓冲,其他g不能发,直接阻塞
注意:select是非阻塞发送,会直接返回false
func Test_send2(t *testing.T) {ch := make(chan int) //双向//开启goroutine将1~5的数发送到ch中go func() {for i := 1; i <= 5; i++ {fmt.Println("写入ch 元素:", i)ch <- i}close(ch) //写完,关闭通道}()//写入ch 元素: 1//无缓存通道:没人接收,尝试发送1时,g被阻塞
}
3.异常发送【有缓冲】
没g在接收:有缓冲,其他g能发缓冲容量个数,再阻塞
有缓存通道,容量5,能先发5个,第6个阻塞
func Test_send4(t *testing.T) {ch := make(chan int, 5) //双向//开启goroutine将1~5的数发送到ch中go func() {for i := 1; i <= 10; i++ {fmt.Println("写入ch 元素:", i)ch <- i}close(ch) //写完,关闭通道}()//写入ch 元素: 1//写入ch 元素: 2//写入ch 元素: 3//写入ch 元素: 4//写入ch 元素: 5//写入ch 元素: 6//无缓存通道:没人接收,前5个元素成功发送,尝试发送6时,g才被阻塞
}
4.已关闭发送
已关闭的channel,操作发送会panic
func Test_send3(t *testing.T) {ch := make(chan int) //双向close(ch) //关闭通道ch <- 1//在主goroutine中从ch中接收值打印for i := range ch {fmt.Println("读取ch 结果:", i)}//panic: send on closed channel
}
5.发送源码:
ch <- i
//发送入口:参数(通道,数据指针)
func chansend1(c *hchan, elem unsafe.Pointer) {chansend(c, elem, true, getcallerpc())
}
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {//(1)如果通道==nil了,并且block==ture,会调用gopark()函数,使当前的g休眠//从而导致了:all goroutines are asleep - deadlock!if c == nil {if !block {return false}gopark(nil, nil, waitReasonChanSendNilChan, traceBlockForever, 2)throw("unreachable")}...lock(&c.lock)if c.closed != 0 {//(2)如果通道关闭了,不允许发送,直接panicunlock(&c.lock)panic(plainError("send on closed channel"))}...if sg := c.recvq.dequeue(); sg != nil { //(3)接收队列有gsend(c, sg, ep, func() { unlock(&c.lock) }, 3)return true}if c.qcount < c.dataqsiz { //(4)接收队列没有g,看有缓冲容量且还没满,往缓冲中发...}...gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceBlockChanSend, 2) //(5)阻塞...
}
//有接收的g,直接发送
func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {...sendDirect(c.elemtype, sg, ep)...
}
//将数据直接写入到接收方的执行栈
func sendDirect(t *_type, sg *sudog, src unsafe.Pointer) {...memmove(dst, src, t.Size_)...
}
// memmove从"from"拷贝n个字节到"to"
func memmove(to, from unsafe.Pointer, n uintptr)
发送数据大概过程:
(1)判断channel是否nil,nil且bolck=true,阻塞当前发送g,等待接收方接收数据
(2)获取锁,如果通道关闭了,不允许发送,直接panic
(3)如果当前有接收队列有g在接收,直接调send()函数发送,返回true
(4)接收队列没有g,看有缓冲容量且还没满,往缓冲中发,返回true
(5)找不到接收的g,且缓冲容量也满了,阻塞当前发送的g,等待接收方接收数据
(6)唤醒等待执行的其他g
尤其注意:通道是nil的情况,直接写数据,会发生all goroutines are asleep - deadlock!的情况
func Test_deadlock1(t *testing.T) {// 未初始化的channel,直接写死锁var ch chan intch <- 1//fatal error: all goroutines are asleep - deadlock!// 初始化无缓冲的channel,直接写死锁ch := make(chan int)ch <- 1//fatal error: all goroutines are asleep - deadlock!
}
四、向channel读数据
1.正常读取:
channel读取数据一般用range来遍历读。
也可以用for循环来遍历,channel的recvq和sendq本质是一个双向链表。
跟发送类似,不管是【有缓冲容量】和【无缓冲容量】的通道,只要有g在接收(接收先于发送),其他g就能正常发
func Test_send1(t *testing.T) {ch := make(chan int) //双向//开启goroutine将1~5的数发送到ch中go func() {for i := 1; i <= 5; i++ {fmt.Println("写入ch 元素:", i)ch <- i}close(ch) //写完,关闭通道}()//在主goroutine中从ch中接收值打印for i := range ch {fmt.Println("读取ch 结果:", i)}//写入ch 元素: 1//写入ch 元素: 2//读取ch 结果: 1//读取ch 结果: 2//写入ch 元素: 3//写入ch 元素: 4//读取ch 结果: 3//读取ch 结果: 4//写入ch 元素: 5//读取ch 结果: 5//主goroutine和goroutine读写互斥,相互竞争锁。直到通道关闭主程结束
}
2.异常读取【死锁】
(1)读完未关闭的channel会造成死锁
func Test_read8(t *testing.T) {ch := make(chan int) //双向//开启goroutine将1~5的数发送到ch中go func() {for i := 1; i <= 5; i++ {fmt.Println("写入ch 元素:", i)ch <- i}//close(ch) //写完,关闭通道}()//在主goroutine中从ch中接收值打印for i := range ch {fmt.Println("读取ch 结果:", i)}//写入ch 元素: 1//写入ch 元素: 2//读取ch 结果: 1//读取ch 结果: 2//写入ch 元素: 3//写入ch 元素: 4//读取ch 结果: 3//读取ch 结果: 4//写入ch 元素: 5//读取ch 结果: 5//fatal error: all goroutines are asleep - deadlock!
}
同样:如果channel没关闭,一直for循环读,读完之后会报死锁的错误。
func Test_read2(t *testing.T) {ch := make(chan int) //双向for i := 1; i < 9; i++ {v, ok := <-chfmt.Println(v, ok)}
}
func Test_read3(t *testing.T) {ch := make(chan int, 5) //双向for i := 1; i < 9; i++ {v, ok := <-chfmt.Println(v, ok)}
}
func Test_read4(t *testing.T) {var ch chan intfor i := 1; i < 9; i++ {v, ok := <-chfmt.Println(v, ok)}
}
3.已关闭读取
思考:会panic吗?
答案是:不会panic,不影响
已关闭通道,如果有值,返回:值,true
已关闭通道,如果没值,返回:0,false
func Test_read5(t *testing.T) {ch := make(chan int) //双向close(ch)for i := range ch {fmt.Println("读取ch 结果:", i)}//0 false//0 false//0 false//0 false//0 false//0 false//0 false//0 false
}func Test_read6(t *testing.T) {ch := make(chan int, 5) //双向for i := 1; i <= 5; i++ {fmt.Println("写入ch 元素:", i)ch <- i}close(ch)for i := 1; i < 9; i++ {v, ok := <-chfmt.Println(v, ok)}//写入ch 元素: 1//写入ch 元素: 2//写入ch 元素: 3//写入ch 元素: 4//写入ch 元素: 5//1 true//2 true//3 true//4 true//5 true//0 false//0 false//0 false
}
4.读取源码:
v <- ch ,chanrecv1(c *hchan, elem unsafe.Pointer)
v,ok <-ch ,chanrecv2(c *hchan, elem unsafe.Pointer) (received bool)
//接收程序入口
func chanrecv1(c *hchan, elem unsafe.Pointer) {chanrecv(c, elem, true)
}
func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) {_, received = chanrecv(c, elem, true)return
}func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {...//(1)如果channel==nil,block==true,调gopart()阻塞当前读取的g//和发送一样,容易造成all g asleep()的问题if c == nil {if !block {return}gopark(nil, nil, waitReasonChanReceiveNilChan, traceBlockForever, 2)throw("unreachable")}...lock(&c.lock)if c.closed != 0 {//(2)通道已关闭,并且没有缓冲值,直接返回true, falseif c.qcount == 0 {...return true, false}} else {if sg := c.sendq.dequeue(); sg != nil {//(3)否则,如果有发送队列在等待,直接调recv进行接收recv(c, sg, ep, func() { unlock(&c.lock) }, 3)return true, true}}//(4)通道未关闭,且缓冲有值,直接读缓冲队列的值if c.qcount > 0 {...return true, true}...gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanReceive, traceBlockChanRecv, 2) //(5)通道没关闭又没有发送的g,且缓冲也是空的,则阻塞当前读取的g...releaseSudog(mysg) //(6)唤醒等待执行的其他g...
}
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {...recvDirect(c.elemtype, sg, ep)...
}
func recvDirect(t *_type, sg *sudog, dst unsafe.Pointer) {...memmove(dst, src, t.Size_)...
}
//memmove从"from"拷贝n个字节到"to"
func memmove(to, from unsafe.Pointer, n uintptr)
(1)如果channel==nil,block==true,调gopart()阻塞当前读取的g
(2)通道已关闭,并且没有缓冲值,直接返回true, false
(3)否则,如果有发送队列在等待,直接调recv进行接收
(4)通道未关闭,且缓冲有值,直接读缓冲队列的值
(5)通道没关闭又没有发送的g,且缓冲也是空的,则阻塞当前读取的g
(6)唤醒等待执行的其他g
读取也要注意:通道是nil的情况,直接读数据,会发生all goroutines are asleep - deadlock!的情况
func Test_deadlock2(t *testing.T) {// 未初始化的channel,直接读死锁var ch chan int<-ch//fatal error: all goroutines are asleep - deadlock!// 初始化无缓冲的channel,直接读死锁ch := make(chan int)val, ok := <-chfmt.Println(val, ok)// //fatal error: all goroutines are asleep - deadlock!
}
五、channel死锁问题
channel使用不当,很容易造成死锁的问题,死锁的本质是:all goroutines are asleep
1.未关闭channel
通道写完不关闭,容易造成死锁
func Test_deadlock5(t *testing.T) {// 初始化有缓冲的channel,先写后读,读完数据后死锁ch := make(chan int, 5)ch <- 1ch <- 2ch <- 3//range 是阻塞读for v := range ch {fmt.Println(v)}//1//2//3//fatal error: all goroutines are asleep - deadlock!
}
2.已关闭channel
正常使用channel,记得关闭通道
func Test_deadlock5(t *testing.T) {// 初始化有缓冲的channel,先写后读,读完数据后死锁ch := make(chan int, 5)ch <- 1ch <- 2ch <- 3close(ch)//range 是阻塞读for v := range ch {fmt.Println(v)}//1//2//3
}
3.读要先于发
思考为什么无缓冲 Channel 读要先于发?尝试从源码角度分析,因为无缓存channel的接收方会从发送方栈拷贝数据后,发送方才会被放回调度队列种,等待重新调度,如果一直没有读,发就一直卡住,无法被唤醒
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
...
recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
...
}//1.c通道
//2.发送方sg发送的值被放入通道中,发送方被唤醒,继续它的快乐之路
//3.接收方接收到的值(当前G)为写入ep
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {//无缓冲读if c.dataqsiz == 0 {...if ep != nil {// copy data from sender 接收是直接从发送的栈进行拷贝recvDirect(c.elemtype, sg, ep)}} else {//有缓冲读// 从缓存队列拷贝qp := chanbuf(c, c.recvx)...}gp.param = unsafe.Pointer(sg)...//唤醒g准备执行goready(gp, skip+1)
}func recvDirect(t *_type, sg *sudog, dst unsafe.Pointer) {// dst is on our stack or the heap, src is on another stack.// The channel is locked, so src will not move during this// operation.src := sg.elemtypeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.Size_)//从"from"拷贝n个字节到"to",from是src发送方,to就是dst接受方memmove(dst, src, t.Size_)
}
func memmove(to, from unsafe.Pointer, n uintptr)
4.panic问题
(1)已关闭的通道,再次关闭,会panic
(2)已关闭的通道,写入会panic
五、select
待续
六、总结
1.通道创建类型有3种:双向,只读,只写
2.通道有容量可设:无缓冲通道和有缓冲通道
3.通道读写互斥
4.已关闭通道,写panic,读有值,再关闭panic
5.通道通常需要2个g一起工作
相关文章:

go的通信Channel
一、channel是什么 1.一种通信机制 channel是goroutine与goroutine之间数据通信的一种通信机制。一般都是2个g及以上一起工作。 channel与关键字range和select紧密相关。 二、channel的结构 go源码:GitHub - golang/go: The Go programming language src/runt…...

手写红黑树【数据结构】
手写红黑树【数据结构】 前言版权推荐手写红黑树一、理论知识红黑树的特征增加删除 二、手写代码初始-树结点初始-红黑树初始-遍历初始-判断红黑树是否有效查找增加-1.父为黑,直接插入增加-2. 父叔为红,颜色调换增加-3. 父红叔黑,颜色调换&am…...

[蓝桥杯练习]通电
kruskal做法(加边) #include <bits/stdc.h> using namespace std; int x[10005],y[10005],z[10005];//存储i点的x与y坐标 int bcj[10005];//并查集 struct Edge{//边 int v1,v2; double w; }edge[2000005]; int cmp(Edge a, Edge b){return a.w < b.w;} int find(i…...
安全算法 - 摘要算法
摘要算法是一种将任意长度的数据转换为固定长度字节串的算法。它具有以下特点和应用。 首先,摘要算法能够生成一个唯一且固定长度的摘要值,用于验证数据的完整性和一致性。无论输入数据有多长,生成的摘要值始终是固定长度的,且即…...

操作系统:动静态库
目录 1.动静态库 1.1.如何制作一个库 1.2.静态库的使用和管理 1.3.安装和使用库 1.4.动态库 1.4.1.动态库的实现 1.4.2.动态库与静态库的区别 1.4.3.共享动态库给系统的方法 2.动态链接 2.1.操作系统层面的动态链接 1.动静态库 静态库(.a)&…...

车载电子电器架构 —— 局部网络管理汇总
车载电子电器架构 —— 局部网络管理汇总 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明…...

网络安全 | 什么是DDoS攻击?
关注WX:CodingTechWork DDoS-介绍 DoS:Denial of Service,拒绝服务。DDoS是通过大规模的网络流量使得正常流量不能访问受害者目标,是一种压垮性的网络攻击,而不是一种入侵手段。NTP网络时间协议,设备需要…...
[Godot] 3D拾取
CollisionObject3D文档 Camera3D文档 CollisionObject3D有个信号_input_event,可以用于处理3D拾取。 Camera3D也有project_position用于将屏幕空间坐标投影到3D空间。 extends Node3D#是否处于选中状态 var selected : bool false #摄像机的前向量 var front : V…...

知识融合:知识图谱构建的关键技术
目录 一、引言二、知识图谱基础2.1 知识表示三元组属性图 2.2 知识抽取实体抽取关系抽取属性抽取 三、知识融合的核心问题3.1 实体识别与链接实体识别实体链接 3.2 重复实体合并方法示例 3.3 关系融合挑战方法示例 四、知识融合技术深度解析4.1 基于规则的方法规则设计原则规则…...

外贸建站:WordPress搭建外贸独立站零基础自建站完整教程(2024)
对于做外贸来说,拥有自己的外贸独立网站真的非常重要。在外贸领域,如今各平台竞争激烈,规则多,成本高,价格战、政策变化快,还存在封店风险等等因素。在这种情况下,拥有外贸独立站就能很好规避上…...

【教程】Kotlin语言学习笔记(五)——Lambda表达式与条件控制
写在前面: 如果文章对你有帮助,记得点赞关注加收藏一波,利于以后需要的时候复习,多谢支持! 【Kotlin语言学习】系列文章 第一章 《认识Kotlin》 第二章 《数据类型》 第三章 《数据容器》 第四章 《方法》 第五章 《L…...

C++的并发世界(三)——线程对象生命周期
0.案例代码 先看下面一个例子: #include <iostream> #include <thread>void ThreadMain() {std::cout << "begin sub thread:" << std::this_thread::get_id()<<std::endl;for (int i 0; i < 10; i){std::cout <&…...

SAD法(附python实现)和Siamese神经网络计算图像的视差图
1 视差图 视差图:以左视图视差图为例,在像素位置p的视差值等于该像素在右图上的匹配点的列坐标减去其在左图上的列坐标 视差图和深度图: z f b d z \frac{fb}{d} zdfb 其中 d d d 是视差, f f f 是焦距, b b…...

基于DWT(离散小波变换)的图像加密水印算法,Matlab实现
博主简介: 专注、专一于Matlab图像处理学习、交流,matlab图像代码代做/项目合作可以联系(QQ:3249726188) 个人主页:Matlab_ImagePro-CSDN博客 原则:代码均由本人编写完成,非中介,提供…...

【威胁情报综述阅读3】Cyber Threat Intelligence Mining for Proactive Cybersecurity Defense
【威胁情报综述阅读1】Cyber Threat Intelligence Mining for Proactive Cybersecurity Defense: A Survey and New Perspectives 写在最前面一、介绍二、网络威胁情报挖掘方法和分类A. 研究方法1) 第 1 步 - 网络场景分析:2) 第 2 步 - 数据…...

在编程中使用中文到底该不该??
看到知乎上有个热门问题,为什么很多人反对中文在编程中的使用? 这个问题有几百万的浏览热度,其中排名第一的回答非常简洁,我深以为然: 在国内做开发,用中文写注释、写文档,是非常好的习惯&…...
PyQt6从入门到放弃
PyQt6从入门到放弃 安装PyQt6 pip install PyQt6# 查看QT和PyQT的版本 from PyQt6.QtCore import QT_VERSION_STR from PyQt6.QtCore import PYQT_VERSION_STR print(QT_VERSION_STR) print(PYQT_VERSION_STR)PyQt6模块 PyQt6类由一系列模块组成包括QtCore、QtGui、QtWidgets…...
PhpWord导入试卷
规定word导入格式 1、[单选题][2024][一般]题目1 A.选项1 B.选项2 C.选项3 D.选项4 答案:D 试题图片(上传多媒体图片): 分数:2 答案解析: 2、[多选题][2024][困难]题目2 A.选项1 B.选项2 C.选项3 D.选项4 E…...
C# 运算符重载 之前的小总结
C# 中支持运算符重载,所谓运算符重载就是我们可以使用自定义类型来重新定义 C# 中大多数运算符的功能。运算符重载需要通过 operator 关键字后跟运算符的形式来定义的,我们可以将被重新定义的运算符看作是具有特殊名称的函数,与其他函数一样&…...

XenCenter 2024 创建一个虚拟机
前言 实现,创建一个虚拟机,内存,cpu,磁盘,名称,网卡,配置 Xen Center 2024 download 创建虚拟机 选择系统类型 定义虚拟机名称 选择ISO镜像库 选择主服务器 分配虚拟机内存,cpu资源…...

IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
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…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...

排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...
redis和redission的区别
Redis 和 Redisson 是两个密切相关但又本质不同的技术,它们扮演着完全不同的角色: Redis: 内存数据库/数据结构存储 本质: 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能: 提供丰…...

沙箱虚拟化技术虚拟机容器之间的关系详解
问题 沙箱、虚拟化、容器三者分开一一介绍的话我知道他们各自都是什么东西,但是如果把三者放在一起,它们之间到底什么关系?又有什么联系呢?我不是很明白!!! 就比如说: 沙箱&#…...

VisualXML全新升级 | 新增数据库编辑功能
VisualXML是一个功能强大的网络总线设计工具,专注于简化汽车电子系统中复杂的网络数据设计操作。它支持多种主流总线网络格式的数据编辑(如DBC、LDF、ARXML、HEX等),并能够基于Excel表格的方式生成和转换多种数据库文件。由此&…...