【面试题】Golang 之Channel底层原理 (第三篇)

news/2024/9/3 6:35:23/ 标签: golang, 开发语言, 后端

目录

1.常见channel三大坑:死锁、内存泄漏、panic

1.死锁

1.只有生产者,没有消费者,或者反过来

2 生产者和消费者出现在同一个 goroutine 中

3 buffered channel 已满,且在同一个goroutine中

2.内存泄露

1 如何实现 goroutine 泄漏呢?

2 生产者阻塞导致泄漏

3 消费者阻塞导致泄漏

4 如何预防内存泄漏

3.panic

1 向已经 close 掉的 channel 继续发送数据

2 多次 close 同一个 channel

3 如何优雅地 close channel

1 需要检查 channel 是否关闭吗?

2 需要 close 吗?为什么?

3 谁来关?

2.channel底层原理

3.channel为什么线程安全?

4.Go channel如何控制goroutine并发执行顺序?

5.Go channel共享内存有什么优劣势?

6.Go语言中Channel缓冲有什么特点?

1. 容量固定

2. 非阻塞发送与接收

3. 阻塞发送与接收

4. 异步通信与解耦

5. 性能提升

6. 注意事项

7.channel 中的ring buffer实现

8.channel有无缓冲的区别

一、缓冲区大小

二、通信机制

三、适用场景

四、性能影响


1.常见channel三大坑:死锁、内存泄漏、panic

在使用 channel 进行 goroutine 之间的通信时,有时候场面会变得十分复杂,以至于写出难以觉察、难以定位的偶现 bug,而且上线的时候往往跑得好好的,直到某一天深夜收到服务挂了、OOM 了之类的告警…… 来梳理一下使用 channel 中常见的三大坑:panic、死锁、内存泄漏,做到防患于未然。

1.死锁

go 语言新手在编译时很容易碰到这个死锁的问题:

fatal error: all goroutines are asleep - deadlock!

这个就是喜闻乐见的「死锁」了…… 在操作系统中,学过「死锁」就是两个线程互相等待,耗在那里,最后程序不得不终止。

go 语言中的「死锁」也是类似的,两个 goroutine 互相等待,导致程序耗在那里,无法继续跑下去。看了很多死锁的案例后,channel 导致的死锁可以归纳为以下几类案例(先讨论 unbuffered channel 的情况)

1.只有生产者,没有消费者,或者反过来

channel 的生产者和消费者必须成对出现,如果缺乏一个,就会造成死锁,例如:

// 只有生产者,没有消费者
func f1() {ch := make(chan int)ch <- 1
}
​
// 只有消费者,没有生产者
func f2() {ch := make(chan int)<-ch
}

2 生产者和消费者出现在同一个 goroutine 中

除了需要成对出现,还需要出现在不同的 goroutine 中,例如:

// 同一个 goroutine 中同时出现生产者和消费者
func f3() {ch := make(chan int)ch <- 1  // 由于消费者还没执行到,这里会一直阻塞住<-ch
}
​

对于 buffered channel 则是下面这种情况

3 buffered channel 已满,且在同一个goroutine中

buffered channel 会将收到的元素先存在 hchan 结构体的 ringbuffer 中,继而才会发生阻塞。而当发生阻塞时,如果阻塞了主 goroutine ,则也会出现死锁

所以实际使用中,推荐尽量使用 buffered channel ,使用起来会更安全,在下文的「内存泄漏」相关内容也会提及。

2.内存泄露

内存泄漏一般都是通过 OOM(Out of Memory) 告警或者发布过程中对内存的观察发现的,服务内存往往都是缓慢上升,直到被系统 OOM 掉清空内存再周而复始。

在 go 语言中,错误地使用 channel 会导致 goroutine 泄漏,进而导致内存泄漏。

1 如何实现 goroutine 泄漏呢?

不会修 bug,还不会写 bug 吗?让 goroutine 泄漏的核心就是:

生产者/消费者 所在的 goroutine 已经退出,而其对应的 消费者/生产者 所在的 goroutine 会永远阻塞住,直到进程退出

2 生产者阻塞导致泄漏

一般会用 channel 来做一些超时控制,例如下面这个例子:

