Golang基础-13

Go语言基础

介绍

并发

介绍

  • 本文介绍Go语言中 channel、goroutine、互斥锁、读写锁、原子操作、select、超时处理、sync包、runtime包等相关知识。

并发

  • 进程是是最小的资源管理单元,属于操作系统对一个正在运行的程序的一种抽象。可以同时运行多个进程,每个进程都独立的使用系统资源。
  • 线程属于轻量级进程,从属于进程,是 CPU 调度的最小单位。一个进程可以开启多个线程,各线程共享进程的部分资源,同时具有少量各自的资源。
  • 协程又称微线程,纤程。不同于进程或线程,协程拥有自己的寄存器上下文和栈。通过语言层面支持,在用户态执行,避免操作系统多次系统状态切换。一个线程可以拥有多个协程。
  • 并发主要目的是充分利用现代 CPU 多核处理器,通过合理的软件设计将原本顺序执行的任务变成并行执行的任务(同时处理),提高系统的吞吐率。
  • 在 Go 语言中,每个并发执行的单元叫 goroutine,使用关键字 go 启动协程,可较容易开发高并发程序。
channel
  • channel 是 go 语言中特殊的数据类型,可以看作队列(FIFO)。通道提供了一种安全、同步的方式来共享数据,所以通常用来在各个 goroutine 之间传输数据,数据在不同协程中的传输都是通过拷贝的形式完成。
  • channel 按照接收和发送数据分为三种:

chan T // 发送和接收类型 T 的值
chan<- T // 发送类型 T 的值
<-chan T // 接收类型 T 的值

  • channel 声明与初始化。
package mainimport "fmt"func main() {// 声明通道,存储 []int 切片数据类型,默认为 nilvar c1 chan []intfmt.Println("c1: ", c1)// 使用 make 创建通道,默认容量为 0,无缓冲通道c2 := make(chan int)fmt.Println("c2: ", c2, "cap: ", cap(c2))// 使用 make 创建通道,指定通道容量,有缓冲通道c3 := make(chan int, 1)fmt.Println("c3: ", c3, "cap: ", cap(c3))// 初始化只发送通道c4 := make(chan<- int)fmt.Println("c4: ", c4, "cap: ", cap(c4))// 初始化只接收通道c5 := make(<-chan int)fmt.Println("c5: ", c5, "cap: ", cap(c5))
}
  • channel 发送操作: 将数据发送到 channel 中,语法为 ch <- data。
  • channel 接收操作: 从 channel 中接收数据,语法为 data := <- ch。
  • channel 关闭操作: 关闭 channel,语法为 close(ch)。
  • 发送和接收操作都是阻塞的,当没有 goroutine 同时操作相同通道时,通道将一直阻塞(deadlock),直到有 goroutine 进行操作。
  • 关闭操作是非阻塞的。若 channel 已经被关闭,仍然可以从中读取数据。
package mainimport ("fmt""time"
)func main() {// 初始化通道,存储 int 数据类型,无缓冲通道c1 := make(chan int)defer close(c1)// 开启 goroutine 访问通道go func() {data := <-c1fmt.Println("go func c1 len: ", len(c1), ", cap: ", cap(c1), ", data: ", data)}()// 发送数据到通道,此时需要其它 goroutine 访问此通道,否则死锁编译报错c1 <- 1fmt.Println("c1 len: ", len(c1), ", cap: ", cap(c1))// main 函数延时 10 ms,goroutine 在 main 函数中启动,需要一点点时间// 若启动过程中 main 函数退出,则 goroutine 也会退出,看不到结果输出time.Sleep(time.Millisecond * 10)
}
  • channel 有未关闭、已关闭和 nil 三种状态:
操作未关闭已关闭nil
发送阻塞或成功发送panic永久阻塞
读取阻塞或成功读取成功读取或返回零值永久阻塞
关闭成功关闭panicpanic
  • 无并发情况下,无缓冲区通道发送与读取会报错误fatal error: all goroutines are asleep - deadlock!。
package mainimport ("fmt"
)func main() {// 初始化通道,存储 int 切片数据类型,无缓冲通道,以下编译报错c1 := make(chan int)// c1 := make(chan int, 1) // 应该使用有缓冲区通道defer close(c1)c1 <- 1fmt.Println("c1 len: ", len(c1), ", cap: ", cap(c1))<-c1
}
  • channel 有缓冲区发送时,缓冲区数据容量满了后,继续发送数据会报错fatal error: all goroutines are asleep - deadlock!。
