go语言并发的最佳实践
Go 语言的并发模型是其最强大的特性之一,基于 CSP(Communicating Sequential Processes)理论,通过 goroutine 和 channel 实现轻量级并发.
一、并发核心概念
1. Goroutine
在 Go 语言中,Goroutine 是实现并发编程的核心特性之一,它本质上是一种轻量级线程。与传统的操作系统线程相比,Goroutine 有着显著的优势。
- 轻量级线程,由 Go 运行时管理(非操作系统线程)。
- 启动成本低(KB 级栈内存,动态扩缩容),可轻松创建数千个。
在 Go 语言中,通过 go
关键字可以非常方便地启动一个 Goroutine。以下是一个简单的示例:
func main(){go func() {fmt.Println("Hello from goroutine!")}()// 这里需要注意,如果主函数直接结束,goroutine 可能来不及执行// 因为主函数结束会导致整个程序终止fmt.Println("Main function continues...")}
2. Channel
Channel 是 Go 语言中用于在不同 Goroutine 之间进行通信和同步的重要工具,它遵循 “通过通信共享内存” 的设计理念,避免了传统并发编程中使用共享内存带来的竞态问题。
- 是一种类型安全的管道,它只能传输特定类型的数据。例如,一个
chan int
类型的 Channel 只能用于传输整数类型的数据 - 避免共享内存的竞态问题,提倡“通过通信共享内存”。
- 分两种类型:
- 无缓冲通道(同步):发送和接收操作会阻塞,直到另一端准备好。
- 有缓冲通道(异步):缓冲区未满或非空时不会阻塞。
二、原理:GMP 调度模型
1. GMP 调度模型
基本概念
在 Go 语言的并发体系中,GMP 调度模型是其核心机制,它主要由三个关键组件构成:
- G(Goroutine):Goroutine 是 Go 语言中轻量级的执行单元,类似于传统操作系统中的线程,但它的开销要小得多。
- M(Machine):Machine 代表操作系统线程,它是真正在操作系统内核层面上执行的线程。每个 M 都与一个底层的操作系统线程绑定,负责执行 Goroutine。M 是与操作系统交互的桥梁,它负责处理系统调用、线程上下文切换等底层操作。
- P(Processor):Processor 是调度上下文,P 可以看作是 M 执行 Goroutine 的“许可证”,每个 M 要执行 Goroutine 必须先绑定一个 P。P 维护着一个本地的 Goroutine 队列,用于存储待执行的 Goroutine。同时,P 还负责调度和管理这些 Goroutine 的执行顺序。
工作流程
- P 维护本地 Goroutine 队列:每个 P 都有一个自己的本地 Goroutine 队列,当创建一个新的 Goroutine 时,它会被放入某个 P 的本地队列中。这个队列是一个先进先出(FIFO)的队列,P 会按照队列中的顺序依次调度 Goroutine 执行。
- M 绑定 P 后获取 G 执行:一个 M 要执行 Goroutine,必须先绑定一个空闲的 P。绑定成功后,M 会从 P 的本地队列中取出一个 Goroutine 并执行。当 M 执行完一个 Goroutine 后,会继续从 P 的队列中获取下一个 Goroutine,直到队列为空。
- G 阻塞时 M 释放 P:当一个 Goroutine 在执行过程中发生阻塞(例如进行 IO 操作)时,M 会释放当前绑定的 P,以便其他 M 可以使用这个 P 继续执行其他 Goroutine。被释放的 P 会被放入全局 P 列表中,等待其他 M 来绑定。而阻塞的 Goroutine 会被挂起,当阻塞操作完成后,它会被重新放入某个 P 的本地队列或全局队列中,等待再次被调度执行。
2. 抢占式调度
Go 1.14+ 支持基于信号的抢占,避免长时间占用 CPU 的 Goroutine 导致调度延迟。
三、Goroutine 基础
1. 简单示例
下面是一个简单的 Goroutine 示例,展示了如何启动一个 Goroutine 并执行一个简单的任务:
package mainimport ("fmt""time"
)func main() {go sayHello() // 启动 goroutine// 这里使用 time.Sleep 是为了让主函数等待一段时间,以便 goroutine 有机会执行// 但这种方式并不是一个好的做法,实际开发中应该使用 sync.WaitGroup 进行同步time.Sleep(100 * time.Millisecond)
}func sayHello() {fmt.Println("Hello!")
}
2. 使用 sync.WaitGroup
并发组
在实际开发中,为了确保所有的 Goroutine 都执行完毕后再继续执行后续的代码,我们可以使用 sync.WaitGroup
来进行同步。sync.WaitGroup
提供了三个主要的方法:Add
、Done
和 Wait
。
Add
方法用于设置需要等待的 Goroutine 的数量。Done
方法用于标记一个 Goroutine 已经执行完毕,相当于将等待的数量减 1。Wait
方法用于阻塞当前的 Goroutine,直到所有标记的 Goroutine 都执行完毕。
package mainimport ("fmt""sync"
)func main() {var wg sync.WaitGroupwg.Add(2) // 表示有两个任务需要等待go func() {defer wg.Done() // 在函数结束时调用 Done 方法,表示该任务完成fmt.Println("Goroutine 1")}()go func() {defer wg.Done()fmt.Println("Goroutine 2")}()wg.Wait() // 阻塞直到所有任务完成fmt.Println("All done!")
}
四、Channel 操作
1.无缓冲通道
创建和使用无缓冲通道的基本步骤如下:
package mainimport "fmt"func main() {ch := make(chan int) // 创建一个无缓冲的整数类型通道go func() {num := 42ch <- num // 向通道发送数据fmt.Println("Data sent to channel")}()value := <-ch // 从通道接收数据fmt.Println("Received value:", value)
}
2. 有缓冲通道
有缓冲通道的使用可以提高程序的并发性能,避免不必要的阻塞。以下是一个有缓冲通道的示例:
package mainimport "fmt"func main() {ch := make(chan string, 2) // 创建一个缓冲区容量为 2 的字符串类型通道ch <- "A"ch <- "B"fmt.Println("Data sent to channel")fmt.Println(<-ch) // 从通道接收第一个数据fmt.Println(<-ch) // 从通道接收第二个数据
}
3. 关闭通道
在使用通道时,有时需要通知接收方不再有数据发送,这时可以使用 close
函数关闭通道。关闭通道后,接收方仍然可以从通道中接收数据,直到通道中的数据被全部接收完,之后再接收数据会得到该类型的零值。以下是一个关闭通道的示例:
package mainimport "fmt"func main() {ch := make(chan int)go func() {for i := 0; i < 3; i++ {ch <- i}close(ch) // 关闭通道fmt.Println("Channel closed")}()for num := range ch { // 使用 for...range 循环从通道接收数据,直到通道关闭fmt.Println(num)}fmt.Println("All data received")
}
4. Select 多路复
select
语句用于在多个通道操作中进行选择,它类似于 switch
语句,但 select
语句专门用于处理通道操作。select
语句会随机选择一个可以执行的通道操作并执行,如果所有通道操作都无法执行,则会阻塞,直到有一个通道操作可以执行。如果指定了 default
分支,则在所有通道操作都无法执行时会执行 default
分支,不会阻塞。以下是一个使用 select
语句进行多路复用的示例:
package mainimport ("fmt""time"
)func main() {ch1 := make(chan string)ch2 := make(chan string)go func() {time.Sleep(200 * time.Millisecond)ch1 <- "one"}()go func() {time.Sleep(100 * time.Millisecond)ch2 <- "two"}()select {case msg1 := <-ch1:fmt.Println(msg1)case msg2 := <-ch2:fmt.Println(msg2)case <-time.After(1 * time.Second): // 超时控制fmt.Println("timeout")}
}
五、同步原语
1. 互斥锁 sync.Mutex
在并发编程中,多个 Goroutine 同时访问和修改共享资源可能会导致数据不一致的问题,这时可以使用互斥锁 sync.Mutex
来保证同一时间只有一个 Goroutine 可以访问共享资源。互斥锁提供了两个主要的方法:Lock
和 Unlock
。
Lock
方法用于获取锁,如果锁已经被其他 Goroutine 持有,则当前 Goroutine 会阻塞,直到锁被释放。Unlock
方法用于释放锁,允许其他 Goroutine 获取锁。
以下是一个使用互斥锁的示例:
package mainimport ("fmt""sync"
)var counter int
var mu sync.Mutexfunc increment() {mu.Lock() // 获取锁defer mu.Unlock() // 确保在函数结束时释放锁counter++
}func main() {var wg sync.WaitGroupfor i := 0; i < 1000; i++ {wg.Add(1)go func() {defer wg.Done()increment()}()}wg.Wait() // 等待所有 Goroutine 执行完毕fmt.Println(counter) // 输出 1000
}
2. 读写锁 sync.RWMutex
读写锁 sync.RWMutex
是一种特殊的锁,它允许多个 Goroutine 同时进行读操作,但在进行写操作时会独占锁,不允许其他 Goroutine 进行读或写操作。读写锁适用于读多写少的场景,可以提高程序的并发性能。读写锁提供了四个主要的方法:RLock
、RUnlock
、Lock
和 Unlock
。
RLock
方法用于获取读锁,允许多个 Goroutine 同时持有读锁。RUnlock
方法用于释放读锁。Lock
方法用于获取写锁,在持有写锁期间,不允许其他 Goroutine 进行读或写操作。Unlock
方法用于释放写锁。
以下是一个使用读写锁的示例:
package mainimport ("fmt""sync"
)var cache = make(map[string]string)
var rwMu sync.RWMutexfunc read(key string) string {rwMu.RLock() // 获取读锁defer rwMu.RUnlock() // 确保在函数结束时释放读锁return cache[key]
}func write(key, value string) {rwMu.Lock() // 获取写锁defer rwMu.Unlock() // 确保在函数结束时释放写锁cache[key] = value
}func main() {var wg sync.WaitGroup// 启动多个读操作for i := 0; i < 5; i++ {wg.Add(1)go func(index int) {defer wg.Done()key := fmt.Sprintf("key%d", index)value := read(key)fmt.Printf("Read key %s: %s\n", key, value)}(i)}// 启动一个写操作wg.Add(1)go func() {defer wg.Done()key := "key1"value := "value1"write(key, value)fmt.Printf("Written key %s: %s\n", key, value)}()wg.Wait() // 等待所有 Goroutine 执行完毕
}
六、并发模式
1. Worker Pool(工作池)
工作池模式是一种常见的并发模式,它可以帮助我们管理一组工作线程(在 Go 中就是 Goroutine),并将任务分配给这些工作线程进行处理。这种模式在处理大量独立任务时非常有用,可以避免创建过多的 Goroutine 导致系统资源耗尽。
收起
package mainimport ("fmt"
)// worker 函数表示一个工作线程,它从 jobs 通道接收任务,处理后将结果发送到 results 通道
func worker(id int, jobs <-chan int, results chan<- int) {for job := range jobs {fmt.Printf("Worker %d processing job %d\n", id, job)// 模拟任务处理,这里简单地将任务值乘以 2results <- job * 2}
}func main() {// 创建两个有缓冲的通道,分别用于存储任务和结果jobs := make(chan int, 100)results := make(chan int, 100)// 启动 3 个工作线程for w := 1; w <= 3; w++ {go worker(w, jobs, results)}// 发送 9 个任务到 jobs 通道for j := 1; j <= 9; j++ {jobs <- j}// 关闭 jobs 通道,表示不再有新的任务发送close(jobs)// 收集结果for a := 1; a <= 9; a++ {<-results}// 关闭 results 通道close(results)
}
2. Fan-out/Fan-in(扇出/扇入)
扇出 / 扇入模式是一种用于并发处理数据的模式。扇出指的是将一个输入源的数据分发到多个 Goroutine 中进行处理,扇入则是将多个 Goroutine 的处理结果合并到一个通道中。
package mainimport ("fmt""sync"
)// merge 函数用于将多个输入通道的数据合并到一个输出通道中
func merge(chs ...<-chan int) <-chan int {var wg sync.WaitGroupout := make(chan int)// 从每个输入通道读取数据for _, ch := range chs {wg.Add(1)go func(c <-chan int) {defer wg.Done()for n := range c {out <- n}}(ch)}// 关闭输出通道go func() {wg.Wait()close(out)}()return out
}func main() {// 创建 3 个输入通道ch1 := make(chan int)ch2 := make(chan int)ch3 := make(chan int)// 启动 3 个 Goroutine 向输入通道发送数据go func() {ch1 <- 1ch1 <- 2close(ch1)}()go func() {ch2 <- 3ch2 <- 4close(ch2)}()go func() {ch3 <- 5ch3 <- 6close(ch3)}()// 合并 3 个输入通道的数据到一个输出通道resultCh := merge(ch1, ch2, ch3)// 从输出通道读取数据for num := range resultCh {fmt.Println(num)}
}
3. 管道模式(Pipeline)
管道模式是 Go 语言并发编程中一种非常强大且实用的模式,它通过串联多个 Goroutine 来处理数据流,形成一个处理链。这种模式特别适合那些需要分阶段处理任务的场景,例如数据清洗、数据转换、数据筛选等。在管道模式中,每个阶段的 Goroutine 负责完成一个特定的任务,将处理后的数据传递给下一个阶段,就像工厂里的流水线一样,每个工人负责一道工序,最终完成整个产品的生产。
package mainimport "fmt"// generate 函数用于生成一个整数数据流
// 它接收多个整数作为输入,将这些整数依次发送到一个通道中
// 发送完成后关闭通道
func generate(nums ...int) <-chan int {out := make(chan int)go func() {for _, n := range nums {out <- n}close(out)}()return out
}// square 函数用于对输入通道中的每个整数进行平方操作
// 它从输入通道中接收整数,计算其平方值,并将结果发送到一个新的通道中
// 当输入通道关闭且所有数据都处理完后,关闭输出通道
func square(in <-chan int) <-chan int {out := make(chan int)go func() {for n := range in {out <- n * n}close(out)}()return out
}// filterEven 函数用于过滤掉输入通道中的奇数,只保留偶数
// 它从输入通道中接收整数,判断是否为偶数,如果是则发送到输出通道
// 当输入通道关闭且所有数据都处理完后,关闭输出通道
func filterEven(in <-chan int) <-chan int {out := make(chan int)go func() {for n := range in {if n%2 == 0 {out <- n}}close(out)}()return out
}func main() {// 定义一个整数切片作为初始数据nums := []int{1, 2, 3, 4}// 管道串联:生成 → 平方 → 过滤 → 输出// 首先调用 generate 函数生成整数数据流// 然后将生成的数据流传递给 square 函数进行平方操作// 接着将平方后的数据流传递给 filterEven 函数进行偶数过滤// 最后使用 for...range 循环从最终的通道中接收数据并输出for n := range filterEven(square(generate(nums...))) {fmt.Println(n) // 输出结果:4, 16}
}
管道模式的优点 :
- 可维护性高:每个阶段的任务都封装在独立的函数中,代码结构清晰,易于理解和维护。
- 可扩展性强:可以方便地添加、删除或修改处理阶段,以适应不同的业务需求。
- 并发性能好:每个阶段的处理可以并行进行,充分利用多核 CPU 的资源,提高程序的处理效率。
通过管道模式,我们可以将复杂的任务分解为多个简单的子任务,并通过通道将这些子任务连接起来,实现高效、灵活的数据流处理。
4. 使用 Context 控制生命周期
在 Go 中,context
包提供了一种机制来控制 Goroutine 的生命周期,例如取消任务、设置超时等。
package mainimport ("context""fmt""time"
)// longRunningTask 函数表示一个长时间运行的任务,它会监听 context 的取消信号
func longRunningTask(ctx context.Context) {for {select {case <-ctx.Done(): // 监听取消信号fmt.Println("Task canceled:", ctx.Err())returndefault:// 模拟工作time.Sleep(1 * time.Second)}}
}func main() {// 创建一个带有超时的 context,超时时间为 3 秒ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)defer cancel()// 启动一个 Goroutine 执行长时间运行的任务go longRunningTask(ctx)// 等待 4 秒,确保任务会被取消time.Sleep(4 * time.Second)
}
七、常见问题与调试
1. 竞态条件(Race Condition)
竞态条件是指多个 Goroutine 同时访问和修改共享资源,导致程序的行为变得不可预测。可以使用 Go 语言提供的 -race
标志来检测竞态条件。
package mainimport ("fmt""sync"
)var counter int
var wg sync.WaitGroupfunc increment() {counter++ //不用互斥锁
}func main() {for i := 0; i < 1000; i++ {wg.Add(1)go func() {defer wg.Done()increment()}()}wg.Wait()fmt.Println(counter)
}
要检测上述代码中的竞态条件,可以使用以下命令:
go run -race main.go
如果存在竞态条件,Go 编译器会输出详细的错误信息,帮助我们定位问题。
2. 死锁
死锁是指程序中的 Goroutine 相互等待对方释放资源,导致所有 Goroutine 都无法继续执行的情况。要避免死锁,需要确保通道操作成对出现,避免永久阻塞。
func main() {ch := make(chan int)ch <- 1 // 这里会导致死锁,因为没有接收方<-ch
}
//fatal error: all goroutines are asleep - deadlock!
在上面代码中,由于没有接收方,ch <- 1
会导致永久阻塞,从而引发死锁。要避免这种情况,需要确保在发送数据之前有接收方准备好接收数据。
3.在 Goroutine 中传递错误
func doSomething() error {return errors.New("something went wrong")
}func main() {errCh := make(chan error)go func() {errCh <- doSomething()}()if err := <-errCh; err != nil {fmt.Println("Error:", err)}
}
4. Goroutine 泄漏
Goroutine 泄漏是指 Goroutine 由于某种原因无法正常退出,导致系统资源不断被占用。要避免 Goroutine 泄漏,始终确保通道被关闭,或使用 context
控制退出。
检测方法:
使用 runtime.NumGoroutine()
监控 goroutine 数量
runtime.NumGoroutine()
函数可以返回当前程序中正在运行的 Goroutine 的数量。通过定期检查这个数量,我们可以判断是否存在 Goroutine 泄漏。如果 Goroutine 的数量持续增加,而没有相应的减少,那么很可能存在泄漏。
以下是一个示例代码,展示了如何使用 runtime.NumGoroutine()
监控 Goroutine 数量:
package mainimport ("fmt""runtime""time"
)func worker(ch chan int) {for {select {case num := <-ch:fmt.Println("Received:", num)default:time.Sleep(100 * time.Millisecond)}}
}func main() {ch := make(chan int)go worker(ch)// 定期检查 Goroutine 数量ticker := time.NewTicker(1 * time.Second)defer ticker.Stop()for range ticker.C {numGoroutines := runtime.NumGoroutine()fmt.Printf("Number of goroutines: %d\n", numGoroutines)}
}
使用Go 的 pprof
分析 goroutine 堆栈。
pprof
是 Go 语言自带的一个性能分析工具,它可以帮助我们分析程序的 CPU 使用情况、内存分配情况以及 Goroutine 堆栈信息。通过 pprof
,我们可以查看每个 Goroutine 的状态和调用栈,从而找出可能存在泄漏的 Goroutine。
以下是一个使用 pprof
分析 Goroutine 堆栈的示例代码:
package mainimport ("net/http"_ "net/http/pprof""time"
)func worker() {for {time.Sleep(1 * time.Second)}
}func main() {go worker()// 启动 pprof 服务go func() {http.ListenAndServe("localhost:6060", nil)}()// 让程序一直运行select {}
}
代码执行后,使用以下命令:
go tool pprof http://localhost:6060/debug/pprof/goroutine
上述命令后,会进入 pprof
的交互式界面。在这个界面中,可以使用各种命令来查看 Goroutine 的堆栈信息,例如 top
命令可以查看占用资源最多的 Goroutine,list
命令可以查看指定函数的调用栈等。通过分析这些信息,我们可以找出可能存在泄漏的 Goroutine,并对代码进行修复。
八、最佳实践
1.优先使用 channel
而非共享内存
在 Go 语言中,推荐使用 channel
来进行 Goroutine 之间的通信和同步,而不是直接使用共享内存。channel
可以避免竞态条件和死锁等问题,使代码更加简洁和安全。
2.避免在 goroutine 中使用全局变量
全局变量在多个 Goroutine 中共享时容易引发竞态条件。尽量将变量的作用域限制在需要使用的 Goroutine 内部,或者使用 channel
来传递数据。
3.使用 select
实现超时和取消
select
语句可以用于同时监听多个通道的操作,结合 time.After
和 context
可以实现超时和取消功能,提高程序的健壮性。
4.小任务用 goroutine,大任务考虑线程池
对于小的、独立的任务,可以直接使用 Goroutine 来执行,因为 Goroutine 的创建和销毁成本较低。对于大的、复杂的任务,可以考虑使用工作池模式来管理一组 Goroutine,避免创建过多的 Goroutine 导致系统资源耗尽。
通过掌握这些概念和模式,你可以高效地编写并发 Go 程序,充分利用多核 CPU 资源。同时,遵循最佳实践和注意常见问题的处理,可以使你的代码更加健壮和可靠。
相关文章:
go语言并发的最佳实践
Go 语言的并发模型是其最强大的特性之一,基于 CSP(Communicating Sequential Processes)理论,通过 goroutine 和 channel 实现轻量级并发. 一、并发核心概念 1. Goroutine 在 Go 语言中,Goroutine 是实现并发编程的…...
超全Deepseek资料包,deepseek下载安装部署提示词及本地部署指南介绍
该资料包涵盖了DeepSeek模型的下载、安装、部署以及本地运行的详细指南,适合希望在本地环境中高效运行DeepSeek模型的用户。资料包不仅包括基础的安装步骤,还提供了68G多套独立部署视频教程教程,针对不同硬件配置的模型选择建议,以…...
重看Spring聚焦BeanDefinition分析和构造
目录 一、对BeanDefinition的理解 (一)理解元信息 (二)BeanDefinition理解分析 二、BeanDefinition的结构设计分析 (一)整体结构体会 (二)重要接口和类分析 三、构造 BeanDef…...
从MySQL5.7平滑升级到MySQL8.0的最佳实践分享
一、前言 升级需求:将5.7.35升级到8.0.27, 升级方式 in-place升级【关闭现有版本MySQL,将二进制或包替换成新版本并在现有数据目录上启动MySQL并执行升级任务的方式,称为in-place升级】 原版本 5.7.35 CentOS Linux release 7.9.2009 新版本…...
Node-Red
是什么? ——基于浏览器的流程编辑工具可连接设备、服务器和API应用 一、安装 1、本地安装(Windows) 在电脑上安装 Node.js 环境,然后使用 NPM 安装 Node-RED Node.js介绍: JavaScript 在不同的位置有不一样的解析器 写入 HTML 的 JS 语言,浏览器即它的解析器需要独…...
GO语言的安装以及第一个Go语言程序
1. Go语言的安装与设置 官网:golang.org 国内下载:https://studygolang.com/dl 国内镜像:https://goproxy.cn/ 2. GOland的安装 Go 1.13 及以上(推荐) 打开你的终端并执行 $ go env -w GO111MODULEon $ go env -w GOPROXYhttps://goproxy.cn,direc…...
年前集训总结python
1.用空格隔开输出列表list1 print(" ".jion(map(str,list1))) 2.转换函数 int() 将一个数字或字符串转换为整数。可以指定基数,支持从其他进制(如二进制、十六进制)转换为十进制。 int(101, 2) # 二进制字符串转十进制 > 5 …...
【JAVA实战】JAVA实现Excel模板下载并填充模板下拉选项数据
背景 有这样一个场景:前端下载Excel模板,进行数据导入,这个下载模板过程需要经过后端接口去数据库查询数据进行某些列的下拉数据填充,下拉填充的数据过程中会出现错误String literals in formulas can’t be bigger than 255 cha…...
【linux】在 Linux 上部署 DeepSeek-r1:32/70b:解决下载中断问题
【linux】在 Linux 上部署 DeepSeek-r1:32/70b:解决下载中断问题 【承接商业广告,如需商业合作请+v17740568442】 文章目录 【linux】在 Linux 上部署 DeepSeek-r1:32/70b:解决下载中断问题问题描述:解决方法方法一:手动中断并重启下载方法二:使用 Bash 脚本自动化下载在…...
Rust 面试题
1. Rust 中的所有权(Ownership)是什么? 回答: 所有权是 Rust 的核心概念之一,每个值在 Rust 中都有一个唯一的所有者,并且所有权在作用域内转移。当所有者离开作用域时,Rust 会自动释放该值的内存,避免了内存泄漏。 代码示例: fn main() {let s1 = String::from(&q…...
Copilot Next Edit Suggestions(预览版)
作者:Brigit Murtaugh,Burke Holland 排版:Alan Wang 我们很高兴向你介绍在本次 Visual Studio Code 发布中,关于 GitHub Copilot 的三个预览功能: Next Edit Suggestions(NES)Copilot Edits 的…...
LeetCode - 18 四数之和
题目来源 18. 四数之和 - 力扣(LeetCode) 题目描述 给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一…...
汽车零部件开发应该具备哪些编程思维?
目录 1、功能安全思维 2、实时性与确定性思维 3、可靠性和冗余思维 4、硬件软件协同思维 5、CAN总线通信思维 6、故障诊断和自诊断思维 7、功耗优化思维 8、软件更新和版本管理思维 9、用户体验与安全性思维 汽车零部件开发中,嵌入式软件在车辆系统中的作用…...
在Windows系统中安装Open WebUI并连接Ollama
Open WebUI是一个开源的大语言模型(LLM)交互界面,支持本地部署与离线运行。通过它,用户可以在类似ChatGPT的网页界面中,直接操作本地运行的Ollama等大语言模型工具。 安装前的核心要求: Python 3.11&#…...
React 与 Vue 对比指南 - 上
React 与 Vue 对比指南 - 上 本文将展示如何在 React 和 Vue 中实现常见功能,从基础渲染到高级状态管理 Hello 分别使用 react 和 vue 写一个 Hello World! react export default () > {return <div>Hello World!</div>; }vue <…...
自动化办公|通过xlwings进行excel格式设置
1. 介绍 xlwings 是一个强大的 Python 库,可以用来操作 Excel,包括设置单元格格式、调整行高列宽、应用条件格式以及使用内置样式。本文将详细介绍如何使用 xlwings 进行 Excel 格式化操作,并附带代码示例。 2. 基础格式设置(字…...
c# -01新属性-模式匹配、弃元、析构元组和其他类型
文章目录 **学习摘抄分享**模式匹配概述Null 检查类型测试比较离散值关系模型多个输入ObServation列表模式弃元元组和对象析构利用switch的模式进行匹配对于out的方法调用独立弃元析构元组和其他类型元组方法一方法二方法三方法四使用弃元元组的元素使用弃元的用户定义类型解构…...
银河麒麟V10SP1下qt5-12-12编译环境配置
下面的脚本在兆芯版kylin上试过,如果是本地编译,arm版应该也没有问题 a. 如果执行脚本安装有问题,读者可以拷贝命令多次安装 b. 建议在虚拟机环境下进行 #!/bin/bashsudo apt updatesudo apt-get install g gcc make cmake gperf bison flex libdrm-dev libxcompos…...
轻松搭建本地大语言模型(一)Ollama安装与使用
Ollama 是一款开源的本地大语言模型运行框架,支持在 Windows、macOS 和 Linux 系统上运行,能够帮助用户轻松下载和使用各种大语言模型(例如deepseek、llama、qwen)。本文将详细介绍 Ollama 的安装步骤,帮助你快速搭建本…...
【深度学习】Transformer入门:通俗易懂的介绍
【深度学习】Transformer入门:通俗易懂的介绍 一、引言二、从前的“读句子”方式三、Transformer的“超级阅读能力”四、Transformer是怎么做到的?五、Transformer的“多视角”能力六、Transformer的“位置记忆”七、Transformer的“翻译流程”八、Trans…...
浅识Linux高阶用法
(前记:内容有点多,先看目录再挑着看。) 问题:在Java面试中,当被提问到Linux的高阶用法以及如何使用Linux将程序、数据库、中间件等部署在云上时,可以从以下几个方面进行回答: Li…...
I²C简介
前言 IC(Inter-Integrated Circuit, 内置集成电路)总线是由Philips公司(现属于恩智浦)在上世纪80年代开发的两线式串行通信总线,用于连接微控制器及其外围设备,控制设备之间的通信。 IC总线的物理拓扑示意…...
Linux定时静默执行桌面/前台程序
Linux服务器上的程序大都在后台默默的执行,但有些需要在前台或桌面上运行的程序该怎么办呢?这就要借助虚拟显示技术,也就是在后台运行时提供虚拟的显示,使前台运行的程序也能默默的在后台运行,这就是所谓的以“无头模式…...
Redux中间件redux-thunk和redux-saga的具体区别是什么?
Redux 中间件是增强 Redux 功能的重要工具,redux-thunk 和 redux-saga 是两个常用的中间件,它们在处理异步操作和副作用时提供了不同的方式和理念。以下是两者的具体区别: 1. 概念与设计理念 redux-thunk 简洁:redux-thunk 是一…...
【STM32】DRV8833驱动电机
1.电机如何转动 只需要给电机两个端子加一正一负的极性就会转起来了,但是要注意的是不要将电机两端直接接在5v和gnd之间,这种电机一般要提供几百毫安的电流,而GPIO口只能提供几毫安,所以我们使用一个DRV8833来驱动 DRV8833输入口…...
【go语言规范】 使用函数式选项 Functional Options 模式处理可选配置
如何处理可选配置? Config Struct 方式 (config-struct/main.go) 这是最简单的方式,使用一个配置结构体: 定义了一个简单的 Config 结构体,包含 Port 字段创建服务器时直接传入配置对象优点:简单直接缺点:…...
vue中为组建添加样式的方式
在 Vue 中,可以通过多种方式为 view 添加样式,并且支持动态绑定样式。以下是几种常见的方式: 1. 内联样式 直接在模板中使用 style 属性来添加样式。 <template><div style"color: red; font-size: 14px;">这是一个…...
如何简单的去使用jconsloe 查看线程 (多线程编程篇1)
目录 前言 1.进程和线程 进程 PCB 的作用 并发编程和并行编程 线程 为什么选择多线程编程 2.在IDEA中如何简单创建一个线程 1. 通过继承Thread类 2. 通过实现 Runnable 接口 3. 使用 Lambda 表达式 3.如何简单使用jconsloe去查看创建好的线程 前言 2025来了,这是第…...
机器学习·最近邻方法(k-NN)
前言 上一篇简单介绍了决策树,而本篇讲解与决策树相近的 最近邻方法k-NN。 机器学习决策树-CSDN博客 一、算法原理对比 特性决策树最近邻方法(k-NN)核心思想通过特征分割构建树结构,递归划分数据基于距离度量,用最近…...
网络安全试题
ciw网络安全试题 (1)(单选题)使网络服务器中充斥着大量要求回复的信息,消耗带宽,导致网络或系统停止正常服务,这属于什么攻击类型? A、拒绝服务 B、文件共享 C、BIND漏洞 D、远程过程调用 &a…...
沃丰科技大模型标杆案例 | 索尼大模型智能营销机器人建设实践
AI大模型发展日新月异,国内外主流大模型每月必会升级。海外AI大模型市场由美国主导, 各模型已形成“多强竞合”的局面。中国积极响应全球大模型技术的发展趋势,高校、研究院所等科研机构、互联网企业,人工智能企业均不同程度地投入…...
rust学习四、控制语句
rust的控制语句和大部分语言没有什么区别,都是熟悉的for,while,loop,if。 比较不同的是,在绝大部分非常流行的语言中都有的switch,rust是没有的。 诸如c/c,java,javascript,c#。连PL/SQL都有case when语句。 一、基本的for、while、if&…...
会员购交易系统架构演进
本期作者 1.背景 会员购是B站2017年推出的IP消费体验服务平台,在售商品以手办、漫画、JK制服等贴合平台生态的商品为主。随着业务发展,会员购从最开始的预售,现货拓展到全款预售,盲盒,众筹等多种售卖方式,销…...
在Linux系统下修改Docker的默认存储路径
在Linux系统下修改Docker的默认存储路径可以通过多种方法实现,下边是通过修改daemon.json文件方式实现 查看当前Docker存储路径 使用命令 docker info | grep "Docker Root Dir" 查看当前Docker的存储路径,默认为 /var/lib/docker 停止Docker…...
【人工智能】释放数据潜能:使用Featuretools进行自动化特征工程
《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 特征工程是机器学习流程中至关重要的一步,它直接影响模型的性能。然而,手动特征工程既耗时又需要领域专业知识。Featuretools是一个强大的…...
shell脚本备份PostgreSQL数据库和库下表
注意: 以下为对PostgreSQL13.16版本数据库备份shell脚本参考请确认备份节点上psql和pgdump的版本不至于太低,建议>13.16该脚本目前是对于整库、(默认针对public这个schema,如果有其他schema,请自行添加一层循环&am…...
java面试笔记(一)
1. 一万个string类型的数据,设计一个算法如何按照String长度来排序 以使用 Arrays.sort() 方法,并结合一个自定义的比较器。以下是实现的示例代码: public class StringLengthSort {public static void main(String[] args) {// 定义一万个字符串的示例…...
网络分析仪E5071C的回波损耗测量
回波损耗(Return Loss)是评估射频/微波元件(如滤波器、天线、电缆等)信号反射特性的关键参数,反映端口阻抗匹配性能。E5071C矢量网络分析仪(VNA)通过以下步骤实现高精度回波损耗测量:…...
sql注入中,如果information_schema被过滤,该怎么绕过
目录 一、sys.schema_auto_increment_columns 1.功能 2.利用思路 二、sys.schema_table_statistics_with_buffer 1.功能 2.利用思路 三、mysql.innodb_table_stats和mysql.innodb_index_stats 1.mysql.innodb_table_stats 1.1功能 2.mysql.innodb_index_stats 2.1功…...
若依Flowable工作流版本监听器使用方法
1.前言 本文详细介绍如何在若依Flowable工作流版本(RuoYi-Vue-Flowable)中配置执行监听器和任务监听器。是以我二次开发的代码为基础,介绍如何配置监听器,已解决源码在新增或删除监听器出现的问题,如果需要二次开发的…...
Linux(Centos 7.6)命令详解:cat
1.命令作用 将文件或标准输入连接到标准输出(Concatenate FILE(s), or standard input, to standard output), 即将文件内容输出到屏幕上,或者将多个文件合并成一个文件。 2.命令语法 Usage: cat [OPTION]... [FILE]... 3.参数详解 OPTION: -A, -…...
使用DeepSeek+本地知识库,尝试从0到1搭建高度定制化工作流(自动化篇)
7.5. 配图生成 目的:由于小红书发布文章要求图文格式,因此在生成文案的基础上,我们还需要生成图文搭配文案进行发布。 原实现思路: 起初我打算使用deepseek的文生图模型Janus进行本地部署生成,参考博客:De…...
罗德与施瓦茨ZNB20,矢量网络分析仪9KHz-20GHz
罗德与施瓦茨ZNB20矢量网络分析仪9KHz-20GHz R&SZNB20矢量网络分析仪 产品型号: ZNB20 产品品牌:罗德与施瓦茨 R&S 产品名称: 矢量网络分析仪 频率范围:9kHz - 20GHz R&S ZNB 矢量网络分析仪 良好的测量速度、动态范围和操作方便性&am…...
Linux 固定 IP 地址和网关
Linux 固定 IP 地址和网关 查看 IP ifconfig ifconfig eth0 ip addr ip addr show eth0 查看网关 ip route show route -n netstat -rn 设置固定 IP // 配置静态IP文件/etc/network/interfaces $ vi /etc/network/interfacesauto eth0 iface eth0 inet static addre…...
C++ ——const关键字
const关键字通常表示只读,不可修改,可以保证数据的安全性 1、修饰局部变量 const修饰的局部变量,叫做常局部变量,表示该局部变量不可被修改,这种用法常用于引用参数 2、修饰成员变量 const修饰的成员变量…...
MySQL:MySQL8.0 JSON类型使用整理,基于用户画像的案例
摘要:MySQL,JSON类型,多值索引, 用户画像 MySQL是结构化数据存储,JSON是非结构化格式,在MySQL中使用JSON类型可以打通关系型和非关系型数据的存储之间的界限,为业务提供更好的架构选择ÿ…...
Python MoviePy 视频处理全攻略:从入门到实战案例
第1章 环境安装与配置 # 案例1:安装MoviePy及FFmpeg !pip install moviepy # Windows安装FFmpeg:https://ffmpeg.org/download.html # Linux: sudo apt-get install ffmpeg# 验证安装 from moviepy.editor import * print("MoviePy版本:", __…...
30道Qt面试题(答案公布)
前五个答案 ✦ 1. Qt中常用的五大模块是哪些? Qt中常用的五大模块包括: • Qt Core:提供核心非GUI功能,如数据结构、文件操作、国际化等。 • Qt GUI:提供与平台无关的图形和基本窗口功能。 • Qt Widgets:提供用于创建传统桌面应用程序的UI组件。 • Qt Netw…...
深入解析 MySQL 数据删除操作:DELETE、TRUNCATE 与 DROP 的原理与选择
引言 在 MySQL 中,删除数据或表结构的操作看似简单,但不同操作(如 DELETE、TRUNCATE、DROP)背后的原理和适用场景差异巨大。错误选择可能导致性能问题或数据丢失!本文通过通俗的比喻、流程图和表格,带你深入理解它们的原理与差异。 DELETE 操作的原理 DELETE … IN 执…...
spring cloud gateway限流常见算法
目录 一、网关限流 1、限流的作用 1. 保护后端服务 2. 保证服务质量 (QoS) 3. 避免滥用和恶意攻击 4. 减少资源浪费 5. 提高系统可扩展性和稳定性 6. 控制不同用户的访问频率 7. 提升用户体验 8. 避免API滥用和负载过高 9. 监控与分析 10. 避免系统崩溃 2、网关限…...