func leak1() {ch := make(chan int)// g1go func() {time.Sleep(2 * time.Second) // 模拟 io 操作ch <- 100                   // 模拟返回结果}()
​// g2// 阻塞住,直到超时或返回select {case <-time.After(500 * time.Millisecond):fmt.Println("timeout! exit...")case result := <-ch:fmt.Printf("result: %d\n", result)}
}
​

这里用 goroutine g1 来模拟 io 操作,主 goroutine g2 来模拟客户端的处理逻辑,

(1)假设客户端超时为 500ms,而实际请求耗时为 2s,则 select 会走到 timeout 的逻辑,这时 g2 退出,channel ch 没有消费者,会一直在等待状态,输出如下:

Goroutine num: 1 timeout! exit... Goroutine num: 2

如果这是在 server 代码中,这个请求处理完后,g1 就会挂起、发生泄漏了,就等着 OOM 吧 。

(2)假设客户端超时调整为 5000ms,实际请求耗时 2s,则 select 会进入获取 result 的分支,输出如下:

Goroutine num: 1 result: 100 Goroutine num: 1

3 消费者阻塞导致泄漏

如果生产者不继续生产,消费者所在的 goroutine 也会阻塞住,不会退出,例如:

func leak2() {ch := make(chan int)
​// 消费者 g1go func() {for result := range ch {fmt.Printf("result: %d\n", result)}}()
​// 生产者 g2ch <- 1ch <- 2time.Sleep(time.Second)  // 模拟耗时fmt.Println("main goroutine g2 done...")
}
​

这种情况下,只需要增加 close(ch) 的操作即可,for-range 操作在收到 close 的信号后会退出、goroutine 不再阻塞,能够被回收。

4 如何预防内存泄漏

预防 goroutine 泄漏的核心就是:创建 goroutine 时就要想清楚它什么时候被回收。

具体到执行层面,包括:

当 goroutine 退出时,需要考虑它使用的 channel 有没有可能阻塞对应的生产者、消费者的 goroutine; 尽量使用 buffered channel使用 buffered channel 能减少阻塞发生、即使疏忽了一些极端情况,也能降低 goroutine 泄漏的概率;

3.panic

panic 就更刺激了,一般是测试的时候没发现,上线之后偶现,程序挂掉,服务出现一个超时毛刺后触发告警。channel 导致的 panic 一般是以下几个原因:

1 向已经 close 掉的 channel 继续发送数据

先举一个简单的栗子:

func p1() {ch := make(chan int, 1)close(ch)ch <- 1
}
// panic: send on closed channel
​

在实际开发过程中,处理多个 goroutine 之间协作时,可能存在一个 goroutine 已经 close 掉 channel 了,另外一个不知道,也去 close 一下,就会 panic 掉,例如:

func p1() {ch := make(chan int, 1)done := make(chan struct{}, 1)go func() {<- time.After(2*time.Second)println("close2")close(ch)close(done)}()go func() {<- time.After(1*time.Second)println("close1")ch <- 1close(ch)}()
​<-done
}
​

万恶之源就是在 go 语言里,是无法知道一个 channel 是否已经被 close 掉的,所以在尝试做 close 操作的时候,就应该做好会 panic 的准备……

2 多次 close 同一个 channel

同上,在尝试往 channel 里发送数据时,就应该考虑

这个 channel 已经关了吗? 这个 channel 什么时候、在哪个 goroutine 里关呢? 谁来关呢?还是干脆不关?

3 如何优雅地 close channel

1 需要检查 channel 是否关闭吗?

刚遇到上面说的 panic 问题时,也试过去找一个内置的 closed 函数来检查关闭状态,结果发现,并没有这样一个函数……

那么,如果有这样的函数,真能彻底解决 panic 的问题么?答案是不能。因为 channel 是在一个并发的环境下去做收发操作,就算当前执行 closed(ch) 得到的结果是 false,还是不能直接去关,例如代码:

if !closed(ch) { // 返回 false // 在这中间出了幺蛾子! close(ch) // 还是 panic 了…… }

遵循 less is more 的原则,这个 closed 函数是要不得了

2 需要 close 吗?为什么?

结论:除非必须关闭 chan,否则不要主动关闭。关闭 chan 最优雅的方式,就是不要关闭 chan~

当一个 chan 没有 sender 和 receiver 时,即不再被使用时,GC 会在一段时间后标记、清理掉这个 chan。那么什么时候必须关闭 chan 呢?

比较常见的是将 close 作为一种通知机制,尤其是生产者与消费者之间是 1:M 的关系时,通过 close 告诉下游:我收工了,你们别读了。

3 谁来关?

chan 关闭的原则:

Don’t close a channel from the receiver side 不要在消费者端关闭 chan Don’t close a channel if the channel has multiple concurrent senders 有多个并发写的生产者时也别关 只要遵循这两条原则,就能避免两种 panic 的场景,即:向 closed chan 发送数据,或者是 close 一个 closed chan。

按照生产者和消费者的关系可以拆解成以下几类情况:

一写一读:生产者关闭即可 一写多读:生产者关闭即可,关闭时下游全部消费者都能收到通知 多写一读:多个生产者之间需要引入一个协调 channel 来处理信号 多写多读:与 3 类似,核心思路是引入一个中间层以及使用 try-send 的套路来处理非阻塞的写入.

代码示例:

func main() {rand.Seed(time.Now().UnixNano())log.SetFlags(0)const Max = 100000const NumReceivers = 10const NumSenders = 1000wgReceivers := sync.WaitGroup{}wgReceivers.Add(NumReceivers)dataCh := make(chan int)stopCh := make(chan struct{})// stopCh 是额外引入的一个信号 channel.// 它的生产者是下面的 toStop channel,// 消费者是上面 dataCh 的生产者和消费者toStop := make(chan string, 1)// toStop 是拿来关闭 stopCh 用的,由 dataCh 的生产者和消费者写入// 由下面的匿名中介函数(moderator)消费// 要注意,这个一定要是 buffered channel (否则没法用 try-send 来处理了)var stoppedBy string// moderatorgo func() {stoppedBy = <-toStopclose(stopCh)}()// sendersfor i := 0; i < NumSenders; i++ {go func(id string) {for {value := rand.Intn(Max)if value == 0 {// try-send 操作// 如果 toStop 满了,就会走 default 分支啥也不干,也不会阻塞select {case toStop <- "sender#" + id:default:}return}// try-receive 操作,尽快退出// 如果没有这一步,下面的 select 操作可能造成 panicselect {case <- stopCh:returndefault:}// 如果尝试从 stopCh 取数据的同时,也尝试向 dataCh// 写数据,则会命中 select 的伪随机逻辑,可能会写入数据select {case <- stopCh:returncase dataCh <- value:}}}(strconv.Itoa(i))}// receiversfor i := 0; i < NumReceivers; i++ {go func(id string) {defer wgReceivers.Done()for {// 同上select {case <- stopCh:returndefault:}// 尝试读数据select {case <- stopCh:returncase value := <-dataCh:if value == Max-1 {select {case toStop <- "receiver#" + id:default:}return}log.Println(value)}}}(strconv.Itoa(i))}wgReceivers.Wait()log.Println("stopped by", stoppedBy)
}
​

2.channel底层原理

1) 概念 Go 中channel 是一个先进先出(FIFO)的队列,负责协程之间的通信(Go语言提倡不要通过共享内存来通信,而要通过通信的方式实现共享内存),其中CSP并发模型就是通过goroutine 和 channel来实现的。

2) 使用场景

停止信号监听、定时任务、生产方与消费方解耦、控制并发数

3) 底层数据结构 channel 的整体结构

简单说明:

buf是有缓冲的channel所特有的结构,用来存储缓存数据。是个循环链表 sendx和recvx用于记录buf这个循环链表中的发送或者接收的index lock是个互斥锁。 recvq和sendq分别是接收(<-channel)或者发送(channel <- xxx)的goroutine抽象出来的结构体(sudog)的队列。是个双向链表 源码位于/runtime/chan.go中。结构体为hchan。