package mainimport ("fmt"
)func main() {// 初始化通道,存储 int 切片数据类型,有缓冲通道c1 := make(chan int, 1)// 	c1 := make(chan int, 2) // 应该预留两个缓冲区defer close(c1)c1 <- 1fmt.Println("c1 len: ", len(c1), ", cap: ", cap(c1))c1 <- 2fmt.Println("c1 len: ", len(c1), ", cap: ", cap(c1))<-c1
}
  • channel 数据遍历。
package mainimport "fmt"func Test() {// 初始化通道,存储 int 切片数据类型,无缓冲通道c1 := make(chan int)// 创建协程,发送数据go func(c chan int) {for i := 0; i < 5; i++ {c1 <- 1 + i}close(c1)}(c1)// 使用 for range 遍历,实际上由于不知道通道中的数据个数,如果不在协程中关闭通道,// 会一直遍历,通道中无数据时,报错 deadlockfor v := range c1 {fmt.Println(v)}
}func Test2() {// 初始化通道,存储 int 切片数据类型,无缓冲通道c1 := make(chan int)// 创建协程,发送数据go func(c chan int) {for i := 0; i < 5; i++ {c1 <- 1 + i}}(c1)// 使用普通 for 循环,知道发送多少个数据,此处接收多少个数据for i := 0; i < 5; i++ {fmt.Println(<-c1)}close(c1)
}func main() {Test()Test2()
}
goroutine
  • go 语言中,使用每个并发执行的单元叫 goroutine,启动 goroutine 使用 go 关键字,后边跟函数创建。可以在单个进程中执行成千上万的并发任务。
  • go 的并发编程采用的 CSP (Communicating Sequential Process) 模型,通过 GMP 调度模型,在用户态实现了 M:N 的线程模型(M 个 goroutine 在 N 个线程上调度,go 语言实现了其调度机制)。golang 内置的调度器,可以充分利用多核 CPU 资源。
  • 创建协程,由此例可知,使用 go 关键字创建 goroutine 非常容易,但观察输出结果可以发现,创建的 goroutine 执行顺序并不是按照创建顺序依次执行,多次执行此程序可以发现会输出多种不同的执行结果。
package mainimport ("fmt""time"
)func Test(index int) {fmt.Println("go Test ", index)
}func main() {// 使用匿名函数创建 goroutinego func() {fmt.Println("go func")}()// 使用函数创建多个 goroutinefor i := 0; i < 10; i++ {go Test(i)}// 主协程等待工作协程执行time.Sleep(time.Second)
}

输出结果
go func
go Test 9
go Test 0
go Test 1
go Test 2
go Test 3
go Test 4
go Test 5
go Test 6
go Test 7
go Test 8

  • 使用 time.Sleep 函数等待工作协程结束,实际上并不能确定工作协程执行多长时间,所以 go 语言中提供等待组(sync.WaitGroup)来等待所有协程结束。
package mainimport ("fmt""sync"
)func main() {// 初始化等待组变量,默认内部计数为 0wg := &sync.WaitGroup{}// 由于知道创建多少个协程,所以此处直接将内部计数添加 10,// 注意添加的数不能大于需要等待的协程数量,否则执行报错wg.Add(10)// 使用匿名函数创建 goroutinefor i := 0; i < 10; i++ {// wg.Add(1) // 或创建协程时依次增加内部计数go func() {fmt.Println(i)wg.Done()}()}wg.Wait()
}
  • 使用协程并发处理业务时,由于资源数据可能在不同的协程中访问修改,为了保持资源数据访问的正确性,使其运行符合预期逻辑流程,需要对共享资源数据(多个协程修改资源数据时)进行保护,达到同一时间资源数据(尤其是修改资源数据时)只能被一个协程访问。
  • 下边的例程是未对共享资源数据做任何保护,多次执行从其结果分析,预期的结果输出不稳定,也就是说在多协程同时修改共享资源数据时,结果不一定符合预期,存在资源访问竞争。
package mainimport ("fmt""sync"
)func main() {// 初始化等待组变量,默认内部计数为 0wg := &sync.WaitGroup{}wg.Add(10) // 等待 10 个子协程var index int32 = 0      // 共享变量for i := 0; i < 5; i++ { // 创建协程 5 * 2 = 10 个go func() { // 协程 1for j := 0; j < 1000; j++ {index++}wg.Done()}()go func() { // 协程 2for j := 0; j < 1000; j++ {index--}wg.Done()}()}wg.Wait()                     // 主协程等待子协程结束fmt.Println("index: ", index) // 打印修改后的最终值
}

