GO语言学习(14)GO并发编程
目录
🌈前言
1.goroutine🌟
2.GMP模型🌟
2.1 GMP的由来☀️
2.2 什么是GMP☀️
3.channel 🌟
3.1 通道声明与数据传输💥
3.2 通道关闭 💥
3.3 通道遍历 💥
3.4 Select语句 💥
4.同步并发工具🌟
4.1 WaitGroup(协程等待组)💐
4.2 Mutex(互斥锁) 💐
4.3 RWMutex(读写锁) 💐
🌈前言
并发是指程序同时执行多个任务的能力。Go 语言支持并发,通过 goroutines 和 channels 提供了一种简洁且高效的方式来实现并发。
1.goroutine🌟
- 定义:Goroutine是Go语言中实现并发的基本单位,是一种轻量级的线程,由Go运行时(runtime)负责调度。通过使用关键字go,我们可以启动一个新的goroutine
-
特点:
-
轻量级:创建和切换的开销极小,启动和销毁的代价远低于传统的操作系统线程。
-
高效调度:Go运行时使用M:N调度模型,将Goroutine调度到少量的操作系统线程上执行,提高了并发效率。
-
简单的创建方式:只需在函数调用前加上
go关键字即可启动一个新的Goroutine。
-
| 特性 | Goroutine (Go) | 操作系统进程 (Process) | 线程 (Thread) |
|---|---|---|---|
| 定义 | Go 语言实现的轻量级用户态线程 | 操作系统资源分配的基本单位 | 操作系统调度的最小执行单元(属于进程) |
| 调度方式 | Go 运行时调度(用户态协作+抢占) | 操作系统内核调度(抢占式) | 操作系统内核调度(抢占式) |
| 创建开销 | 极低(初始约 2KB 栈,动态扩展) | 高(独立内存、文件句柄、PCB 等) | 中(需内核分配资源,但共享进程内存) |
| 切换开销 | 极低(无内核参与,仅用户态切换) | 高(需内核态/用户态切换) | 中(需内核切换,但比进程轻量) |
| 并发数量 | 轻松支持数十万 | 通常数百个(受系统资源限制) | 数千到数万(受内核限制) |
| 内存占用 | 共享堆,独立栈(可动态增长) | 独立地址空间(完全隔离) | 共享进程内存,独立栈 |
| 通信机制 | Channel(推荐)、共享内存(需同步) | IPC(管道、信号、共享内存等) | 共享进程内存(需同步) |
| 隔离性 | 无(需自行管理共享数据) | 高(进程间完全隔离) | 低(同一进程内线程共享资源) |
| 阻塞影响 | 阻塞时自动切换其他 Goroutine | 阻塞整个进程 | 阻塞所属进程的所有线程 |
| 并行能力 | 支持(通过多线程绑定多核) | 支持(多进程在多核并行) | 支持(多线程在多核并行) |
| 崩溃影响 | 仅影响所在 Goroutine(除非未恢复) | 仅崩溃当前进程 | 同一进程内所有线程终止 |
| 典型应用 | 高并发 I/O 密集型(如 Web 服务器) | 计算密集型、需隔离的任务(如数据库) | 需要并发的计算密集型任务 |
| 跨平台性 | 依赖 Go 运行时(抽象操作系统差异) | 所有操作系统通用 | 所有操作系统通用(实现可能不同) |
2.GMP模型🌟
2.1 GMP的由来☀️
我们知道软件都是跑在操作系统上,真正用来执行任务的是 CPU。早期的操作系统每个程序就是一个进程,直到一个程序运行完,才能进行下一个进程,一切的程序只能串行发生。这就是 “单进程时代”。

☄️不难看出上述操作系统,存在着两个主要的问题:
- 单一的执行流程,计算机只能一个任务一个任务处理。
- 进程阻塞所带来的 CPU 时间浪费。
所以,之后便出现了 多进程 / 线程 时代 ,操作系统就具有了最早的并发能力:多进程并发,当一个进程阻塞的时候,切换到另外等待执行的进程,这样就能尽量把 CPU 利用起来,CPU 就不浪费了。