type hchan struct {closed uint32        // 标识关闭状态:表示当前通道是否处于关闭状态。创建通道后,该字段设置为0,即通道打开; 通过调用close将其设置为1,通道关闭。qcount uint          // 当前队列列中剩余元素个数dataqsiz uint        // 环形队列长度,即可以存放的元素个数即缓冲区的大小,即make(chan T,N),N.buf unsafe.Pointer   // 环形队列列指针,ring buffer 环形队列elemsize uint16      // 每个元素的⼤⼩elemtype *_type      // 元素类型:用于数据传递过程中的赋值;sendx uint           // 队列下标,指示元素写⼊入时存放到队列列中的位置 xrecvx uint           // 队列下标,指示元素从队列列的该位置读出  recvq waitq          // 等待读消息的goroutine队列sendq  waitq         // 等待写消息的goroutine队列lock mutex           // 互斥锁,chan不允许并发读写
} type waitq struct {first *sudoglast  *sudog
}

从数据结构可以看出channel由队列、类型信息、goroutine等待队列组成。

4) 实现方式 创建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:指从该位置读取数据

3.channel为什么线程安全?

Go channel是线程安全的,因为它内部实现了同步机制。

当一个goroutine向channel中写入数据时,如果channel已满,则该goroutine会被阻塞,直到有其他goroutine从channel中取出数据为止;

反之,当一个goroutine从channel中取出数据时,如果channel为空,则该goroutine会被阻塞,直到有其他goroutine向channel中写入数据为止。

这种同步机制可以保证在多个goroutine同时操作同一个channel时,数据的读写是安全的,不会出现数据竞争等问题。因此,Go channel在并发编程中被广泛使用,它是一种高效、简单而又安全的并发通信机制。

4.Go channel如何控制goroutine并发执行顺序?

Go channel可以用来控制goroutine的并发执行顺序,通过channel的特性可以实现同步和异步的调用方式,从而控制goroutine的执行顺序。

同步调用:使用无缓冲的channel,当goroutine A向channel发送数据时会阻塞,直到有goroutine B从channel接收数据,这样就能保证goroutine A在goroutine B执行完之后再执行。 异步调用:使用有缓冲的channel,可以让多个goroutine同时向channel发送数据,然后再由其他goroutine从channel接收数据。这样可以实现并发的执行顺序。 另外,可以使用select语句来控制多个channel的并发执行顺序,以及使用sync包中的WaitGroup来等待所有goroutine执行完毕再继续执行下一步操作。

5.Go channel共享内存有什么优劣势?

Go语言中的Channel是一种用于在不同Goroutine之间进行通信和同步的机制,它可以看作是一种共享内存的方式。Channel的优势在于:

线程安全:Go语言中的Channel是线程安全的,在并发编程中可以有效避免竞态条件和锁问题。 同步性:使用Channel可以实现两个Goroutine之间的同步,一个Goroutine在读取Channel中的数据时,会一直等待直到有写入操作,这种同步性对于一些并发编程任务非常有用。 协作性:使用Channel可以协调多个Goroutine的执行,通过传递消息来控制它们执行的顺序和方式。 但是,Channel也有一些劣势:

限制性:Channel只能用于同一个进程内部的Goroutine之间通信,无法用于多个不同进程之间的通信。 主动性:Channel的读取和写入都是被动的,即读取方必须等待写入方写入数据。 内存消耗:Channel会消耗一定的内存,如果不及时关闭Channel,可能会造成内存泄漏。 因此,在实际应用中,我们需要根据具体场景来选择使用Channel还是其他的共享内存方式。

在使用channel有这么几点要注意

确保所有数据发送完后再关闭channel,由发送方来关闭 不要重复关闭channel 不要向为nil的channel里面发送值 不要向为nil的channel里面接收值 接收数据时,可以通过返回值判断是否ok n , ok := <- c 这样防止channel被关闭后返回了零值,对业务造成影响

6.Go语言中Channel缓冲有什么特点?

Go语言中的Channel缓冲具有以下几个显著特点:

1. 容量固定

  • 定义容量:缓冲Channel在创建时需要指定一个容量,这个容量表示Channel可以存储的元素数量。例如,ch := make(chan int, 3)创建了一个容量为3的整型缓冲Channel。
  • 固定性:一旦Channel的容量被设定,它就是固定的,不能更改。

2. 非阻塞发送与接收

  • 非阻塞发送:当向缓冲Channel发送元素时,如果缓冲区未满(还有剩余容量),发送操作会立即完成,并将元素存储在缓冲区中。这使得发送操作不会阻塞,即使没有接收方也可以继续发送元素。
  • 非阻塞接收:当从缓冲Channel接收元素时,如果缓冲区非空(有元素可用),接收操作会立即完成,并将缓冲区中的元素传递给接收方。这使得接收操作不会阻塞,即使没有发送方也可以继续接收元素。