输出结果
共享资源竞争

互斥锁
  • Go语言中提供锁机制来处理数据竞争状态,基本的锁有互斥锁(Mutex)与读写锁(RWMutex),通过对资源加锁和释放锁提供对资源同步方式访问(同一时刻只能有一个协程访问)。
  • 注意:使用互斥锁会影响程序性能(主要是程序执行速度),所以对性能要求较高的模块开发人员需要仔细斟酌取舍。
package mainimport ("fmt""sync"
)func main() {// 初始化等待组变量,默认内部计数为 0wg := &sync.WaitGroup{}lock := sync.Mutex{} // 初始化互斥锁wg.Add(10)           // 等待 10 个子协程var index int32 = 0      // 共享变量for i := 0; i < 5; i++ { // 创建协程 5 * 2 = 10 个go func() { // 协程 1for j := 0; j < 1000; j++ {// atomic.AddInt32(&index, 1)lock.Lock() // 加锁index++lock.Unlock() // 解锁}wg.Done()}()go func() { // 协程 2for j := 0; j < 1000; j++ {// atomic.AddInt32(&index, -1)lock.Lock() // 加锁index--lock.Unlock() // 解锁}wg.Done()}()}wg.Wait()                     // 主协程等待子协程结束fmt.Println("index: ", index) // 打印修改后的最终值
}
读写锁
  • Go 语言也提供读写锁来保证资源同步,此锁应用于读需求远大于写需求,也就是不需要频繁修改共享资源内容。读时加锁不影响,写时加锁保证资源数据同步性。
package mainimport ("fmt""sync""time"
)func main() {// 初始化等待组变量,默认内部计数为 0wg := &sync.WaitGroup{}lock := sync.RWMutex{} // 初始化互斥锁wg.Add(6)              // 等待 6 个子协程var index int32 = 0 // 共享变量go func() {         // 协程, 定时修改变量值for j := 0; j < 5; j++ {lock.Lock() // 加写锁index++lock.Unlock() // 解锁time.Sleep(time.Second) // 定时 1 秒}wg.Done()}()for i := 0; i < 5; i++ { // 创建协程 5 个go func() { // 协程 1for {lock.RLock() // 加读锁fmt.Printf("%v, ", index)lock.RUnlock()                     // 解锁time.Sleep(time.Millisecond * 250) // 定时 0.25 秒if index >= 5 { // 满足条件退出协程break}}wg.Done()}()}wg.Wait()                       // 主协程等待子协程结束fmt.Println("\nindex: ", index) // 打印修改后的最终值
}

输出结果
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
index: 5

原子操作
  • 原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch(上下文切换)。
  • go 语言中也提供原子操作用于解决并发编程中的资源数据竞争问题,在 sync/atomic 包提供了对基本数据类型的原子操作支持。
  • 原子操作主要提供了五类操作函数:Add*(增加值)、Load*(读取值)、Store*(存储值)、Swap*(更新值)、CompareAndSwap*(比较第一个参数引用的值是否与第二个参数值相同,若相同则将第一个参数值更新为第三个参数,同时返回 true,若不相同,第一个参数值不变,返回 false)。
package mainimport ("fmt""sync""sync/atomic"
)func main() {// 初始化等待组变量,默认内部计数为 0wg := &sync.WaitGroup{}wg.Add(10) // 等待 10 个子协程var index int32 = 0      // 共享变量for i := 0; i < 5; i++ { // 创建协程 5 * 2 = 10 个go func() { // 协程 1for j := 0; j < 1000; j++ {atomic.AddInt32(&index, 1)}wg.Done()}()go func() { // 协程 2for j := 0; j < 1000; j++ {atomic.AddInt32(&index, -1)}wg.Done()}()}wg.Wait()                     // 主协程等待子协程结束fmt.Println("index: ", index) // 打印修改后的最终值
}

输出结果
原子操作避免资源竞态

select
  • 对于 channel,当写入无缓冲区通道或有缓冲区通道已被写满时,如果继续写入通道会阻塞,直到通道中有数据被读取。同样的,当通道中无元素,继续读取也会阻塞,直到通道被写入数据。
  • go 语言从语言层面提供了一种 IO 多路复用机制,可同时对多个通道进行监听(写入或读取)。select-case 是一种控制结构,写法有点类似于用于 switch 语句,实际规则不相同。
  • 如果所有的 case 的 channel 都不可读或不可写,此时若有 default 分支会执行此分支然后退出 select 流程。
