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 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 相关。

起始

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

webpack-babel

babel Babel 是一个 JavaScript 编译器&#xff0c;主要用于将高版本的 JavaScript 代码转换为低版本的 JavaScript 代码&#xff0c;从而确保代码在不同浏览器和环境中的兼容性。它可以将 ES6/ES7/ES8 等新特性转换为 ES5 等旧版本的 JavaScript 代码&#xff0c;使得开发人员…

CSS 格式化上下文 + CSS兼容处理

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 ✍CSS 格式化上下文&#x1f525;1 格式化上下文&#x1f337;1.1 块级格式化…

微软(TTS)文本转语音服务API实现

此博客实现与java实现微软文本转语音&#xff08;TTS&#xff09;经验总结_java tts_${简简单单}的博客-CSDN博客之上&#xff0c;首先感谢博客源码的提供&#xff0c;本人在上面添加了一些详细的注释&#xff0c;方便大家跟好的理解和使用&#xff0c;毕竟我已经用原文调试了一…

python入门之简洁安装VS保姆版安装(含虚拟环境)

11、保姆版安装 Anoconda安装&#xff08;python的一个发行版本&#xff09; 优点&#xff1a;集成了许多关于python科学计算的第三方库&#xff0c;保姆级别 下载&#xff1a;www.anaconda.com/download/ 版本默认64位&#xff0c;py37 √&#xff1a;add anaconda to my…

教程 | 亚组分析森林图模块使用介绍

本周风暴统计平台最新更新了亚组森林图板块&#xff01;界面与功能进行了全新升级&#xff0c;今天就通过这篇教程为大家详细介绍&#xff0c;亚组森林图模块各种细节的设置与使用方式&#xff01; 教程将从以下方面开展&#xff1a; 1. 亚组分析使用介绍2. 不同回归分析中亚组…

Java 数据类型

一 Java 的数据类型 二 整数类型 类型占用存储空间范围byte[字节]1字节-127~127short[短整型]2字节-215~215-1 即 -32768~ 32767int[整型]4字节-231~231-1 即 -2147483648~2147483647long[长整型]8字节-263~263-1 字节 byte是计算机存储单位的基本单元&#xff0c;通常由8个比…

Redis: 集群

文章目录 一、单点Redis的问题二、主从架构1、概述2、集群结构3、主从数据同步原理&#xff08;1&#xff09;全量同步&#xff08;2&#xff09;增量同步 4、总结&#xff08;1&#xff09;全量同步和增量同步的区别&#xff08;2&#xff09;什么时候执行全量同步&#xff08…

面试经典150题——跳跃游戏 II

面试经典150题 day10 题目来源我的题解方法一 动态规划方法二 贪心 题目来源 力扣每日一题&#xff1b;题序&#xff1a;45 我的题解 方法一 动态规划 动态规划&#xff0c;当j位置可达i位置时&#xff1a;dp[i]Math.min(dp[i],dp[j]1); 时间复杂度&#xff1a;O( n 2 n^2 n…

SpringBlade dict-biz/list SQL 注入漏洞复现

0x01 产品简介 SpringBlade 是一个由商业级项目升级优化而来的 SpringCloud 分布式微服务架构、SpringBoot 单体式微服务架构并存的综合型项目。 0x02 漏洞概述 SpringBlade 后台框架 /api/blade-system/dict-biz/list 路径存在SQL注入漏洞,攻击者除了可以利用 SQL 注入漏洞…

chromedriver最新版下载地址