3. 阻塞发送与接收

  • 阻塞发送:当缓冲Channel的缓冲区已满时,继续向通道发送元素会导致发送操作阻塞,直到有接收方从通道中接收元素,腾出缓冲区空间。
  • 阻塞接收:当缓冲Channel的缓冲区为空时,从通道接收元素会导致接收操作阻塞,直到有发送方向通道发送元素。

4. 异步通信与解耦

  • 异步通信:使用缓冲Channel可以实现异步通信,发送和接收操作可以在不同的时间进行,只要缓冲区有足够的空间或者有元素可用。
  • 时间解耦:缓冲Channel解耦了发送方和接收方的时间,使得它们不需要同时准备好即可进行通信。

5. 性能提升

  • 避免阻塞:缓冲Channel可以减少因等待对方准备好而导致的阻塞,从而提高程序的执行效率。
  • 高效数据传输:在并发场景下,缓冲Channel可以存储一定量的数据,从而避免发送方因等待接收方而浪费资源,提高了数据传输的效率。

6. 注意事项

  • 同步发送与接收:虽然缓冲Channel可以实现异步通信,但在发送和接收数据时仍需遵循同步原则,即发送方在发送完数据后应等待接收方接收完毕,以避免数据竞争等问题。
  • 避免死锁:在使用缓冲Channel时,需要确保发送和接收的操作是匹配的,否则可能会导致死锁或其他并发问题。

综上所述,Go语言中的Channel缓冲通过其容量固定、非阻塞/阻塞发送与接收、异步通信与解耦等特点,为并发编程提供了强大的支持。

7.channel 中的ring buffer实现

channel 中使用了 ring buffer(环形缓冲区) 来缓存写入的数据。ring buffer 有很多好处,而且非常适合用来实现 FIFO 式的固定长度队列。 在 channel 中,ring buffer 的实现如下:

hchan 中有两个与 buffer 相关的变量:recvx 和 sendx。其中 sendx 表示 buffer 中可写的 index,recvx 表示 buffer 中可读的 index。 从 recvx 到 sendx 之间的元素,表示已正常存放入 buffer 中的数据。 我们可以直接使用 buf[recvx]来读取到队列的第一个元素,使用 buf[sendx] = x 来将元素放到队尾。

8.channel有无缓冲的区别

Go语言中的Channel有无缓冲的区别主要体现在以下几个方面:

一、缓冲区大小

  • 无缓冲Channel:其缓冲区大小为0,即不能存储任何数据。这意味着数据必须即时从发送方传输到接收方,不能有任何延迟。
  • 有缓冲Channel:其缓冲区大小可以设定(通常大于0),用于存储待传输的数据。这意味着发送方可以在缓冲区未满的情况下继续发送数据,而接收方也可以在缓冲区非空的情况下继续接收数据。

二、通信机制

  • 无缓冲Channel:要求发送和接收操作同步进行。即发送方发送数据的操作必须与接收方接收数据的操作同时发生,否则两者都会进入阻塞状态。这种机制保证了数据的即时传输,但可能会限制程序的并发性能。
  • 有缓冲Channel:允许发送和接收操作在一定程度上解耦。发送方可以在缓冲区未满时继续发送数据,而无需等待接收方立即接收;同样,接收方也可以在缓冲区非空时继续接收数据,而无需等待发送方发送新数据。这种机制提高了程序的并发性能,但也可能导致数据在缓冲区中滞留过久。

三、适用场景

  • 无缓冲Channel:适用于对实时性要求极高、需要即时响应的场景。例如,实时通信、实时数据处理等场景,这些场景要求数据必须立即传输和处理,不能有任何延迟。
  • 有缓冲Channel:适用于对实时性要求相对较低、但需要提高并发性能的场景。例如,大规模数据传输、需要保持数据同步的系统等场景,这些场景允许数据在缓冲区中暂时滞留,以换取更高的并发性能和吞吐量。

