Golang基础-13

news2024/11/15 17:54:29

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 main

import "fmt"

func main() {
	// 声明通道,存储 []int 切片数据类型,默认为 nil
	var c1 chan []int
	fmt.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 main

import (
	"fmt"
	"time"
)

func main() {
	// 初始化通道,存储 int 数据类型,无缓冲通道
	c1 := make(chan int)
	defer close(c1)
	// 开启 goroutine 访问通道
	go func() {
		data := <-c1
		fmt.Println("go func c1 len: ", len(c1), ", cap: ", cap(c1), ", data: ", data)
	}()

	// 发送数据到通道,此时需要其它 goroutine 访问此通道,否则死锁编译报错
	c1 <- 1
	fmt.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 main

import (
	"fmt"
)

func main() {
	// 初始化通道,存储 int 切片数据类型,无缓冲通道,以下编译报错
	c1 := make(chan int)
	// c1 := make(chan int, 1) // 应该使用有缓冲区通道
	defer close(c1)

	c1 <- 1
	fmt.Println("c1 len: ", len(c1), ", cap: ", cap(c1))
	<-c1
}
  • channel 有缓冲区发送时,缓冲区数据容量满了后,继续发送数据会报错fatal error: all goroutines are asleep - deadlock!。
package main

import (
	"fmt"
)

func main() {
	// 初始化通道,存储 int 切片数据类型,有缓冲通道
	c1 := make(chan int, 1)
	// 	c1 := make(chan int, 2) // 应该预留两个缓冲区
	defer close(c1)

	c1 <- 1
	fmt.Println("c1 len: ", len(c1), ", cap: ", cap(c1))
	c1 <- 2
	fmt.Println("c1 len: ", len(c1), ", cap: ", cap(c1))
	<-c1
}
  • channel 数据遍历。
package main

import "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 遍历,实际上由于不知道通道中的数据个数,如果不在协程中关闭通道,
	// 会一直遍历,通道中无数据时,报错 deadlock
	for 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 main

import (
	"fmt"
	"time"
)

func Test(index int) {
	fmt.Println("go Test ", index)
}

func main() {
	// 使用匿名函数创建 goroutine
	go func() {
		fmt.Println("go func")
	}()

	// 使用函数创建多个 goroutine
	for 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 main

import (
	"fmt"
	"sync"
)

func main() {
	// 初始化等待组变量,默认内部计数为 0
	wg := &sync.WaitGroup{}

	// 由于知道创建多少个协程,所以此处直接将内部计数添加 10,
	// 注意添加的数不能大于需要等待的协程数量,否则执行报错
	wg.Add(10)
	// 使用匿名函数创建 goroutine
	for i := 0; i < 10; i++ {
		// wg.Add(1) // 或创建协程时依次增加内部计数
		go func() {
			fmt.Println(i)
			wg.Done()
		}()
	}

	wg.Wait()
}
  • 使用协程并发处理业务时,由于资源数据可能在不同的协程中访问修改,为了保持资源数据访问的正确性,使其运行符合预期逻辑流程,需要对共享资源数据(多个协程修改资源数据时)进行保护,达到同一时间资源数据(尤其是修改资源数据时)只能被一个协程访问。
  • 下边的例程是未对共享资源数据做任何保护,多次执行从其结果分析,预期的结果输出不稳定,也就是说在多协程同时修改共享资源数据时,结果不一定符合预期,存在资源访问竞争。
package main

import (
	"fmt"
	"sync"
)

func main() {
	// 初始化等待组变量,默认内部计数为 0
	wg := &sync.WaitGroup{}
	wg.Add(10) // 等待 10 个子协程

	var index int32 = 0      // 共享变量
	for i := 0; i < 5; i++ { // 创建协程 5 * 2 = 10 个
		go func() { // 协程 1
			for j := 0; j < 1000; j++ {
				index++
			}
			wg.Done()
		}()

		go func() { // 协程 2
			for j := 0; j < 1000; j++ {
				index--
			}
			wg.Done()
		}()
	}

	wg.Wait()                     // 主协程等待子协程结束
	fmt.Println("index: ", index) // 打印修改后的最终值
}

输出结果
共享资源竞争

互斥锁
  • Go语言中提供锁机制来处理数据竞争状态,基本的锁有互斥锁(Mutex)与读写锁(RWMutex),通过对资源加锁和释放锁提供对资源同步方式访问(同一时刻只能有一个协程访问)。
  • 注意:使用互斥锁会影响程序性能(主要是程序执行速度),所以对性能要求较高的模块开发人员需要仔细斟酌取舍。
package main

import (
	"fmt"
	"sync"
)

func main() {
	// 初始化等待组变量,默认内部计数为 0
	wg := &sync.WaitGroup{}

	lock := sync.Mutex{} // 初始化互斥锁
	wg.Add(10)           // 等待 10 个子协程

	var index int32 = 0      // 共享变量
	for i := 0; i < 5; i++ { // 创建协程 5 * 2 = 10 个
		go func() { // 协程 1
			for j := 0; j < 1000; j++ {
				// atomic.AddInt32(&index, 1)
				lock.Lock() // 加锁
				index++
				lock.Unlock() // 解锁
			}
			wg.Done()
		}()

		go func() { // 协程 2
			for j := 0; j < 1000; j++ {
				// atomic.AddInt32(&index, -1)
				lock.Lock() // 加锁
				index--
				lock.Unlock() // 解锁
			}
			wg.Done()
		}()
	}

	wg.Wait()                     // 主协程等待子协程结束
	fmt.Println("index: ", index) // 打印修改后的最终值
}
读写锁
  • Go 语言也提供读写锁来保证资源同步,此锁应用于读需求远大于写需求,也就是不需要频繁修改共享资源内容。读时加锁不影响,写时加锁保证资源数据同步性。
package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	// 初始化等待组变量,默认内部计数为 0
	wg := &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() { // 协程 1
			for {
				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 main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
	// 初始化等待组变量,默认内部计数为 0
	wg := &sync.WaitGroup{}

	wg.Add(10) // 等待 10 个子协程

	var index int32 = 0      // 共享变量
	for i := 0; i < 5; i++ { // 创建协程 5 * 2 = 10 个
		go func() { // 协程 1
			for j := 0; j < 1000; j++ {
				atomic.AddInt32(&index, 1)
			}
			wg.Done()
		}()

		go func() { // 协程 2
			for 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 main

import (
	"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 main

import (
	"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 main

import (
	"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 main

import (
	"fmt"
	"time"
)

func main() {
	// 定义两个通道
	chan1 := make(chan int)
	timeout := make(chan bool)

	go func() { // 协程 1,定时向通道1中写入数据
		i := 100
		for {
			time.Sleep(time.Second)
			i++
			chan1 <- i
		}
	}()

	go func() { // 协程 2,模拟 5 秒超时
		time.Sleep(time.Second * 5)
		timeout <- true
	}()

	for { // 循环监听通道
		var tmout bool = false
		select {
		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 main

import (
	"fmt"
	"time"
)

func main() {
	// 定义两个通道
	chan1 := make(chan int)

	go func() { // 协程 1,定时向通道1中写入数据
		i := 100
		var t int64 = 0
		for {
			t++
			time.Sleep(time.Second * time.Duration(t)) // 模拟写数据延时 1,2,3...
			i++
			chan1 <- i
		}
	}()

	for { // 循环监听通道
		var tmout bool = false
		select {
		case data := <-chan1:
			fmt.Printf("%v  ", data)
		case <-time.After(time.Second * 2): // 模拟 select 2 秒未处理通道表示超时
			tmout = true
			fmt.Println("\ntimeout ", tmout)
		}

		if tmout { // 超时,退出监听通道
			break
		}
	}

	fmt.Println("main function exit.")
}

输出结果
101
timeout true
main function exit.

  • 使用 select + context 方式实现超时处理机制。
package main

import (
	"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 := 100
		var t int64 = 0
		for {
			t++
			time.Sleep(time.Second * time.Duration(t)) // 模拟写数据延时 1,2,3...
			i++
			chan1 <- i
		}
	}()

	for { // 循环监听通道
		var tmout bool = false
		select {
		case data := <-chan1:
			fmt.Printf("%v  ", data)
		case <-ctx.Done(): // 模拟 3 秒时间超时
			tmout = true
			fmt.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 main

import (
	"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 = true
	fmt.Println("exit Producer!!!")
	wg.Done()

	close(ch) // 关闭通道
	cond.L.Lock()
	cond.Broadcast() // 通知所有消费者
	cond.L.Unlock()
}

func main() {
	cond = sync.NewCond(&mtx) // 初始化条件变量

	stop := false
	ch := 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 main

import (
	"fmt"
	"sync"
)

var once sync.Once

func 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.WaitGroup
	wg.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 main

import (
	"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 main

import (
	"fmt"
	"sync"
)

var pool *sync.Pool

type Cpu struct {
	Name string
	Id   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 = 10000
	fmt.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 = 10010
	fmt.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 main

import (
	"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.coloradmin.cn/o/1606090.html

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

相关文章

ArcGIS多数据框联动批量出图

这次内容是《ArcPy结合数据驱动模块的批量制图》课程的新增内容。学完这个课程大家对arcgis的数据驱动页面的批量出图应该是驾轻就熟&#xff0c;不管是无编程的完全基于ArcGIS数据驱动模块批量出图还是结合ArcPy的Mapping模块批量出图&#xff08;arcpy.mapping&#xff09;。…

MercadoLibre(美客多)入仓预约系统操作流程-自动化约号(开篇)

目录 一、添加货件信息 二、输入货件信息 三、选择发货 四、填写交货日期 五、注意事项 MercadoLibre&#xff08;美客多&#xff09;于2021年10月18号上线了新预约入仓系统&#xff0c;在MercadoLibre美客多平台上&#xff0c;新入仓预约系统是一项非常重要的功能&#x…

【技巧】win11 删除网络中心中多余的以太网信息

因为网络环境的变化&#xff0c;系统在识别网络时会出现“以太网1”&#xff0c;“以太网2”&#xff0c;“以太网3”的情况。虽然不影响使用&#xff0c;但是对于一些强迫症来说很不友好&#xff0c;通过以下方式删除&#xff1a; 1、Win R 打开&#xff0c;运行&#xff0c;…

「 安全工具介绍 」软件成分分析工具Black Duck,业界排名TOP 1的SCA工具

在现代的 DevOps 或 DevSecOps 环境中&#xff0c;SCA 激发了“左移”范式的采用。提早进行持续的 SCA 测试&#xff0c;使开发人员和安全团队能够在不影响安全性和质量的情况下提高生产力。前期在博文《「 网络安全常用术语解读 」软件成分分析SCA详解&#xff1a;从发展背景到…

open Gauss 数据库-05 openGauss数据库备份恢复指导手册

发文章是为了证明自己真的掌握了一个知识&#xff0c;同时给他人带来帮助&#xff0c;如有问题&#xff0c;欢迎指正&#xff0c;祝大家万事胜意&#xff01; 目录 前言 openGauss数据库备份恢复 1 实验介绍 1.1 关于本实验 1.2 实验目的 2 实验前提 3 物理备份和恢复…

Day3 权限管理

Day3 权限管理 这里会总结构建项目过程中遇到的问题&#xff0c;以及一些个人思考&#xff01;&#xff01; 学习方法&#xff1a; 1 github源码 文档 官网 2 内容复现 &#xff0c;实际操作 项目源码同步更新到github 欢迎大家star~ 后期会更新并上传前端项目 创建管理员…

技术周刊的转变:如何平衡热爱与现实?

大家好&#xff0c;我是那个自己打脸自己的猫哥&#xff0c;本来说周刊不做订阅制的&#xff0c;现在却推出了订阅专栏。今天想为自己辩护一下&#xff0c;同时聊聊技术周刊今后的发展计划。 首先回顾一下我过去的想法吧&#xff0c;然后再解释为什么会突然出现转变。 出于对…

Stable Diffusion 3 API 发布!超越Midjourney v6和DALL-E 3

Stable Diffusion 3 于 2 月首次宣布作为预览版发布。而今天&#xff0c;StabilityAI 正式推出了 Stable Diffusion 3 和 Stable Diffusion 3 Turbo API 的API接口服务。 Stability AI 称仍在持续改进该模型&#xff0c;并没有说明发布日期。模型还没发布&#xff0c;但API先来…

Python | Leetcode Python题解之第28题找出字符串中的第一个匹配项的下标

题目&#xff1a; 题解&#xff1a; class Solution:def strStr(self, haystack: str, needle: str) -> int:# Func: 计算偏移表def calShiftMat(st):dic {}for i in range(len(st)-1,-1,-1):if not dic.get(st[i]):dic[st[i]] len(st)-idic["ot"] len(st)1re…

JS-39-underscore01-初识underscore

一、underscore简介 前面我们已经讲过了&#xff0c;JavaScript是函数式编程语言&#xff0c;支持高阶函数和闭包。 函数式编程非常强大&#xff0c;可以写出非常简洁的代码。例如Array的map()和filter()方法&#xff1a; use strict; var a1 [1, 4, 9, 16]; var a2 a1.ma…

数据结构书后习题

p17 1&#xff0c; 个人解答&#xff1a; int DeleteMinElem(SqList &L,int &min) {int j 0;if (L.length 0){printf("error!");return 0;}int min L.data[0];for (int i 1; i < L.length; i){if (L.data[i] < min){min L.data[i];j i;}}L.dat…

电工与电子技术选择题填空题计算题复习题含参考答案

答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 电工与电子技术复习题 一 . 单选题&#xff08;共 33 题&a…

[渗透测试学习] TwoMillion-HackTheBox

TwoMillion-HackTheBox 信息搜集 nmap扫描一下 nmap -sV -v 10.10.11.221扫描结果 PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0) 80/tcp open http nginx 3851/tcp f…

SAP 技巧:如何查询TCODE的节点路径。

目录 SAP 技巧&#xff1a;如何查询TCODE的节点路径。 步骤一&#xff1a;在命令栏里输入&#xff1a;Search_SAP_Menu 会弹出以上页面&#xff0c;然后输入你想查询的T-code。回车。 步骤二&#xff1a;找到正确路径&#xff0c; SAP 技巧&#xff1a;如何查询TCODE的节点路…

0418WeCross搭建 + Caliper测试TPS

1. 基本信息 虚拟机名称&#xff1a;Pure-Ununtu18.04 WeCross位置&#xff1a;/root/wecross-demo 2. 搭建并启动WeCross 参考官方指导文档 https://wecross.readthedocs.io/zh-cn/v1.2.0/docs/tutorial/demo/demo.html 访问WeCross网页管理平台 http://localhost:8250/s/…

变频超声波驱鸟器,变电站驱鸟

随着春夏季来临&#xff0c;各种鸟类活动也愈发频繁。这一时期&#xff0c;变电站内有很多中大型鸟类&#xff0c;选择在户外架空高压裸导线龙门架、主变进线支撑架等重要设备上筑巢停留&#xff0c;它们筑巢所用的枝干和各类杂物&#xff0c;时常会掉落&#xff0c;引发设备短…

SQL Server Management Studio 显示行号

前言 在使用 SQL Server Management Studio (SSMS) 进行数据库管理和查询时&#xff0c;能够看到代码的行号是非常有用的。这可以帮助您更容易地定位代码错误、讨论特定的代码行&#xff0c;或者在执行长查询时快速找到特定行。在本文中&#xff0c;我将向您展示如何在 SSMS 中…

怎么你出的MES方案像屎一样?

最近在一个群里面&#xff0c;大家普遍感受到制定MES技术方案变得越来越困难&#xff0c;客户也变得越来越挑剔&#xff0c;方案的复杂度也在不断增加。在竞标过程中&#xff0c;各方技术水平的差距变得越来越小&#xff0c;这让人们感到相当困扰。考虑到这一问题&#xff0c;我…

OpenHarmony多媒体-ohos_videocompressor

介绍 videoCompressor是一款ohos高性能视频压缩器。 目前实现的能力&#xff1a; 支持视频压缩 使用本工程 有两种方式可以下载本工程&#xff1a; 开发者如果想要使用本工程,可以使用git命令 git clone https://gitee.com/openharmony-sig/ohos_videocompressor.git --…

U盘文件突然消失?别急,这里有数据恢复的终极攻略!

在日常的工作和生活中&#xff0c;U盘几乎成了我们随身携带的“数据小仓库”&#xff0c;存放着各种重要的文件。然而&#xff0c;就在某一天&#xff0c;你突然发现U盘中的文件神秘失踪&#xff0c;仿佛从未存在过一般。这种突如其来的U盘文件消失&#xff0c;无疑让人措手不及…