package mainimport ("fmt""time"
)func main() {// 定义两个通道chan1 := make(chan int)chan2 := make(chan int)go func() { // 协程 1,向通道1中写入数据time.Sleep(time.Second) // 延时 1 秒后写入数据chan1 <- 1}()go func() { // 协程 2,向通道2中写入数据for {time.Sleep(time.Second) // 延时 1 秒后写入数据chan2 <- 1}}()select { // 主协程监听两个通道case <-chan2:fmt.Println("channel 2 ready.")case <-chan1:fmt.Println("channel 1 ready.")default: // 默认分支fmt.Println("default case")}fmt.Println("main function exit.")
}

输出结果
default case
main function exit.

  • 若没有 default 分支,select 将阻塞所在协程,直至有可读或可写的通道为止。若存在多个 case 的 channel 可读或可写,则随机选择一个 case 进行处理,然后退出 select 流程。
package mainimport ("fmt""time"
)func main() {// 定义两个通道chan1 := make(chan int)chan2 := make(chan int)go func() { // 协程 1,向通道1中写入数据time.Sleep(time.Second)chan1 <- 1}()go func() { // 协程 2,向通道2中写入数据for {time.Sleep(time.Second)chan2 <- 1}}()// 无 default 分支,会阻塞主协程,直到有通道可读或可写时执行 case 分支,然后退出 select 流程select { case <-chan2:fmt.Println("channel 2 ready.")case <-chan1:fmt.Println("channel 1 ready.")}fmt.Println("main function exit.")
}

输出结果
存在多个 case 分支

  • 若 select 语句块为空,则会阻塞 select 所在协程,遇到 panic 退出,若无通道,空 select 执行报错。
package mainimport ("fmt""time"
)func main() {// 定义两个通道chan1 := make(chan int)chan2 := make(chan int)go func() { // 协程 1,向通道1中写入数据for {time.Sleep(time.Second)chan1 <- 1}}()go func() { // 协程 2,向通道2中写入数据for {time.Sleep(time.Second)chan2 <- 1}}()go func() { // 读取通道值,避免死锁for {<-chan1<-chan2}}()// 主协程在此阻塞,不能执行到最后一行的 fmt 语句select {}fmt.Println("main function exit.")
}
超时处理
  • 在实际开发中,超时处理机制比较常见,可以通过 select、select + time.After、select + context 实现对执行操作超时的控制。
  • select 方式实现超时处理机制。
package mainimport ("fmt""time"
)func main() {// 定义两个通道chan1 := make(chan int)timeout := make(chan bool)go func() { // 协程 1,定时向通道1中写入数据i := 100for {time.Sleep(time.Second)i++chan1 <- i}}()go func() { // 协程 2,模拟 5 秒超时time.Sleep(time.Second * 5)timeout <- true}()for { // 循环监听通道var tmout bool = falseselect {case data := <-chan1:fmt.Printf("%v  ", data)case tmout = <-timeout:fmt.Println("\ntimeout ", tmout)}if tmout { // 超时,退出监听通道break}}fmt.Println("main function exit.")
}

输出结果
101 102 103 104
timeout true
main function exit.

  • 使用 select + time.After 方式实现超时处理机制。
package mainimport ("fmt""time"
)func main() {// 定义两个通道chan1 := make(chan int)go func() { // 协程 1,定时向通道1中写入数据i := 100var t int64 = 0for {t++time.Sleep(time.Second * time.Duration(t)) // 模拟写数据延时 1,2,3...i++chan1 <- i}}()for { // 循环监听通道var tmout bool = falseselect {case data := <-chan1:fmt.Printf("%v  ", data)case <-time.After(time.Second * 2): // 模拟 select 2 秒未处理通道表示超时tmout = truefmt.Println("\ntimeout ", tmout)}if tmout { // 超时,退出监听通道break}}fmt.Println("main function exit.")
}

输出结果
101
timeout true
main function exit.

  • 使用 select + context 方式实现超时处理机制。
package mainimport ("context""fmt""time"
)func main() {// 定义两个通道chan1 := make(chan int)ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) // 设置模拟 3 秒超时时间defer cancel()go func() { // 协程 1,定时向通道1中写入数据i := 100var t int64 = 0for {t++time.Sleep(time.Second * time.Duration(t)) // 模拟写数据延时 1,2,3...i++chan1 <- i}}()for { // 循环监听通道var tmout bool = falseselect {case data := <-chan1:fmt.Printf("%v  ", data)case <-ctx.Done(): // 模拟 3 秒时间超时tmout = truefmt.Println("\ntimeout ", tmout)}if tmout { // 超时,退出监听通道break}}fmt.Println("main function exit.")
}