四、性能影响

  • 无缓冲Channel:由于要求发送和接收操作同步进行,因此可能会限制程序的并发性能。但是,由于数据即时传输,因此可以减少因数据滞留而导致的延迟。
  • 有缓冲Channel:通过允许发送和接收操作解耦,可以提高程序的并发性能。但是,如果缓冲区设置不当(如过大或过小),可能会导致资源浪费或数据滞留过久的问题。因此,在设置缓冲区大小时需要根据具体场景进行权衡。

综上所述,Go语言中的Channel有无缓冲的区别主要体现在缓冲区大小、通信机制、适用场景以及性能影响等方面。开发者在选择使用哪种类型的Channel时需要根据具体的应用需求和系统约束进行权衡和选择。


http://www.ppmy.cn/news/1475473.html

相关文章

UE5 04-重新加载当前场景

在Unreal Engine 5 (UE5) 中&#xff0c;重新加载当前场景可以通过编程来实现。以下是一个简单的示例代码&#xff0c;展示了如何在UE5中重新加载当前场景&#xff1a; #include "Engine.h" #include "EngineUtils.h" #include "Kismet/GameplaySt…

Docker 容器内的php 安装redis扩展

1、https://pecl.php.net/package/redis 下载redis扩展 2、解压redis扩展包&#xff0c;然后通过命令拷贝到php容器 docker cp ~/nginx/redis-4.3.0/* myphp-fpm:/usr/src/php/ext/redis/ myphp-fpm是你的php容器 &#xff5e;/nginx/redis**** 是redi扩展包路径 3、进入php容…

【大模型LLM面试合集】大语言模型基础_Word2Vec

Word2Vec 文章来源&#xff1a;Word2Vec详解 - 知乎 (zhihu.com) 1.Word2Vec概述 Word2Vec是google在2013年推出的一个NLP工具&#xff0c;它的特点是能够将单词转化为向量来表示&#xff0c;这样词与词之间就可以定量的去度量他们之间的关系&#xff0c;挖掘词之间的联系。 …

超市管理系统 需求分析与设计 UML 方向

一、项目介绍 1.1项目背景 随着经济一体化和电子商务的迅速发展&#xff0c;网络传播信息的速度打破了传统信息传递的模式&#xff0c;互联网的高速发展和计算机应用在各个高校进展迅速&#xff0c;更多信息化产品的突飞猛进&#xff0c;让现代的管理模式也发生了巨大的变化&…

永磁同步电机控制算法--基于 SVM 的无磁链环 DTC

永磁同步电机无磁链环 DTC 通过控制定子磁链交轴分量来直接控制转矩&#xff0c;不再要求控制磁链幅值恒定&#xff0c;省去了传统 DTC 中的磁链环&#xff0c;不仅转矩响应更快&#xff0c;有效抑制了转矩脉动&#xff0c;而且提高了电机功率因数。但无磁链环 DTC 方案仍采用传…

vmware 虚拟机扩容 centos 硬盘扩容 kylinos v10扩容

1. 虚拟机先扩容 1.1 关机&#xff0c;并点击系统&#xff0c;让他是点选状态&#xff0c;但是没开机 1.2 右击&#xff0c;点击最下方设置&#xff0c;点击硬盘 1.3 点击扩展磁盘 1.4 选择你需要扩容的大小&#xff0c;数字为总大小 完成提示&#xff1a; 磁盘已成功扩展。您…

「Pytorch」roLabelImg 图像异常旋转 bug

在进行Yolo-obb 模型训练的时候需要标注旋转框&#xff0c;roLabelImg 是比较推荐的一款旋转框标注工具&#xff0c;既可以标注正常的矩形框&#xff0c;还可以标注旋转框 roLabelImg Github 地址&#xff1a;https://github.com/HumanSignal/labelImg 但是在使用过程中遇到了…

策略模式适用场景与具体实例解析

策略模式在多种场合下都能发挥其优势&#xff0c;尤其在需要根据不同条件或策略选择不同算法的场景中。下面是几个具体的适用场景及其对应的实例&#xff0c;以帮助进一步理解策略模式的实际应用。 1. 支付方式选择 在电子商务网站中&#xff0c;用户可以选择多种支付方式&am…

UDP 报文结构与注意事项全解析