在多进程 / 多线程的操作系统中,就解决了阻塞的问题,因为一个进程阻塞 cpu 可以立刻切换到其他进程中去执行,而且调度 cpu 的算法可以保证在运行的进程都可以被分配到 cpu 的运行时间片。这样从宏观来看,似乎多个进程是在同时被运行。
但此时进程拥有太多的资源,进程的创建、切换、销毁,都会占用很长的时间,CPU 虽然利用起来了,但如果进程过多,CPU 有很大的一部分都被用来进行进程调度了。此时仍然存在以下问题:
- 高内存占用
- 调度的高消耗 CPU

随后工程师们就发现,其实一个线程可以分为 内核态线程和用户态线程。CPU并不知道“用户态”线程的存在,只知道执行“内核态”线程。此后,我们将内核线程叫 “线程 (thread)”,用户线程叫 “协程 (co-routine)”。

协程跟线程是有区别的,线程由操作系统内核调度,并且线程切换需要保存和恢复上下文,线程的内存占用较大,其存在于内核空间,其并发能力有限,通常受操作系统支持的线程数量限制,线程编程需要处理复杂的同步和锁问题,容易出现竞态条件和死锁。
而协程存在于用户空间由用户态调度器管理,内存占用极小,调度开销较小。并且并发能力极高,协程之间可以通过共享内存或通道(channel)通信,通常不需要复杂的同步机制。而由GO语言创建的Goroutine与协程的主要不同在于Goroutine可能发生并行,是一种特殊的协程。
在 Go 语言的 Goroutine 调度模型(GMP)中,M:N 模型 是指将多个 Goroutine(用户态协程)映射到少量的操作系统线程(M)上,从而实现高效的并发调度。

2.2 什么是GMP☀️
GMP实际上是三个单词的缩写:
- 🌷Goroutine (G): Goroutine是Go语言的并发执行单元。它们非常轻量,初始栈空间小,可以动态伸缩。Goroutine的调度由Go运行时管理,而不是操作系统内核
- 🌻Machine (M): Machine是操作系统的线程。在Go的并发模型中,M是执行Goroutine代码的实体。每个M都会被分配一个P,并从P的本地运行队列中获取G来执行
- 🌼Processor (P): Processor是G和M之间的调度中介。每个P都有一个本地的Goroutine队列,负责维护和调度这些Goroutines到M上执行。P的数量通常等于机器的逻辑CPU数量
下面通过图例具体说明:
先介绍各个图标表示的含义,如下:
GMP模型架构图:

- 全局队列(Global Queue):存放等待运行的 G。
- P 的本地队列:同全局队列类似,存放的也是等待运行的 G,存的数量有限,不超过 256 个。新建 G’时,G’优先加入到 P 的本地队列,如果队列满了,则会把本地队列中一半的 G 移动到全局队列。
- P 列表:所有的 P 都在程序启动时创建,并保存在数组中,最多有 GOMAXPROCS(可配置) 个。
- M:线程想运行任务就得获取 P,从 P 的本地队列获取 G,P 队列为空时,M 也会尝试从全局队列拿一批 G 放到 P 的本地队列,或从其他 P 的本地队列偷一半放到自己 P 的本地队列。M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去。
- work stealing 机制:当本线程无可运行的 G 时,尝试从其他线程绑定的 P 偷取 G,而不是销毁线程
- hand off 机制:当本线程因为 G 进行系统调用阻塞时,线程释放绑定的 P,把 P 转移给其他空闲的线程执行
下面以一段示例代码进行说明:
package mainimport ("fmt""time"
)func sayHello() {for i := 0; i < 5; i++ {fmt.Println("Hello")time.Sleep(100 * time.Millisecond)}
}func main() {go sayHello() // 启动 Goroutinefor i := 0; i < 5; i++ {fmt.Println("Main")time.Sleep(100 * time.Millisecond)}
}
上述例子并没有固定的输出,可能输出如下:
Main
Hello
Main
Hello
Main
Hello
...
🍓解释:
1.初始阶段🥕:
Go 程序启动时,会创建一个主 Goroutine(对应
main()函数)。默认创建一个 P(逻辑处理器)和绑定一个 M(系统线程)来执行主 Goroutine。
2.创建新的Goroutine🥕:
- 执行 go sayHello() 将会创建一个新的协程,同样的会被放入当前P的本地队列当中,如果本地队列满了,则会放入全局队列当中。
- 如果 Go 程序设置了
GOMAXPROCS > 1(默认等于 CPU 核心数),可能会有多个 M 同时运行不同的 G,实现并行。此时 go sayHello()创建的协程不一定会在原先主Goroutine的P的本地队列中,有可能会为其创建一个新的P的M。(本次假设GOMAXPROCS=1,如下图所示,之后图示也为此情况)
⛅️
GOMAXPROCS > 1时,情况可能如下,由于线程是并发进行,没有固定的调度顺序,所以程序的第一个输出的不一定是Main,也可能是 Hello。3.Goroutine 切换🥕:
- 主 Goroutine(main())和 sayHello() Goroutine 交替执行。
- 当主 Goroutine 调用 time.Sleep() 时,会主动让出 CPU,触发调度器切换到其他可运行的 G(如 sayHello()),此时退出的协程将进入休眠状态(
Gwaiting),并将其从当前 P 的 运行队列 中移出。- 此时,调度器会立即选择另一个 可运行的 G(从当前 P 的本地队列、全局队列或其他 P 偷取)继续执行。
- 当休眠时间到期后,Go 运行时会将 G 重新标记为 可运行状态(
Grunnable),并放回 原来 P 的本地队列(或其他 P 的队列,取决于调度器的负载均衡策略)
👉 更一般的go func流程情况,可参考下图所示:
3.channel 🌟
3.1 通道声明与数据传输💥
通道(Channel)是用于 Goroutine 之间的数据传递。通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。使用 make 函数创建一个 channel,使用 <- 操作符发送和接收数据。如果未指定方向,则为双向通道。并且chanel的数据结构本质为队列,数据满足先进先出(FIFO)的操作顺序
而通道可分为带缓冲通道和无缓冲通道,具体区别如下表所示,简单来说,无缓冲通道是"你发我必收",带缓冲通道是"你先发着,我慢慢收"。
| 特性 | 无缓冲通道 | 带缓冲通道 |
|---|---|---|
| 创建方式 | make(chan T) | make(chan T, n),n为通道大小 |
| 容量 | 0 | n > 0 |
| 发送阻塞条件 | 没有接收者等待时 | 缓冲区满时 |
| 接收阻塞条件 | 没有数据时 | 缓冲区空时 |
| 同步性 | 强同步,发送接收必须同时就绪 | 弱同步,允许短暂不同步 |
| 类比 | 实时电话 | 短信或邮件 |
- 📞无缓冲通道:发送端发送数据,同时必须有接收端相应的接收数据。就好比两个人打电话,必须双方同时接通,通信才能进行。
- 🔧带缓冲通道:允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
🌱示例代码:
package main
import "fmt"func main() {// 无缓冲通道ch1 := make(chan int) // 容量为0go func() {ch1 <- 1 // 发送会阻塞,直到有人接收}()fmt.Println(<-ch1) // 接收// 带缓冲通道ch2 := make(chan int, 3) // 容量为3ch2 <- 1 // 不会阻塞ch2 <- 2 // 不会阻塞ch2 <- 3 // 不会阻塞// ch2 <- 4 // 这会阻塞,因为缓冲区满了fmt.Println(<-ch2) // 取出1ch2 <- 4 // 不会阻塞,1已被取出
}
🌷解释🌷:
3.2 通道关闭 💥
通道可以通过close函数来关闭。一旦通道被关闭,就不能再向其发送数据,但仍然可以从通道中接收已有的数据,直到所有数据都被取出。
close(ch) // 关闭后无法发送,但仍可接收剩余数据
val, ok := <-ch // ok=false表示通道已关闭
但忽略通道关闭状态可能会导致接收方不知道何时停止读取,进而造成goroutine永远挂起。一般来说我们关闭通道应遵循如下要求:
- 仅由负责发送数据的一方关闭通道。
- 避免多次关闭同一个通道。
- 在发送数据之前,确保通道尚未关闭。
3.3 通道遍历 💥
可以使用for range循环来遍历通道中的所有元素,直到通道被关闭。这种方式非常适合用于处理未知数量的数据流。
for value := range ch {fmt.Println(value)
}
3.4 Select语句 💥
select 是 Go 中的一个控制结构,类似于 switch 语句。select 语句只能用于通道操作,每个 case 必须是一个通道操作,要么是发送要么是接收。select 语句会监听所有指定的通道上的操作,一旦其中一个通道准备好就会执行相应的代码块。如果多个通道都准备好,那么 select 语句会随机选择一个通道执行。如果所有通道都没有准备好,那么执行 default 块中的代码。
语法格式如下:
select {case <- channel1:// 执行的代码case value := <- channel2:// 执行的代码case channel3 <- value:// 执行的代码// 你可以定义任意数量的 casedefault:// 所有通道都没有准备好,执行的代码
}
- 每个 case 都必须是一个通道
- 所有 channel 表达式都会被求值
- 所有被发送的表达式都会被求值
- 如果任意某个通道可以进行,它就执行,其他被忽略。
- 如果有多个 case 都可以运行,select 会随机公平地选出一个执行,其他不会执行。
否则:
- 如果有 default 子句,则执行该语句。
- 如果没有 default 子句,select 将阻塞,直到某个通道可以运行;Go 不会重新对 channel 或值进行求值。
🌺示例代码:
package main
import "fmt"func fibonacci(c, quit chan int) {x, y := 0, 1for {select {case c <- x:x, y = y, x+ycase <-quit:fmt.Println("quit")return}}
}func main() {c := make(chan int)quit := make(chan int)go func() {for i := 0; i < 10; i++ {fmt.Println(<-c)}quit <- 0}()fibonacci(c, quit)
}
上述代码中fibonacci goroutine 在 channel c 上发送斐波那契数列,当接收到 quit channel 的信号时退出。最终输出结果如下:
0
1
1
2
3
5
8
13
21
34
4.同步并发工具🌟
在Go语言并发编程中,同步控制工具是协调多个Goroutine执行的关键。
4.1 WaitGroup(协程等待组)💐
WaitGroup 是 Go 语言标准库中 sync 包提供的一种同步原语,本质是一个计数器,用于等待一组协程(goroutine)完成任务后再继续执行。它的主要作用是让主协程或某个协程能够等待其他协程完成后再继续执行。
主要方法:
-
Add(delta int):增加计数器的值。通常在启动协程前调用,表示需要等待的协程数量。
-
Done():减少计数器的值。通常在协程任务完成后调用,表示该协程已完成。
-
Wait():阻塞当前协程,直到计数器归零。
🔥示例代码:
package mainimport ("fmt""sync"
)func worker(id int, wg *sync.WaitGroup) {defer wg.Done() // Goroutine 完成时调用 Done()fmt.Printf("Worker %d started\n", id)fmt.Printf("Worker %d finished\n", id)
}func main() {var wg sync.WaitGroupfor i := 1; i <= 3; i++ {wg.Add(1) // 增加计数器go worker(i, &wg)}wg.Wait() // 等待所有 Goroutine 完成fmt.Println("All workers done")
}
上述代码,最终输出结果如下:
Worker 1 started
Worker 1 finished
Worker 2 started
Worker 2 finished
Worker 3 started
Worker 3 finished
All workers done
4.2 Mutex(互斥锁) 💐
Mutex用于确保在并发环境下对共享资源的独占访问,其通过互斥锁机制防止多个goroutine同时访问共享资源,从而避免数据竞争(data race)问题。
主要方法:
-
Lock():获取锁。如果锁已被其他协程持有,则调用此方法的协程会阻塞,直到锁被释放。
-
Unlock():释放锁。释放锁后,其他等待的协程可以获取锁。
🔥实例代码(银行账户余额更新):
package mainimport ("fmt""sync""time"
)// 定义一个银行账户结构体
type Account struct {balance intmu sync.Mutex // 互斥锁
}// 存款操作
func (a *Account) Deposit(amount int) {a.mu.Lock()defer a.mu.Unlock()a.balance += amountfmt.Printf("存入 %d,当前余额: %d\n", amount, a.balance)
}// 取款操作
func (a *Account) Withdraw(amount int) {a.mu.Lock()defer a.mu.Unlock()if a.balance >= amount {a.balance -= amountfmt.Printf("取出 %d,当前余额: %d\n", amount, a.balance)} else {fmt.Println("余额不足")}
}func main() {account := &Account{balance: 1000}var wg sync.WaitGroup// 启动多个协程进行存款和取款操作for i := 0; i < 10; i++ {wg.Add(1)go func(i int) {defer wg.Done()account.Deposit(i * 100)time.Sleep(time.Millisecond * 100)account.Withdraw(i * 50)}(i)}wg.Wait()fmt.Printf("最终余额: %d\n", account.balance)
}
结构体定义:
- Account 结构体包含一个 balance 字段,表示账户余额。
- mu 是一个 sync.Mutex 类型的互斥锁,用于保护对 balance 的访问。
存款操作:
- Deposit 方法使用 Lock() 方法获取锁,确保在修改 balance 时没有其他协程在访问。
- 使用 defer a.mu.Unlock() 确保锁在方法结束时被释放,即使发生错误也不会导致死锁。
取款操作:
- Withdraw 方法同样使用 Lock() 和 Unlock() 来保护对 balance 的访问。
- 在取款前检查余额是否足够,避免透支。
并发执行:
- 在 main 函数中,创建了一个 Account 实例,并启动了 10 个协程来模拟存款和取款操作。
- 使用 sync.WaitGroup 等待所有协程完成,确保在打印最终余额之前所有操作都已完成。
4.3 RWMutex(读写锁) 💐
RWMutex(读写锁)是 Go 语言标准库 sync 包中提供的一种同步原语,用于在并发环境中保护共享资源的访问。它允许多个读操作同时进行,但写操作必须独占访问。这种锁特别适合读多写少的场景,能够显著提高并发性能。 RWMutex 采用写优先(write-preferring)设计,即当有写操作等待时,新的读操作会被阻塞,直到写操作完成。
🔒读锁:
- 允许多个协程同时读取共享资源。
- 使用 RLock() 方法获取读锁。
- 使用 RUnlock() 方法释放读锁。
🔒写锁:
- 确保同一时间只有一个协程可以写入共享资源,且写操作会阻塞所有读操作。
- 使用 Lock() 方法获取写锁。
- 使用 Unlock() 方法释放写锁。
🔥示例代码:
var cache = make(map[string]string)
var rwMu sync.RWMutex// 写操作使用写锁
func Set(key, value string) {rwMu.Lock()defer rwMu.Unlock()cache[key] = value
}// 读操作使用读锁
func Get(key string) string {rwMu.RLock()defer rwMu.RUnlock()return cache[key]
}
注意事项:
- 避免死锁:不要在持有读锁的情况下尝试获取写锁,否则会导致死锁。
- 性能优化:合理安排读写操作的顺序,避免写操作过于频繁,从而影响读操作的性能。
- 锁的粒度:尽量减少锁的持有时间,以提高并发性能
相关文章:
GO语言学习(14)GO并发编程
目录 🌈前言 1.goroutine🌟 2.GMP模型🌟 2.1 GMP的由来☀️ 2.2 什么是GMP☀️ 3.channel 🌟 3.1 通道声明与数据传输💥 3.2 通道关闭 💥 3.3 通道遍历 💥 3.4 Select语句 Ǵ…...
【Audio开发二】Android原生音量曲线调整说明
一,客制化需求 客户方对于音量加减键从静音到最大音量十五个档位区域的音量变化趋势有定制化需求。 二,音量曲线调试流程 Android根据不同的音频流类型定义不同的曲线,曲线文件存放在/vendor/etc/audio_policy_volumes.xml或者default_volu…...
sass报错,忽略 Sass 弃用警告,降级版本
最有效的方法是创建一个 .sassrc.json 文件来配置 Sass 编译器。告诉 Sass 编译器忽略来自依赖项的警告消息。 解决方案: 1. 在项目根目录创建 .sassrc.json 文件: {"quietDeps": true }这个配置会让 Sass 编译器忽略所有来自依赖项&#x…...
spring-security原理与应用系列:HttpSecurity.filters
目录 AnyRequestMatcher WebSecurityConfig HttpSecurity AbstractInterceptUrlConfigurer AbstractAuthenticationProcessingFilter 类图 在前面的文章《spring-security原理与应用系列:securityFilterChainBuilders》中,我们遗留了一个问题&…...
JVM生产环境问题定位与解决实战(六):总结篇——问题定位思路与工具选择策略
本文已收录于《JVM生产环境问题定位与解决实战》专栏,完整系列见文末目录 引言 在前五篇文章中,我们深入探讨了JVM生产环境问题定位与解决的实战技巧,从基础的jps、jmap、jstat、jstack、jcmd等工具,到JConsole、VisualVM、MAT的…...
数据仓库项目启动与管理
数据仓库项目启动与管理 确定项目 评估项目就绪情况 项目就绪的三个条件 强力型高级业务管理发起人 对数据仓库解决方案的影响有先见之明是所在组织内有影响的领导者要求严格,但是又比较现实,会为其他成员提供强力支持 强制型业务动机 数据仓库系统和战略性业务动机紧密结合…...
并行治理机制对比:Polkadot、Ethereum 与 NEAR
治理是任何去中心化网络的基础。它塑造了社区如何发展、如何为创新提供资金、如何应对挑战以及如何随着时间的推移建立信任。随着 Web3 的不断发展,决定这些生态系统如何做出决策的治理模型也在不断发展。 在最近的一集的【The Decentralized Mic】中, Polkadot 汇…...
利用 PHP 爬虫按关键字搜索淘宝商品
在当今数字化时代,网络爬虫技术已成为获取网络数据的重要手段之一。淘宝作为国内最大的电商平台之一,拥有海量的商品信息。通过 PHP 爬虫技术,我们可以实现按关键字搜索并抓取淘宝商品信息。以下将详细介绍如何使用 PHP 实现这一功能。 一、…...
在未归一化的线性回归模型中,特征的尺度差异可能导致模型对特征重要性的误判
通过数学公式来更清晰地说明归一化对模型的影响,以及它如何改变特征的重要性评估。 1. 未归一化的情况 假设我们有一个线性回归模型: y β 0 β 1 x 1 β 2 x 2 ϵ y \beta_0 \beta_1 x_1 \beta_2 x_2 \epsilon yβ0β1x1β2x2ϵ 其…...
TDengine tar.gz和docker两种方式安装和卸载
下载地址 3.1.1.0 Linux版本 安装包 下载地址 3.1.1.0 docker 镜像 下载地址 3.1.1.0 Window客户端 1. 将文件上传至服务器后解压 tar -zxvf TDengine-server-3.1.1.0-Linux-x64.tar.gz 2. tar.gz安装 解压文件后,进入相应子目录,执行其中的 install.…...
【STM32设计】基于STM32的智能门禁管理系统(指纹+密码+刷卡+蜂鸣器报警)(代码+资料+论文)
本课题为基于单片机的智能门禁系统,整个系统由AS608指纹识别模块,矩阵键盘,STM32F103单片机,OLED液晶,RFID识别模块,继电器,蜂鸣器等构成,在使用时,用户可以录入新的指纹…...
贪心算法,其优缺点是什么?
什么是贪心算法? 贪心算法(Greedy Algorithm)是一种在每一步选择中都采取在当前状态下最优(局部最优)的选择,从而希望导致全局最优解的算法策略。 它不像动态规划那样考虑所有可能的子问题,而是做出局部最优选择,依赖这些选择来…...
java知识梳理(二)
一.lambda表达式 作用:Lambda 表达式在 Java 8 引入,主要用于简化匿名内部类的写法,特别是在函数式编程场景中,比如 函数式接口、流式 API(Streams)、并发编程等。它让 Java 代码更简洁、可读性更强&#x…...
鸿蒙Flutter实战:20. Flutter集成高德地图,同层渲染
本文以同层渲染为例,介绍如何集成高德地图 完整代码见 Flutter 鸿蒙版 Demo 概述 Dart 侧 核心代码如下,通过 OhosView 来承载原生视图 OhosView(viewType: com.shaohushuo.app/customView,onPlatformViewCreated: _onPlatformViewCreated,creation…...
c++中%符号使用的注意事项/易错点
在C中,% 是取模运算符(modulus operator),用于计算两个数相除后的余数。虽然它的用法看起来简单,但在实际编程中有一些需要注意的细节和易错点。以下是关键注意事项: 1. 操作数必须为整数类型 % 只能用于整…...
AI辅助下基于ArcGIS Pro的SWAT模型全流程高效建模实践与深度进阶应用
目前,流域水资源和水生态问题逐渐成为制约社会经济和环境可持续发展的重要因素。SWAT模型是一种基于物理机制的分布式流域水文与生态模拟模型,能够对流域的水循环过程、污染物迁移等过程进行精细模拟和量化分析。SWAT模型目前广泛应用于流域水文过程研究…...
Java 基础-30-单例设计模式:懒汉式与饿汉式
在软件开发中,单例设计模式(Singleton Design Pattern)是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点。这种模式通常用于管理共享资源(如数据库连接池、线程池等)或需要…...
尚语翻译图册翻译|专业图册翻译|北京专业翻译公司推荐|专业文件翻译报价
内容概要 尚语翻译公司聚焦多语种产品图册翻译的竞价推广服务,通过行业垂直化运营构建差异化竞争力。其核心服务覆盖机械制造、医疗器械、电子元件三大领域,依托ISO 17100认证的翻译流程和Trados术语管理系统,实现技术文档的精准转化。为提升…...
杂篇-行业分类一二-2(通、专用设备制造,汽车制造)
接上篇, 本篇列举制造业中另外几个细分行业:通用设备制造,专用设备制造,汽车制造业。 一、通用设备制造 分类 序号 类别名称 说明 1 锅炉及原动设备制造 1 锅炉及辅助设备制造 指各种蒸汽锅炉、汽化锅炉,以及…...
[笔记.AI]大模型训练 与 向量值 的关系
(借助 DeepSeek-V3 辅助生成) 大模型在训练后是否会改变向量化的值,取决于模型的训练阶段和使用方式。以下是详细分析: 1. 预训练阶段:向量化值必然改变 动态调整过程: 在预训练阶段(如BERT、…...
LeetCode 解题思路 30(Hot 100)
解题思路: 递归参数: 生成括号的对数 n、结果集 result、当前路径 path、左括号数 open、右括号数 close。递归过程: 当当前路径 path 的长度等于 n * 2 时,说明已经生成有效括号,加入结果集。若左括号数小于 n&…...
Java EE(18)——网络原理——应用层HTTP协议
一.初识HTTP协议 HTTP(HyperText Transfer Protocol,超文本传输协议)是用于在客户端(如浏览器)和服务器之间传输超媒体文档(如HTML)的应用层协议。 HTTP协议发展至今发布了多个版本,其中1.0,1.…...
强大而易用的JSON在线处理工具
强大而易用的JSON在线处理工具:程序员的得力助手 在当今的软件开发世界中,JSON(JavaScript Object Notation)已经成为了数据交换的通用语言。无论是前端还是后端开发,我们都经常需要处理、验证和转换JSON数据。今天&a…...
Qt笔记----》不同环境程序打包
文章目录 概要1、windows环境下打包qt程序2、linux环境下打包qt程序2.1、程序目录2.2、创建一个空文件夹2.3、添加依赖脚本2.4、打包过程2.4.1、添加程序依赖库2.4.2、添加Qt相关依赖库 概要 qt不同运行环境下打包方式:windows/linux 1、windows环境下打包qt程序 …...
企业服务器备份软件,企业服务器备份的方法有哪些?
企业服务器备份需综合考虑数据量、业务连续性要求(RTO/RPO)、合规性及成本等因素。以下是分场景的工具和方法指南: 一、备份软件推荐 1. 80KM备份软件 80KM备份软件可以进行很复杂的备份方式,也可以内网对内网备份、还能内网的…...
Vue3 表单
Vue3 表单 随着前端技术的发展,Vue.js 作为一款流行的前端框架,不断更新迭代,以适应更高效、更便捷的开发需求。Vue3 作为 Vue.js 的第三个主要版本,引入了许多新特性和改进,其中包括对表单处理机制的优化。本文将深入探讨 Vue3 表单的使用方法、技巧以及注意事项。 1. …...
html5炫酷图片悬停效果实现详解
html5炫酷图片悬停效果实现详解 这里写目录标题 html5炫酷图片悬停效果实现详解项目介绍技术栈核心功能实现1. 页面布局2. 图片容器样式3. 炫酷悬停效果缩放效果倾斜效果模糊效果旋转效果 4. 悬停文字效果5. 性能优化6. 响应式设计 项目亮点总结 项目介绍 本文将详细介绍如何使…...
安徽京准:GPS北斗卫星校时服务器助力大数据云计算
安徽京准:GPS北斗卫星校时服务器助力大数据云计算 安徽京准:GPS北斗卫星校时服务器助力大数据云计算 GPS北斗卫星校时服务器在大数据与云计算系统中发挥着关键作用,其通过提供高精度、高可靠的时间同步服务,解决了分布式系统的核…...
【Linux】内核驱动学习笔记(二)
7、framebuffer驱动详解 7.1、什么是framebuffer (1)裸机中如何操作LCD (2)OS下操作LCD的难点 (3)framebuffer帧缓冲(简称fb)是linux内核中虚拟出的一个设备 (4)framebuffer向应用层提供一个统一标准接口的显示设备 (5)从驱动来看,fb是一个…...
机器学习的一百个概念(5)数据增强
前言 本文隶属于专栏《机器学习的一百个概念》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和参考文献请见[《机器学习的一百个概念》 ima 知识库 知识库广场搜索&…...


⛅️