输出结果
101 102
timeout true
main function exit.

sync包
  • go 语言中的 sync 包是一个重要的同步原语库,它提供了一些基本的同步原语。包中括互斥锁、读写锁、原子变量、等待组、条件变量、单次执行、协程安全映射、对象池等。前边已经使用过互斥锁、读写锁、原子变量、等待组,接下来对其它几个使用例子进行简单介绍。
  • 条件变量(sync.Cond)是用于多个goroutine之间进行同步和互斥的一种机制。如下例程实现了一个简单的生产者-消费者模型。
package mainimport ("fmt""sync""time"
)var (mtx  sync.Mutex      // 定义互斥锁cond *sync.Cond      // 定义条件变量wg   *sync.WaitGroup // 定义等待组
)func Consumer(stop *bool, ch chan int, id int) {cond.L.Lock()         // 加锁defer cond.L.Unlock() // 延迟解锁for !(*stop) {cond.Wait() // 等待条件变量触发fmt.Println("go ", id, ": ", <-ch)}fmt.Println("exit ", id, "Consumer!!!")wg.Done()
}func Producer(stop *bool, ch chan int) {for i := 0; i < 8; i++ {time.Sleep(time.Second)ch <- i + 25 // 写入数据cond.L.Lock()   // 加锁cond.Signal()   // 通知一个消费者cond.L.Unlock() // 解锁}*stop = truefmt.Println("exit Producer!!!")wg.Done()close(ch) // 关闭通道cond.L.Lock()cond.Broadcast() // 通知所有消费者cond.L.Unlock()
}func main() {cond = sync.NewCond(&mtx) // 初始化条件变量stop := falsech := make(chan int, 10)wg = &sync.WaitGroup{}wg.Add(6)go Producer(&stop, ch) // 启动生产者for i := 0; i < 5; i++ { // 创建 5 个消费者go Consumer(&stop, ch, i)}wg.Wait() // 等待所有协程结束fmt.Println("exit main function")
}

输出结果
go 4 : 25
go 0 : 26
go 1 : 27
go 2 : 28
go 3 : 29
go 4 : 30
go 0 : 31
exit Producer!!!
go 1 : 32
exit 1 Consumer!!!
go 0 : 0
exit 0 Consumer!!!
go 3 : 0
exit 3 Consumer!!!
go 2 : 0
exit 2 Consumer!!!
go 4 : 0
exit 4 Consumer!!!
exit main function

  • 单次执行(sync.Once)保证某个操作只执行一次。如下例程模拟初始化环境函数只执行一次的场景。
package mainimport ("fmt""sync"
)var once sync.Oncefunc InitEnv() { // 此初始化环境函数全局只执行一次fmt.Println("InitEnv")
}func Test(wg *sync.WaitGroup, id int) { // 协程测试函数once.Do(InitEnv) // 调用执行函数fmt.Printf("Test %v\t", id)wg.Done()
}func main() {var wg sync.WaitGroupwg.Add(10)for i := 0; i < 10; i++ { // 开启 10 个测试协程go Test(&wg, i)}wg.Wait()fmt.Println("\nexit main function")
}

输出结果
InitEnv
Test 0 Test 9 Test 1 Test 2 Test 3 Test 4 Test 5 Test 6 Test 7 Test 8
exit main function

  • go 语言内置的 map 数据类型不是并发安全的,在 sync 包中提供(go 1.9 及之后)的协程安全映射(sync.Map)是并发安全的。sync.Map 的读取、插入、删除一般都是常数级的时间复杂度。
  • sync.Map 的零值是有效的,就是一个空 map。适用于读多写少的场景,当 sync.Map 在第一次使用之后,就不允许被拷贝。
package mainimport ("fmt""sync"
)func main() {var m sync.Map// 写入m.Store("Intel", 10000)m.Store("AMD", 10001)// 读取str, ok2 := m.Load("Intel")fmt.Println("read: ", ok2, "value: ", str.(int))// 遍历m.Range(func(key, value any) bool {name := key.(string)id := value.(int)fmt.Println("name: ", name, "id: ", id)return true})// 删除m.Delete("AMD")str1, ok3 := m.Load("AMD")if ok3 {fmt.Println("read: ", ok3, "value: ", str1.(int))} else {fmt.Println("read: ", ok3, "value: ", "***")}// 读取或写入m.LoadOrStore("Intel+", 10001)str2, ok4 := m.Load("Intel+")fmt.Println("read: ", ok4, "value: ", str2.(int))fmt.Println(m)
}

输出结果
read: true value: 10000
name: Intel id: 10000
name: AMD id: 10001
read: false value: ***
read: true value: 10001
{{0 0} {[] {} 0xc0000240f0} map[Intel:0xc000056028 Intel+:0xc000056040] 1}

  • 对象池,可以理解为存储对象的容器,对于很多需要重复分配、回收内存的地方,对象池(sync.Pool)是一个很好的选择。因为频繁的创建对象和回收内存,垃圾回收机制(GC)会频繁调用从而影响程序性能,可以提前创建一些对象放入对象池中,若需要使用对象时从对象池中获取,使用完成还给对象池,这样避免运行时使用时创建对象,用完时GC清理对象的开销。
package mainimport ("fmt""sync"
)var pool *sync.Pooltype Cpu struct {Name stringId   int
}func Init() {}func main() {pool = &sync.Pool{New: func() any { // 创建 New 函数fmt.Println("create a new Cpu object")return new(Cpu)},}p := pool.Get().(*Cpu) // 获取对象fmt.Println("get object: ", p)// 修改对象属性p.Name = "Intel"p.Id = 10000fmt.Println("p value: ", p)pool.Put(p)                                      // 将对象放入对象池中fmt.Println("get object 1: ", pool.Get().(*Cpu)) // 可以获取到对象fmt.Println("get object 2:", pool.Get().(*Cpu)) // 获取到临时对象p = pool.Get().(*Cpu)p.Name = "AMD"p.Id = 10010fmt.Println("p value: ", p)pool.Put(p)fmt.Println("get object3: ", pool.Get().(*Cpu)) // 获取对象fmt.Println("get object 4:", pool.Get().(*Cpu)) // 获取对象
}

输出结果
create a new Cpu object
get object: &{ 0}
p value: &{Intel 10000}
get object 1: &{Intel 10000}
create a new Cpu object
get object 2: &{ 0}
create a new Cpu object
p value: &{AMD 10010}
get object3: &{AMD 10010}
create a new Cpu object
get object 4: &{ 0}

runtime包
  • go 语言中提供 runtime 包,用于和程序的运行时环境进行交互,包中提供了一系列函数和变量方便控制、管理和监视程序的执行情况。
常用操作函数描述
Gosched当前 goroutine 让出时间片
GOROOT获取 Go 安装路径
NumCPU获取可使用的逻辑 CPU 数量
GOMAXPROCS设置当前进程可使用的逻辑 CPU 数量
NumGoroutine获取当前进程中 goroutine 的数量
Goexit退出当前 goroutine(defer语句会照常执行)
GOOS获取目标操作系统
GC执行垃圾回收机制
package mainimport ("fmt""runtime""sync""time"
)func main() {fmt.Println("GOOS: ", runtime.GOOS)fmt.Println("GOROOT: ", runtime.GOROOT())fmt.Println("NumCPU: ", runtime.NumCPU())fmt.Println("GOMAXPROCS: ", runtime.GOMAXPROCS(8)) // 返回上一次设置值wg := &sync.WaitGroup{}wg.Add(2)go func() {defer func() { // 协程退出时执行此函数fmt.Println("go func eixt!!!")wg.Done()}()for { // 协程死循环,正常运行时此协程会一直运行直到程序退出time.Sleep(time.Second * 2)runtime.Goexit() // 主动退出协程}}()go func() {for i := 0; i < 5; i++ {runtime.Gosched() // 让出协程时间片}wg.Done()}()fmt.Println("NumGoroutine: ", runtime.NumGoroutine())runtime.GC() // 主动调用垃圾回收机制wg.Wait()
}

输出结果
GOOS: windows
GOROOT: C:\Program Files\Go
NumCPU: 12
GOMAXPROCS: 12
NumGoroutine: 3
go func eixt!!!

  • GC(Garbage Collection 的缩写)表示一种自动的存储器管理机制。当一个计算机上的动态存储器不再需要时,就应该将其释放,让出存储器,这种存储器资源管理,称为垃圾回收。
  • Go 语言中提供 GC 机制,可以自动管理程序内存,释放不再使用的内存,帮助开发者管理内存,尽量使其避免一些常见的内存错误。Go 语言的GC由早起的标记-清除(mark and sweep)算法到后来的三色标记 + 混合写屏障算法,优化 GC 工作时尽量减少对主程序的影响。
  • GC 的触发方式一般有三种,第一种是内存分配达到当前内存分配的阈值(动态变化,一般下一次会设置为当前垃圾集的2倍时会再次调用 GC)时自动触发;第二种是定时(一般为 2 分钟)自动触发;第三种事手动调用触发(runtime.GC()函数)。
  • GC 调优:读程序代码进行内存优化,尽量减少额外内存开销,调整 GOGC(Go 为了保证使用 GC 的简洁性,只提供了一个参数 GOGC,减少 GOGC 对 CPU 的占用)。实际开发过程中,一般都会对程序内存优化,所以大多数情况下不需要关注 GC 相关。

起始

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/378.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

异步FIFO约束set_max_delay

1.最大延迟set_max_delay set_max_delay [-datapath_only] -from [ node_list] -to [node_list] delay_value 在Set Max Delay约束中使用-datapath_only选项时&#xff0c;它指示综合工具在优化设计时仅考虑数据通路的延迟&#xff0c;而不考虑控制逻辑的延迟。 关于最大最小…

L1正则化的数学公式

L1正则化是机器学习和统计学中常用的正则化技术&#xff0c;用于控制模型的复杂度并防止过拟合。它们的数学表达如下&#xff1a; L1正则化&#xff08;也称为Lasso正则化&#xff09;&#xff1a;在损失函数中添加模型参数的绝对值之和作为正则化项。其数学公式如下所示&…

利用地图资源工具让Sentinel-2自动生成NDVI\EVI

新版地图资源工具已经能自动计算EVI了&#xff0c;也就是现在工具可以自动计算NDVI、EVI及做哨兵L1C数据的自动预处理&#xff01;只要勾选如下选项后数据下载的同时会自动生成NDVI、EVI&#xff01; 归一化差异植被指数 (NDVI) 由于植被在近红外波段处有较强的反射&#…

Training - PyTorch Lightning 的 Horovod 策略实践 (all_gather)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/137686312 在 PyTorch Lightning 中使用 Horovod 策略&#xff0c;可以在多个 GPU 上并行训练模型。Horovod 是分布式训练框架&#xff…

Oladance、南卡、漫步者开放式耳机好不好用?3大当红明星产品测评PK

​在音频市场中&#xff0c;开放式耳机以其创新设计和卓越听感赢得了一定的关注。然而&#xff0c;也存在一些产品质量和音质不尽人意&#xff0c;甚至可能影响用户听力安全。作为一名专业的音频设备评测师&#xff0c;我建议用户在选择开放式耳机时&#xff0c;应优先选择那些…

《R语言与农业数据统计分析及建模》学习——控制流

1、控制流的概念和作用 控制流是指根据循环或条件来控制程序的执行顺序和逻辑。 2、循环语句 循环语句允许程序重复执行特定的代码块&#xff0c;知道满足特定条件。 在R语言中&#xff0c;常见的循环语句有for、while和repeat。 # for循环用于在已知次数的情况下重复执行代码…

磁盘的管理

会在linux中使用硬盘 分区 格式化&#xff08;重新安装文件系统&#xff09; 挂载 硬盘的分类 1.机械硬盘 2.固态硬盘 硬盘的数据结构 扇区&#xff1a;盘片被分为多个扇形区域&#xff0c;每个扇区存放512字节的 数据 &#xff08;扇区越多容量越大&#xff09; 存放数据的…

2024.4.13 Python 爬虫复习day01

目录 day01_HTTP协议HTML页面web服务器 各类名词解释 URL统一资源定位符 HTTP协议 HTML页面 知识点: 第一个页面 标题标签和图片标签 注册页面 登录页面 WEB服务器 安装fastapi和uvicorn 原始命令方式 镜像源命令方式 工具方式 快速搭建web服务器 知识点: 示例…

openGauss学习笔记-266 openGauss性能调优-TPCC性能调优测试指导-文件系统配置

文章目录 openGauss学习笔记-266 openGauss性能调优-TPCC性能调优测试指导-文件系统配置266.1 查看当前数据盘的文件系统类型266.2 对于需要修改的磁盘&#xff0c;备份所需的数据至其他磁盘或其他服务器266.3 格式化磁盘为xfs文件系统266.4 执行**步骤一** openGauss学习笔记-…

《Kubernetes部署篇:基于Kylin V10+ARM架构CPU+外部etcd使用containerd部署K8S 1.26.15容器版集群(多主多从)》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;企业级K8s集群运维实战 1、在当前实验环境中安装K8S1.25.14版本&#xff0c;出现了一个问题&#xff0c;就是在pod中访问百度网站&#xff0c;大…

Mysql嵌套查询太简单了

1、子查询的分类 不相关查询&#xff1a; 子查询能独立执行 相关查询&#xff1a; 子查询不能独立运行 相关查询的执行顺序&#xff1a; 首先取外层查询中表的第一个元组,根据它与内层查询相关的属性值处理内层查询, 若WHERE子句返回值为真&#xff0c;则取此元组放入结果…

maven3.9+下载安装

maven介绍 Maven 是一个项目管理和理解工具&#xff0c;它基于项目对象模型&#xff08;POM&#xff09;概念。Maven 可以帮助开发者定义项目结构、依赖关系、构建过程以及其他任务。它主要用于 Java 项目&#xff0c;但也可以用于其他类型的项目。Maven 的主要目标是简化构建…

Centos7 ElasticSearch集群搭建

1. 服务器环境配置 1.1 配置hosts文件 3台服务器都要执行 vim /etc/hosts; # 将以下内容写入3台服务器hosts文件 192.168.226.148 es001 192.168.226.149 es002 192.168.226.150 es003 1.2 关闭防火墙 3台服务器都要执行 systemctl stop firewalld; systemctl disable…

OpenCV从入门到精通实战(四)——答题卡识别判卷系统

基于OpenCV的答题卡识别系统&#xff0c;其主要功能是自动读取并评分答题卡上的选择题答案。系统通过图像处理和计算机视觉技术&#xff0c;自动化地完成了从读取图像到输出成绩的整个流程。下面是该系统的主要步骤和实现细节的概述&#xff1a; 1. 导入必要的库 系统首先导入…

偏微分方程算法之一阶双曲差分法

目录 一、研究目标 二、理论推导 2.1 引言 2.2 迎风格式 2.3 完全不稳定差分格式 2.4 蛙跳格式&#xff08;Leapfrog&#xff09; 2.5 Lax-Friedrichs格式 2.6 Lax-Wendroff格式 2.7 Beam-Warming格式 2.8 隐格式 2.9 Courant-Friedrichs-Lewy条件&#xff08;CFL条…

(51单片机)第十一章-串行口应用提高

11.1 方式0应用 在第6章中&#xff0c;已经对51单片机的串行口结构做过详细介绍&#xff0c;并且通过实例讲解了串行口的4种工作方式中方式1的具体用法&#xff0c;本节详细讲述串行口方式0的用法。 串行口方式0被称为同步移位寄存器的输入/输出方式&#xff0c;主要用于扩展并…

【结构型模式】外观模式

​一、外观模式概述 外观模式定义与意图&#xff1a;外观类为复杂的子系统提供了一个统一的入口。外观模式定义了一个高层接口&#xff0c;这个接口使得这一子系统更加容易使用。&#xff08;对象结构型模式&#xff09; 外观模式的特点&#xff1a; 1.又叫做门面模式&#xf…

试驾小米SU7后,我准备退了我的订单

文 | AUTO芯球 作者 | 雷歌 我真想退了我之前大定的小米SU7Pro版&#xff01; 前两天我不是和朋友三人一起开着问界M9去试驾SU7了嘛&#xff0c; 说实话&#xff0c;这一圈下来&#xff0c;有欣喜有失望。 SU7的优点特别明显&#xff0c;也很突出&#xff0c; 就是它的底…

了解MySQL InnoDB多版本

了解MySQL InnoDB多版本 在数据库管理系统中&#xff0c;多版本并发控制&#xff08;MVCC&#xff09;是一种用于实现高并发和事务隔离的技术。MySQL的InnoDB存储引擎支持MVCC&#xff0c;这使得它可以在提供高事务性能的同时&#xff0c;也保证了数据的一致性和隔离性 MVCC简…

MySQL中的死锁预防和解决

MySQL中的死锁预防和解决 死锁是数据库管理系统中常见的问题&#xff0c;特别是在高并发的应用场景下。MySQL数据库中的死锁会导致事务处理速度减慢&#xff0c;甚至完全停止&#xff0c;因此理解并预防死锁至关重要。本文将详细介绍如何预防MySQL中的死锁&#xff0c;包括常用…