在网络通信中&#xff0c;UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;是一种无连接、不可靠的传输层协议。尽管它不如 TCP 那样提供可靠的传输服务&#xff0c;但在某些特定场景中&#xff0c;UDP 因其简单高效而备受青睐。 一、UDP 报文结…

WPF学习(3) -- 控件模板

一、操作过程 二、代码 <Window x:Class"学习.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schemas.microsoft.com/expressio…

.NET MAUI开源架构_1.学习资源分享

最近需要开发Android的App&#xff0c;想预研下使用.NET开源架构.NET MAUI来开发App程序。因此网上搜索了下相关资料&#xff0c;现在把我查询的结果记录下&#xff0c;方便后面学习。 1.官方文档 1.1MAUI官方学习网站 .NET Multi-Platform App UI 文档 - .NET MAUI | Micro…

上传图片到腾讯云和wangeditor的图片上传到腾讯云

1.创建src/utils/upload-file.js文件 import COS from cos-js-sdk-v5 import SparkMD5 from spark-md5 import { cosTmpsecret, cosConfig } from /api/upload // 通过后台获取临时密钥 let key // 配置 // const cosConfig { // // Bucket: xlcp-tong-1253334579, // …

WPF学习(6) -- WPF命令和通知

一 、WPF命令 1.ICommand代码 创建一个文件夹和文件 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input;namespace 学习.Command {public class MyCommand : ICommand{Acti…

旅游景区度假村展示型网站如何建设渠道品牌

景区、度假村、境外旅游几乎每天的人流量都非常高&#xff0c;还包括本地附近游等&#xff0c;对景区及度假村等固定高流量场所&#xff0c;品牌和客户赋能都是需要完善的&#xff0c;尤其是信息展示方面&#xff0c;旅游客户了解前往及查看信息等。 通过雨科平台建设景区度假…

本地部署,APISR: 动漫超分辨率技术

目录 引言 技术背景 APISR 的架构与原理 APISR 的主要特点 应用实例 本地部署 运行结果 结论 参考文献 GitHub - Kiteretsu77/APISR: APISR: Anime Production Inspired Real-World Anime Super-Resolution (CVPR 2024)APISR: Anime Production Inspired Real-World A…

百川工作手机实现销售管理微信监控系统

在瞬息万变的商业战场中&#xff0c;每一分效率的提升都是企业制胜的关键。传统销售管理模式已难以满足现代企业对精准、高效、合规的迫切需求。今天&#xff0c;让我们一同探索如何利用工作手机这一创新工具&#xff0c;为您的销售团队装上智能翅膀&#xff0c;开启销售管理的…

MySQL Binlog详解:提升数据库可靠性的核心技术

文章目录 1. 引言1.1 什么是MySQL Bin Log&#xff1f;1.2 Bin Log的作用和应用场景 2. Bin Log的基本概念2.1 Bin Log的工作原理2.2 Bin Log的三种格式 3. 配置与管理Bin Log3.1 启用Bin Log3.2 配置Bin Log参数3.3 管理Bin Log文件3.4 查看Bin Log内容3.5 使用mysqlbinlog工具…

张量笔记(4):张量网络

张量分解通常是将高维张量分解成一系列较低维的张量&#xff0c;表示能力相对较低。而张量网络可以表示复杂的高维数据结构&#xff0c;通过连接多个张量形成网络结构&#xff0c;可以更灵活地表示和处理复杂的数据关系。本节主要介绍HT和TT网络。 2.5.1 HT分解——首先我们引入…

Mac OS ssh 连接提示 Permission denied (publickey)

这错误有点奇葩&#xff0c;MacBook的IDE(vscode和pycharm)远程都连不上&#xff0c;terminal能连上&#xff0c;windows的pycharm能连上&#xff0c;见鬼了&#xff0c;所以肯定不是秘钥的问题了&#xff0c;查了好久竟然发现是权限的问题。。 chmod 400 ~/.ssh/id_rsa http…

兼容问题---ios底部的安全距离css设置

在H5上适配安全区域&#xff1a;采用viewportenvconstant方案。 具体操作如下&#xff1a; 1. 需要将viewport设置为cover&#xff0c;env和constant才能生效。设置代码如下&#xff1a; <meta name"viewport" content"widthdevice-width,initial-scale1.…