地址1.百度网盘 链接(提取码&#xff1a;2vo3)&#xff1a;百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固&#xff0c;支持教育网加速&#xff0c;支持手机端。注册使用百度网盘即可享受免费存储空间https://pan.baidu.com…

微信域名防封/QQ域名防封/域名状态检测/域名防红防封API平台源码

下载地址&#xff1a;API平台源码 这套源码是使用thinkphp3.1.3开发的&#xff0c;可以在PHP5.3-5.6下运行&#xff0c;程序是有一点老了&#xff0c;但是思路仍在&#xff01;然后&#xff0c;这套源码我已经成功搭建起来了&#xff0c;后台、个人&#xff08;用户&#xff0…

基于Material Design风格开源、易用、强大的WPF UI控件库

前言 今天大姚给大家分享一款基于Material Design风格开源、免费&#xff08;MIT License&#xff09;、易于使用、强大的WPF UI控件库&#xff1a;MaterialDesignInXamlToolkit。 项目介绍 MaterialDesignInXamlToolkit 是一个开源、易于使用、强大的 WPF UI 控件库&#x…

【opencv】示例-videocapture_starter.cpp 从视频文件、图像序列或连接到计算机的摄像头中捕获帧...

/** * file videocapture_starter.cpp * brief 一个使用OpenCV的VideoCapture与捕获设备&#xff0c;视频文件或图像序列的入门示例 * 就像CV_PI一样简单&#xff0c;对吧&#xff1f; * * 创建于: 2010年11月23日 * 作者: Ethan Rublee * * 修改于: 2013年4月17日 * …

mysql 查询实战3-解答

对mysql 查询实战3-题目&#xff0c;进行一个解答 11、查询每⽉产品交易与退款情况 目标&#xff1a;查询每⽉产品交易&#xff08;交易总额&#xff0c;交易数&#xff09;与退款情况&#xff08;退款总额&#xff0c;退款数&#xff09; 1&#xff0c;先把日期格式化 使用 E…

Savina Mx 高級的無塵擦拭布系列產品,吸水吸油性極強,不磨損原件

Savina Mx是日本KBSEIREN株式會社&#xff08;原KANEBO&#xff09;開發的目前*高級的無塵擦拭布系列產品&#xff0c;吸水吸油性極強&#xff0c;不磨損原件。廣氾用於光學鏡頭製造&#xff0c;辦公器材保養&#xff0c;10級以上的無塵車間淨化室&#xff0c;半導體生產線車間…

美易官方:以色列袭击伊朗!原油、黄金走势上涨?

以色列突然袭击伊朗的消息震惊了全球市场&#xff0c;引发了一场原油和黄金价格的飙升。这一事件不仅令投资者感到紧张&#xff0c;也引发了国际社会对于中东地区紧张局势的担忧。 以色列此次袭击的目标据说是伊朗的一处军事基地&#xff0c;据称该基地涉及到伊朗的核武器研发计…

Network: wirehark: 解包问题:乱序重组

如果一个大的TCP数据被分成几个segment&#xff0c;而每个segment如果走的路由途径不同的化&#xff0c;会导致下面这个解析上错误。从下面这个图里看&#xff0c;第一片和第二片的顺序的&#xff0c;但是第三片跑到了第二片的前面&#xff0c;wirehark就解析不出来了&#xff…

安卓apk文件签名

一、环境准备 链接: https://pan.baidu.com/s/1D3WxIL5M5ewyFNTqJzARPw 提取码: pd6w 上篇博文编译的apk文件 1、docker build -t android-build:v1.0.1 . 直接制作镜像 2、docker run -it android-build:v1.0.1 /bin/bash 运行进入容器 指定sdk的路径&#xff0c;然后直接…

华为欧拉系统(openEuler-22.03)安装深信服EasyConnect软件(图文详解)

欧拉镜像下载安装 iso镜像官网下载地址 选择最小化安装&#xff0c;标准模式 换华为镜像源 更换华为镜像站&#xff0c;加速下载&#xff1a; sed -i "s#http://repo.openeuler.org#https://mirrors.huaweicloud.com/openeuler#g" /etc/yum.repos.d/openEuler.r…

使用Termux在Android设备上编译运行SpecCPU2006

Spec CPU 2006 的使用说明&#xff08;曲线救国版&#xff09; 因本部分实验用到的Spec CPU2006依赖于多个编译工具包&#xff0c;因此对源码的编译要在配置好环境的Linux设备上运行&#xff0c;根据实验发现&#xff0c;现有的环境&#xff08;包括adb和termux&#xff09;都不…