一、获取环境变量

:::info 导入包import os;

var goos string = os.Getenv("GOOS")

:::

二、产生随机数

:::info 导入包:"math/rand"、 "time"包,import "math/rand";``import "time";

设置随机种子rand.Seed(time.Now().Unix())

产生一个0到1000随机数rand.Intn(1000)

:::

  1. package main
  2. import (
  3. "fmt"
  4. "math/rand"
  5. "time"
  6. )
  7. // 求元素和
  8. func sumArr(a [10]int) int {
  9. var sum int = 0
  10. for i := 0; i < len(a); i++ {
  11. sum += a[i]
  12. }
  13. return sum
  14. }
  15. func main() {
  16. // 若想做一个真正的随机数,要种子
  17. // seed()种子默认是1
  18. //rand.Seed(1)
  19. rand.Seed(time.Now().Unix())
  20. var b [10]int
  21. for i := 0; i < len(b); i++ {
  22. // 产生一个0到1000随机数
  23. b[i] = rand.Intn(1000)
  24. }
  25. sum := sumArr(b)
  26. fmt.Printf("sum=%d\n", sum)
  27. }

三、排序

:::color2 导入包import "sort"

:::

:::info 基础类型排序**:**

sort函数sort用于各种排序,这里仅列出sort中一些常用的函数方法:

基础数据类型排序:可以对整数、浮点数、字符串进行正序和逆序排序。

sort.Sort()函数不能保证排序的稳定性。底层使用的是快排

Ints、Float64s、Strings底层使用的是Sort(),所以他们三个也是不稳定的;

go常用工具(5) - 图1

:::

  1. package main
  2. import (
  3. "fmt"
  4. "sort"
  5. )
  6. func main() {
  7. //整数排序
  8. a := []int{1,3,5,2,35}
  9. // 正序
  10. sort.Ints(a)
  11. fmt.Println("正序sort.Ints():a=",a)
  12. sort.Sort(sort.IntSlice(a))
  13. fmt.Println("正序sort.Sort():a=",a)
  14. // 逆序
  15. sort.Sort(sort.Reverse(sort.IntSlice(a)))
  16. fmt.Println("逆序:a=",a)
  17. //浮点数排序
  18. b := []float64{1.2,5.6,3.4,7.0,2.5}
  19. // 正序
  20. sort.Float64s(b)
  21. fmt.Println("正序sort.Float64s():b=",b)
  22. sort.Sort(sort.Float64Slice(b))
  23. fmt.Println("正序sort.Sort():b=",b)
  24. // 逆序
  25. sort.Sort(sort.Reverse(sort.Float64Slice(b)))
  26. fmt.Println("逆序:b=",b)
  27. //字符串排序
  28. c := []string{"aaa","dds","ccc","bbd"}
  29. // 正序
  30. sort.Strings(c)
  31. fmt.Println("正序sort.Strings():c=",c)
  32. sort.Sort(sort.StringSlice(c))
  33. fmt.Println("正序sort.Sort():c=",c)
  34. // 逆序
  35. sort.Sort(sort.Reverse(sort.StringSlice(c)))
  36. fmt.Println("逆序:c=",c)
  37. }

:::info sort.Stable()函数能保证排序的稳定性。底层使用的是插入排序

:::

  1. package main
  2. import (
  3. "fmt"
  4. "sort"
  5. )
  6. func main() {
  7. //整数排序
  8. a := []int{1,3,5,2,35}
  9. // 正序
  10. sort.Stable(sort.IntSlice(a))
  11. fmt.Println("正序sort.Stable():a=",a)
  12. //浮点数排序
  13. b := []float64{1.2,5.6,3.4,7.0,2.5}
  14. sort.Stable(sort.Float64Slice(b))
  15. fmt.Println("正序sort.Stable():b=",b)
  16. //字符串排序
  17. c := []string{"aaa","dds","ccc","bbd"}
  18. // 正序
  19. sort.Stable(sort.StringSlice(c))
  20. fmt.Println("正序sort.Stable():c=",c)
  21. }

:::warning 结构体类型排序

Slice()是不稳定的,底层使用的是快排

:::

  1. package main
  2. import (
  3. "fmt"
  4. "sort"
  5. )
  6. //定义一个结构体
  7. type Student struct {
  8. Score int
  9. Name string
  10. }
  11. func main() {
  12. x := []Student{ {2, "lcc"},{1, "ry"},{2, "yyr"}, {3, "lyh"}}
  13. sort.Slice(x, func(i, j int) bool {
  14. if x[i].Score != x[j].Score {
  15. return x[i].Score < x[j].Score
  16. } else {
  17. return x[i].Name < x[j].Name
  18. }
  19. })
  20. fmt.Println(x)
  21. }

:::warning SliceStable()是稳定的,底层使用的是插入排序

:::

  1. package main
  2. import (
  3. "fmt"
  4. "sort"
  5. )
  6. //定义一个结构体
  7. type Student struct {
  8. Score int
  9. Name string
  10. }
  11. func main() {
  12. x := []Student{ {2, "lcc"},{1, "ry"},{2, "yyr"}, {3, "lyh"}}
  13. sort.SliceStable(x, func(i, j int) bool {
  14. if x[i].Score != x[j].Score {
  15. return x[i].Score < x[j].Score
  16. } else {
  17. return x[i].Name < x[j].Name
  18. }
  19. })
  20. fmt.Println(x)
  21. }

:::warning 判断是否有序

sort.IsSorted()只能判断正序,尽管元素已经为逆序,也要经过sort.Reverse()处理一下,否则返回false。

:::

  1. package main
  2. import (
  3. "fmt"
  4. "sort"
  5. )
  6. func main() {
  7. //判断是否有序
  8. fmt.Println(sort.IsSorted(sort.IntSlice([]int{5,4,3,2}))) // fasle
  9. fmt.Println(sort.IsSorted(sort.Reverse(sort.IntSlice([]int{5,4,3,2})))) //true
  10. fmt.Println(sort.IsSorted(sort.IntSlice([]int{1,2,4,5}))) //true
  11. }

:::warning 查找元素**:**

Search函数采用二分法搜索找到[0, n)区间内最小的满足f(i)==true的值i,如果没有该值,函数会返回n。

常用的就是找元素插入位置:找到值x在插入一个有序的、可索引的数据结构时,应插入的位置

SearchInts、SearchFloat64s、SearchStrings都是将Search封装了一层。

:::

  1. package main
  2. import (
  3. "fmt"
  4. "sort"
  5. )
  6. 定义一个结构体
  7. type Student struct {
  8. Score int
  9. Name string
  10. }
  11. func main() {
  12. // 整数查找
  13. a := []int{1, 2, 3, 5, 35}
  14. fmt.Println(sort.SearchInts(a, 4)) //3
  15. // 浮点数查找
  16. b := []float64{1.2, 2.5, 3.4, 5.6, 7.0}
  17. fmt.Println(sort.SearchFloat64s(b, 3.0)) //2
  18. // 字符串查找
  19. c := []string{"aaa", "bbd", "ccc", "dds"}
  20. fmt.Println(sort.SearchStrings(c, "cac")) //2
  21. // 自定义查找——查找学生中score为2且名字为lca学生的插入位置
  22. x := []Student{{1, "ry"}, {2, "lcc"}, {2, "yyr"}, {3, "lyh"}}
  23. fmt.Println(sort.Search(len(x), func(i int) bool {
  24. return x[i].Score == 2 && x[i].Name >= "lca"
  25. })) //1
  26. }

四、tag和json序列化和反序列化

:::color2 导入包import "encoding/json"

:::

:::color1 结构体与JSON序列化:JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号””包裹使用冒号:分隔,然后**紧接着值(不要有空格)多个键值之间使用英文,分隔**

:::

  1. //Student 学生
  2. type Student struct {
  3. ID int
  4. Gender string
  5. Name string
  6. }
  7. //Class 班级
  8. type Class struct {
  9. Title string
  10. Students []*Student
  11. }
  12. func main() {
  13. c := &Class{
  14. Title: "101",
  15. Students: make([]*Student, 0, 200),
  16. }
  17. for i := 0; i < 10; i++ {
  18. stu := &Student{
  19. Name: fmt.Sprintf("stu%02d", i),
  20. Gender: "男",
  21. ID: i,
  22. }
  23. c.Students = append(c.Students, stu)
  24. }
  25. //JSON序列化:结构体-->JSON格式的字符串
  26. data, err := json.Marshal(c)
  27. if err != nil {
  28. fmt.Println("json marshal failed")
  29. return
  30. }
  31. fmt.Printf("json:%s\n", data)
  32. //JSON反序列化:JSON格式的字符串-->结构体
  33. str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}`
  34. c1 := &Class{}
  35. err = json.Unmarshal([]byte(str), c1)
  36. if err != nil {
  37. fmt.Println("json unmarshal failed!")
  38. return
  39. }
  40. fmt.Printf("%#v\n", c1)
  41. }

:::color1 结构体标签(Tag):Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来;Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:key1:"value1" key2:"value2"

结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。 注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。

tag及json序列化相关

  1. 结构体需要序列化的字段要大写,小写不被序列化;
  2. 没有写tag的结构体成员只要首字母大写也可以序列化,json序列化是默认使用字段名作为key;
  3. 结构体成员后面通过指定tag实现json序列化该字段时的key为tag里面的key(主要思想就是结构体中的成员首字母小写对外不可见,但是我们把成员定义为首字母大写这样与外界进行数据交互会带来极大的不便,此时tag带来了解决方法

:::

  1. //Student 学生
  2. type Student struct {
  3. ID int `json:"id"` //通过指定tag实现json序列化该字段时的key
  4. Gender string //json序列化是默认使用字段名作为key
  5. name string //私有不能被json包访问
  6. }
  7. func main() {
  8. s1 := Student{
  9. ID: 1,
  10. Gender: "女",
  11. name: "pprof",
  12. }
  13. data, err := json.Marshal(s1)
  14. if err != nil {
  15. fmt.Println("json marshal failed!")
  16. return
  17. }
  18. fmt.Printf("json str:%s\n", data) //json str:{"id":1,"Gender":"女"}
  19. }

五、加锁 & 线程安全的sync.Map

:::color2 导入包import "sync"

:::

方式一:sync.Map

在 2017 年发布的 <font style="color:rgb(51, 51, 51);">Go 1.9</font>中正式加入了并发安全的字典类型<font style="color:rgb(51, 51, 51);">sync.Map</font>。这个字典类型提供了一些常用的键值存取操作方法,并保证了这些操作的并发安全。同时,它的存、取、删等操作都可以基本保证在常数时间内执行完毕。换句话说,它们的算法复杂度与map类型一样都是O(1)的。在有些时候,与单纯使用原生map和互斥锁的方案相比,使用sync.Map可以显著地减少锁的争用。sync.Map本身虽然也用到了锁,但是,它其实在尽可能地避免使用锁。
  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. func main() {
  7. var ma sync.Map// 该类型是开箱即用,只需要声明既可
  8. ma.Store("key", "value") // 存储值
  9. ma.Delete("key") //删除值
  10. ma.LoadOrStore("key", "value")// 获取值,如果没有则存储
  11. fmt.Println(ma.Load("key"))//获取值
  12. //遍历
  13. ma.Range(func(key, value interface{}) bool {
  14. fmt.Printf("key:%s ,value:%s \n", key, value)
  15. //如果返回:false,则退出循环,
  16. return true
  17. })
  18. }

方式二:增加同步机制

map在并发访问中使用不安全,因为不清楚当同时对map进行读写的时候会发生什么,如果像通过goroutine进行并发访问,则需要一种同步机制来保证访问数据的安全性。一种方式是使用**<font style="color:#DF2A3F;">sync.RWMutex</font>**读写锁
  1. package main
  2. import (
  3. "sync"
  4. )
  5. func main() {
  6. // 通过匿名结构体声明了一个变量counter,变量中包含了map和sync.RWMutex
  7. var counter = struct{
  8. sync.RWMutex
  9. m map[string]int
  10. }{m: make(map[string]int)}
  11. // 读取数据的时候使用读锁
  12. counter.RLock()
  13. n := counter.m["Tony"]
  14. counter.RUnlock()
  15. // 写数据的使用使用写锁
  16. counter.Lock()
  17. counter.m["Tony"]++
  18. counter.Unlock()
  19. }

六、runtime包

:::color2 导入包:import "runtime"

:::

:::warning **<font style="color:rgb(51, 51, 51);">runtime.Gosched()</font>**让出CPU时间片,重新等待安排任务;

:::

  1. package main
  2. import (
  3. "fmt"
  4. "runtime"
  5. )
  6. func main() {
  7. go func(s string) {
  8. for i := 0; i < 2; i++ {
  9. fmt.Println(s)
  10. }
  11. }("world")
  12. // 主协程
  13. for i := 0; i < 2; i++ {
  14. // 切一下,再次分配任务
  15. runtime.Gosched()
  16. fmt.Println("hello")
  17. }
  18. }
  19. // 输出
  20. /*
  21. world
  22. world
  23. hello
  24. hello
  25. */

:::warning **<font style="color:rgb(51, 51, 51);">runtime.Goexit()</font>**退出当前协程;

:::

  1. package main
  2. import (
  3. "fmt"
  4. "runtime"
  5. )
  6. func main() {
  7. go func() {
  8. defer fmt.Println("A.defer")
  9. func() {
  10. defer fmt.Println("B.defer")
  11. // 结束协程
  12. runtime.Goexit()
  13. defer fmt.Println("C.defer")
  14. fmt.Println("B")
  15. }()
  16. fmt.Println("A")
  17. }()
  18. for {
  19. }
  20. }
  21. // 输出
  22. /*
  23. B.defer
  24. A.defer
  25. */

:::warning **<font style="color:rgb(51, 51, 51);">runtime.GOMAXPROCS</font>**Go运行时的调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU核心数

:::

  1. package main
  2. import (
  3. "fmt"
  4. "runtime"
  5. "time"
  6. )
  7. func a() {
  8. for i := 1; i < 10; i++ {
  9. fmt.Println("A:", i)
  10. }
  11. }
  12. func b() {
  13. for i := 1; i < 10; i++ {
  14. fmt.Println("B:", i)
  15. }
  16. }
  17. func main() {
  18. runtime.GOMAXPROCS(1)
  19. go a()
  20. go b()
  21. time.Sleep(time.Second)
  22. }

七、time包

:::color2 导入包<font style="color:rgb(51, 51, 51);">import "time"</font>

时间和日期是我们编程中经常会用到的;

time包:提供了时间的显示和测量用的函数。日历的计算采用的是公历

:::

:::color2 时间类型(**time.Time**):我们可以通过<font style="color:rgb(51, 51, 51);">time.Now()</font>函数获取当前的时间对象,然后获取时间对象的年月日时分秒等信息。

:::

:::color2 获取时间格式方法

now := time.Now()打印now:2023-04-08 10:56:45.6546694 +0800 CST m=+0.007066301

timestamp := now.Unix()打印timestamp:1680922823

timestamp := now.UnixNano()打印timestamp:1680922823775088700

timeObj := time.Unix(timestamp, 0)打印timeObj:2023-04-08 11:09:00 +0800 CST

:::

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func main() {
  7. now := time.Now() //获取当前时间
  8. fmt.Printf("current time:%T\n", now) // current time:time.Time
  9. fmt.Printf("current time:%v\n", now) // current time:2023-04-08 10:56:45.6546694 +0800 CST m=+0.007066301
  10. year := now.Year() //年
  11. month := now.Month() //月
  12. day := now.Day() //日
  13. hour := now.Hour() //小时
  14. minute := now.Minute() //分钟
  15. second := now.Second() //秒
  16. fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second) //2023-04-08 10:56:45
  17. }

:::color2 时间戳:时间戳是自1970年1月1日(08:00:00GMT)至当前时间的总毫秒数。它也被称为Unix时间戳(UnixTimestamp)

<font style="color:rgb(51, 51, 51);">time.Unix()</font>:函数可以将时间戳转为时间格式

:::

  1. func timestampDemo() {
  2. now := time.Now() // 获取当前时间
  3. timestamp1 := now.Unix() // 时间戳
  4. timestamp2 := now.UnixNano() // 纳秒时间戳
  5. fmt.Printf("current timestamp1:%v\n", timestamp1) // current timestamp1:1680922823
  6. fmt.Printf("current timestamp2:%v\n", timestamp2) // current timestamp2:1680922823775088700
  7. }
  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func main() {
  7. now := time.Now() //获取当前时间
  8. timestamp := now.Unix() //时间戳
  9. timeObj := time.Unix(timestamp, 0) //将时间戳转为时间格式 //2023-04-08 11:09:00 +0800 CST
  10. fmt.Println(timeObj)
  11. year := timeObj.Year() //年
  12. month := timeObj.Month() //月
  13. day := timeObj.Day() //日
  14. hour := timeObj.Hour() //小时
  15. minute := timeObj.Minute() //分钟
  16. second := timeObj.Second() //秒
  17. fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second) // 2023-04-08 11:09:00
  18. }

:::color2 时间间隔类型(**time.Duration**:time.Duration是time包定义的一个类型,以纳秒为单位,time.Duration表示1纳秒,time.Second表示1秒;

:::

  1. const (
  2. Nanosecond Duration = 1
  3. Microsecond = 1000 * Nanosecond
  4. Millisecond = 1000 * Microsecond
  5. Second = 1000 * Millisecond
  6. Minute = 60 * Second
  7. Hour = 60 * Minute
  8. )

:::color2 时间操作

Addfunc (t Time) Add(d Duration) Time

Sub求两个时间之间的差值func (t Time) Sub(u Time) Duration,返回一个时间段t-u

Equalfunc (t Time) Equal(u Time) bool判断两个时间是否相同,会考虑时区的影响,因此不同时区标准的时间也可以正确比较;

Beforefunc (t Time) Before(u Time) bool如果t代表的时间点在u之前,返回真;否则返回假。

Afterfunc (t Time) After(u Time) bool如果t代表的时间点在u之后,返回真;否则返回假。

:::

  1. func main() {
  2. now := time.Now()
  3. later := now.Add(time.Hour) // 当前时间加1小时后的时间
  4. fmt.Println(later)
  5. }

:::color2 定时器使用<font style="color:rgb(51, 51, 51);">time.Tick(时间间隔)</font>来设置定时器,定时器的本质上是一个通道(channel)

:::

  1. func tickDemo() {
  2. ticker := time.Tick(time.Second) //定义一个1秒间隔的定时器
  3. for i := range ticker {
  4. fmt.Println(i)//每秒都会执行的任务
  5. }
  6. }

:::color2 时间格式化时间类型有一个自带的方法Format进行格式化,需要注意的是Go语言中格式化时间模板不是常见的Y-m-d H:M:S,而是使用Go的诞生时间2006年1月2号15点04分记忆口诀为2006 1 2 3 4)。也许这就是技术人员的浪漫吧。

补充:如果想格式化为12小时方式,需指定PM

:::

  1. now := time.Now()
  2. // 格式化的模板为Go的出生时间2006年1月2号15点04分 Mon Jan
  3. // 24小时制
  4. fmt.Println(now.Format("2006-01-02 15:04:05.000 Mon Jan")) // 2023-04-08 11:36:44.841 Sat Apr
  5. // 12小时制
  6. fmt.Println(now.Format("2006-01-02 03:04:05.000 PM Mon Jan")) // 2023-04-08 11:36:44.841 AM Sat Apr
  7. fmt.Println(now.Format("2006/01/02 15:04")) // 2023/04/08 11:36
  8. fmt.Println(now.Format("15:04 2006/01/02")) // 11:36 2023/04/08
  9. fmt.Println(now.Format("2006/01/02")) // 2023/04/08

八、sync.WaitGroup

:::success 引包import "sync"

在代码中生硬的使用time.Sleep肯定是不合适的,Go语言中可以使用sync.WaitGroup来实现并发任务的同步。 sync.WaitGroup有以下几个方法:

需要注意sync.WaitGroup是一个结构体,传递的时候要传递指针。

:::

方法名 功能
(wg * WaitGroup) Add(delta int) 计数器+delta
(wg *WaitGroup) Done() 计数器-1
(wg *WaitGroup) Wait() 阻塞直到计数器变为0

sync.WaitGroup内部维护着一个计数器,计数器的值可以增加和减少。例如当我们启动了N 个并发任务时,就将计数器值增加N。每个任务完成时通过调用Done()方法将计数器减1。通过调用Wait()来等待并发任务执行完,当计数器值为0时,表示所有并发任务已经完成。

  1. var wg sync.WaitGroup
  2. func hello() {
  3. defer wg.Done()
  4. fmt.Println("Hello Goroutine!")
  5. }
  6. func main() {
  7. wg.Add(1)
  8. go hello() // 启动另外一个goroutine去执行hello函数
  9. fmt.Println("main goroutine done!")
  10. wg.Wait()
  11. }

九、sync.Once.Do

:::success 引包import "sync"

**<font style="color:#DF2A3F;">sync.Once</font>**只执行一次场景任务;在编程的很多场景下我们需要确保某些操作在高并发的场景下只执行一次,例如只加载一次配置文件、只关闭一次通道等。

<font style="color:rgb(51, 51, 51);">sync.Once</font>只有一个Do方法:func (o *Once) Do(f func()) {}

注意:如果要执行的函数f需要传递参数就需要搭配闭包来使用。

:::

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. )
  7. var once sync.Once
  8. func main() {
  9. for i, v := range make([]string, 10) {
  10. once.Do(onces)
  11. fmt.Println("count:", v, "---", i)
  12. }
  13. for i := 0; i < 5; i++ {
  14. go func() {
  15. once.Do(onced)
  16. fmt.Println("213")
  17. }()
  18. }
  19. time.Sleep(4000)
  20. }
  21. func onces() {
  22. fmt.Println("执行onces")
  23. }
  24. func onced() {
  25. fmt.Println("执行onced")
  26. }
  27. /*
  28. PS go run .\3\3-6-once.go
  29. 执行onces
  30. count: --- 0
  31. count: --- 1
  32. count: --- 2
  33. count: --- 3
  34. count: --- 4
  35. count: --- 5
  36. count: --- 6
  37. count: --- 7
  38. count: --- 8
  39. count: --- 9
  40. 213
  41. 213
  42. 213
  43. 213
  44. 213
  45. PS
  46. */

十、阻塞等待信号输入

:::success 导包**import ****"os"****import ****"os/signal"****import ****"syscall"**

:::

  1. import (
  2. "fmt"
  3. "os"
  4. "os/signal"
  5. "syscall"
  6. )
  7. //Ctrl +C 退出
  8. sig := make(chan os.Signal, 1)
  9. signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
  10. fmt.Printf("wait Ctrl +C\n")
  11. fmt.Printf("quit (%v)\n", <-sig) // 这里有缓冲管道阻塞

十一、互斥锁sync.Mutex

:::success 数据竞态:可能会存在多个goroutine同时操作一个资源(临界区),这种情况会发生竞态问题;

竞态检查工具**是基于运行时代码检查,而不是通过代码静态分析来完成的。这意味着那些没有机会运行到的代码逻辑中如果存在安全隐患,它是检查不出来的。需要加上-race 执行**。go run <font style="color:#DF2A3F;">-race</font> 代码

:::

:::success 互斥锁(**sync.Mutex**:互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个goroutine可以访问共享资源。Go语言中使用sync包的Mutex类型来实现互斥锁。

:::

  1. // 不加锁版本 输出6379
  2. var x int64
  3. var wg sync.WaitGroup
  4. var lock sync.Mutex
  5. func add() {
  6. for i := 0; i < 5000; i++ {
  7. lock.Lock() // 加锁
  8. x = x + 1
  9. lock.Unlock() // 解锁
  10. }
  11. wg.Done()
  12. }
  13. func main() {
  14. wg.Add(2)
  15. go add()
  16. go add()
  17. wg.Wait()
  18. fmt.Println(x)
  19. }
  20. // 加锁版本 输出10000
  21. var x int64
  22. var wg sync.WaitGroup
  23. var lock sync.Mutex
  24. func add() {
  25. for i := 0; i < 5000; i++ {
  26. lock.Lock() // 加锁
  27. x = x + 1
  28. lock.Unlock() // 解锁
  29. }
  30. wg.Done()
  31. }
  32. func main() {
  33. wg.Add(2)
  34. go add()
  35. go add()
  36. wg.Wait()
  37. fmt.Println(x)
  38. }

:::success 读写互斥锁:互斥锁是完全互斥的,但是有很多实际的场景下是读多写少的,当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的,这种场景下使用读写锁是更好的一种选择。读写锁在Go语言中使用sync包中的<font style="color:rgb(51, 51, 51);">RWMutex</font>类型。

读写锁分为两种:读锁和写锁。当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。

读写锁优势:读写锁非常适合读多写少的场景,如果读和写的操作差别不大,读写锁的优势就发挥不出来。

:::

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. "sync"
  6. )
  7. var (
  8. x int64
  9. wg sync.WaitGroup
  10. lock sync.Mutex
  11. rwlock sync.RWMutex
  12. )
  13. func write() {
  14. lock.Lock() // 加互斥锁
  15. // rwlock.Lock() // 加写锁
  16. x = x + 1
  17. time.Sleep(10 * time.Millisecond) // 假设写操作耗时10毫秒
  18. // rwlock.Unlock() // 解写锁
  19. lock.Unlock() // 解互斥锁
  20. wg.Done()
  21. }
  22. func read() {
  23. lock.Lock() // 加互斥锁
  24. // rwlock.RLock() // 加读锁
  25. time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒
  26. // rwlock.RUnlock() // 解读锁
  27. lock.Unlock() // 解互斥锁
  28. wg.Done()
  29. }
  30. func main() {
  31. start := time.Now()
  32. for i := 0; i < 10; i++ {
  33. wg.Add(1)
  34. go write()
  35. }
  36. for i := 0; i < 1000; i++ {
  37. wg.Add(1)
  38. go read()
  39. }
  40. wg.Wait()
  41. end := time.Now()
  42. fmt.Println(end.Sub(start))
  43. }
  44. // 读写锁 146.8492ms
  45. // 互斥锁 15.0116383s

:::success 避免锁复制sync.Mutex是一个结构体对象,这个对象在使用的过程中要避免被复制 —— 浅拷贝。复制会导致锁被「分裂」了,也就起不到保护的作用。所以在平时的使用中要尽量使用它的指针类型

锁复制存在于结构体变量的赋值、函数参数传递、方法参数传递中,都需要注意。

:::

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. type SafeDict struct {
  7. data map[string]int
  8. mutex *sync.Mutex
  9. }
  10. func NewSafeDict(data map[string]int) *SafeDict {
  11. return &SafeDict{
  12. data: data,
  13. mutex: &sync.Mutex{},
  14. }
  15. }
  16. // defer 语句总是要推迟到函数尾部运行,所以如果函数逻辑运行时间比较长,
  17. // 这会导致锁持有的时间较长,这时使用 defer 语句来释放锁未必是一个好注意。
  18. func (d *SafeDict) Len() int {
  19. d.mutex.Lock()
  20. defer d.mutex.Unlock()
  21. return len(d.data)
  22. }
  23. // func (d *SafeDict) Test() int {
  24. // d.mutex.Lock()
  25. // length := len(d.data)
  26. // d.mutex.Unlock() // 手动解锁 减少粒度 // 这种情况就不要用 defer d.mutex.Unlock()
  27. // fmt.Println("length: ", length)
  28. // // 这里还有耗时处理 耗时1000ms
  29. // }
  30. func (d *SafeDict) Put(key string, value int) (int, bool) {
  31. d.mutex.Lock()
  32. defer d.mutex.Unlock()
  33. old_value, ok := d.data[key]
  34. d.data[key] = value
  35. return old_value, ok
  36. }
  37. func (d *SafeDict) Get(key string) (int, bool) {
  38. d.mutex.Lock()
  39. defer d.mutex.Unlock()
  40. old_value, ok := d.data[key]
  41. return old_value, ok
  42. }
  43. func (d *SafeDict) Delete(key string) (int, bool) {
  44. d.mutex.Lock()
  45. defer d.mutex.Unlock()
  46. old_value, ok := d.data[key]
  47. if ok {
  48. delete(d.data, key)
  49. }
  50. return old_value, ok
  51. }
  52. func write(d *SafeDict) {
  53. d.Put("banana", 5)
  54. }
  55. func read(d *SafeDict) {
  56. fmt.Println(d.Get("banana"))
  57. }
  58. // go run -race 3-2-lock.go
  59. func main() {
  60. d := NewSafeDict(map[string]int{
  61. "apple": 2,
  62. "pear": 3,
  63. })
  64. go read(d)
  65. write(d)
  66. }

:::info 匿名锁字段:在结构体章节,我们知道外部结构体可以自动继承匿名内部结构体的所有方法。如果将上面的SafeDict 结构体进行改造,将锁字段匿名,就可以稍微简化一下代码

:::

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. type SafeDict struct {
  7. data map[string]int
  8. *sync.Mutex
  9. }
  10. func NewSafeDict(data map[string]int) *SafeDict {
  11. return &SafeDict{
  12. data,
  13. &sync.Mutex{}, // 一样是要初始化的
  14. }
  15. }
  16. func (d *SafeDict) Len() int {
  17. d.Lock()
  18. defer d.Unlock()
  19. return len(d.data)
  20. }
  21. func (d *SafeDict) Put(key string, value int) (int, bool) {
  22. d.Lock()
  23. defer d.Unlock()
  24. old_value, ok := d.data[key]
  25. d.data[key] = value
  26. return old_value, ok
  27. }
  28. func (d *SafeDict) Get(key string) (int, bool) {
  29. d.Lock()
  30. defer d.Unlock()
  31. old_value, ok := d.data[key]
  32. return old_value, ok
  33. }
  34. func (d *SafeDict) Delete(key string) (int, bool) {
  35. d.Lock()
  36. defer d.Unlock()
  37. old_value, ok := d.data[key]
  38. if ok {
  39. delete(d.data, key)
  40. }
  41. return old_value, ok
  42. }
  43. func write(d *SafeDict) {
  44. d.Put("banana", 5)
  45. }
  46. func read(d *SafeDict) {
  47. fmt.Println(d.Get("banana"))
  48. }
  49. func main() {
  50. d := NewSafeDict(map[string]int{
  51. "apple": 2,
  52. "pear": 3,
  53. })
  54. go read(d)
  55. write(d)
  56. }

十二、配置文件解析器goconfig

:::color2 导包:**"github.com/Unknwon/goconfig"**

go常用工具(5) - 图2

section(段):[]括起来的部分是段的概念,没有在某个显式段下的为default section。

:::

:::color2 API

**<font style="color:#DF2A3F;">cfg</font>****, err := goconfig.LoadConfigFile("./conf.ini")**:加载配置文件,读取后文件就关闭了;

**cfg.GetValue(section, 字段)**:获取配置文件的string类型值,参1是section,参2是section中某个字段名;

section : "",代表默认段,或者使用常量**goconfig.DEFAULT_SECTION**

**cfg.MustInt(section, 字段)**获取配置文件的int类型值,带有Must的方法返回中无err;

**cfg.SetValue(section, 字段, 新值)**:**修改**字段值,修改的是内部的变量值,不会修改真实的配置文件;

**cfg.DeleteKey("", "MAX_PRICE")**:**删除**字段值,删除的是内部的变量值,不会修改真实的配置文件;

**cfg.AppendFiles("conf1.ini")**将更多文件追加到ConfigFile并自动加载,不会修改真实的配置文件;

:::

  1. ;redis cache
  2. USER_LIST = USER:LIST
  3. MAX_COUNT = 50
  4. MAX_PRICE = 123456
  5. IS_SHOW = true
  6. [test]
  7. dbdns = root:@tcp(127.0.0.1:3306)
  8. [prod]
  9. dbdns = root:@tcp(172.168.1.1:3306)
  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "github.com/Unknwon/goconfig"
  6. )
  7. func main() {
  8. cfg, err := goconfig.LoadConfigFile("./conf.ini") // 读取后文件关闭了
  9. if err != nil {
  10. log.Fatalf("无法加载配置文件:%s", err)
  11. }
  12. userListKey, err := cfg.GetValue("", "USER_LIST")
  13. if err != nil {
  14. fmt.Println(err.Error())
  15. }
  16. fmt.Println(userListKey)
  17. userListKey2, _ := cfg.GetValue(goconfig.DEFAULT_SECTION, "USER_LIST")
  18. fmt.Println(userListKey2)
  19. maxCount := cfg.MustInt("", "MAX_COUNT")
  20. fmt.Println(maxCount)
  21. maxPrice := cfg.MustFloat64("", "MAX_PRICE")
  22. fmt.Println(maxPrice)
  23. isShow := cfg.MustBool("", "IS_SHOW")
  24. fmt.Println(isShow)
  25. db := cfg.MustValue("test", "dbdns")
  26. fmt.Println(db)
  27. dbProd := cfg.MustValue("prod", "dbdns")
  28. fmt.Println("dbProd: ",dbProd)
  29. //set 值
  30. cfg.SetValue("", "MAX_NEW", "100")
  31. maxNew := cfg.MustInt("", "MAX_NEW")
  32. fmt.Println(maxNew)
  33. maxNew1, err := cfg.Int("", "MAX_NEW")
  34. if err != nil {
  35. fmt.Println(err.Error())
  36. }
  37. fmt.Println(maxNew1)
  38. cfg.AppendFiles("conf1.ini")
  39. cfg.DeleteKey("", "MAX_PRICE")
  40. dbProd = cfg.MustValue("prod1", "dbdns")
  41. fmt.Println(dbProd)
  42. }

十三、命令行解析器flag

:::color2 导包:**"flag"**:****

**cmd -flag** // 只支持bool类型

**cmd -flag=xxx** // 都支持

**cmd -flag xxx** // 只支持非bool类型

定义flag参数:

参数有三个:第一个为 参数名称,第二个为 默认值,第三个是 使用说明

(1)获取参数:通过 **flag.String(),Bool(),Int()****<font style="color:#DF2A3F;">flag.Xxx()</font>** 方法,该种方式返回一个相应的指针var ip = flag.Int("flagname", 1234, "help message for flagname")

(2)获取参数:通过** flag.XxxVar() **方法将 flag 绑定到一个变量,该种方式返回 值类型 var flagvar int flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")

(3)通过 **flag.Var()** 绑定自定义类型,自定义类型需要实现 Value 接口 (Receiver 必须为指针)

fmt.Println("flagvar has value ", flagvar)

flag参数列表中参数解析完arg中就没有了

:::

  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "os"
  6. )
  7. // go run 4-1-cli-flag.go -ok -id 11111 -port 8899 -name TestUser very goo
  8. func main() {
  9. fmt.Println(os.Args)
  10. ok := flag.Bool("ok", false, "is ok") // 不设置ok 则为false
  11. id := flag.Int("id", 0, "id")
  12. port := flag.String("port", ":8080", "http listen port")
  13. var name string
  14. flag.StringVar(&name, "name", "Jack", "name")
  15. flag.Parse()
  16. //flag.Usage()
  17. others := flag.Args()
  18. fmt.Println("ok:", *ok)
  19. fmt.Println("id:", *id)
  20. fmt.Println("port:", *port)
  21. fmt.Println("name:", name)
  22. fmt.Println("other:", others)
  23. }
  24. /* 执行 go run 4-1-cli-flag.go -ok -id 11111 -port 8899 -name TestUser very goo
  25. [C:\Users\Yi315\AppData\Local\Temp\go-build4257362743\b001\exe\5-1-cli-flag.exe -ok=true -id 11111 -port 8899 -name TestUser very goo]
  26. ok: true
  27. id: 11111
  28. port: 8899
  29. name: TestUser
  30. other: [very goo]
  31. */
  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. )
  6. type FlagSet struct {
  7. Usage func()
  8. }
  9. var myFlagSet = flag.NewFlagSet("myflagset", flag.ExitOnError)
  10. var stringFlag = myFlagSet.String("abc", "default value", "help mesage")
  11. func main() {
  12. myFlagSet.Parse([]string{"-abc", "def", "ghi", "123"})
  13. args := myFlagSet.Args()
  14. for i := range args {
  15. fmt.Println(i, myFlagSet.Arg(i))
  16. }
  17. }

十四、临时对象池

:::color2 引包:**import "sync"**

**sync.Pool**类型值作为存放临时值的容器。此类容器是自动伸缩的,高效的,同时也是并发安全的。

**sync.Pool类型**只有两个方法:

Put,用于在当前的池中存放临时对象,它接受一个空接口类型的值

Get,用于从当前的池中获取临时对象,它返回一个空接口类型的值

New字段**sync.Pool类型的New字段是一个创建临时对象的函数。它的类型是没有参数但是会返回一个空接口类型的函数。即:func() interface{}这个函数是Get方法最后的获取到临时对象的手段。函数的结果不会被存入当前的临时对象池中,而是直接返回给Get方法的调用方。这里的New字段的实际值需要在初始化临时对象池的时候就给定**。否则,在Get方法调用它的时候就会得到nil。

go常用工具(5) - 图3

:::

  1. type Pool struct {
  2. noCopy noCopy
  3. local unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
  4. localSize uintptr // size of the local array
  5. victim unsafe.Pointer // local from previous cycle
  6. victimSize uintptr // size of victims array
  7. // New optionally specifies a function to generate
  8. // a value when Get would otherwise return nil.
  9. // It may not be changed concurrently with calls to Get.
  10. New func() any
  11. }

:::color2 Get

Pool 会为每个 P 维护一个本地池,P 的本地池分为 **私有池 private和共享池 shared。私有池中的元素只能本地 P 使用,共享池中的元素可能会被其他 P 偷走,所以使用私有池 private时不用加锁,而使用共享池 shared 时需加锁**。

**Get** 会优先查找本地 private,再查找本地 shared,最后查找其他 P 的shared,如果以上全部没有可用元素,最后会调用 New 函数获取新元素。

从临时对象池中取出的对象,一般都要做一个reset操作

:::

:::color2 Put

Put 优先把元素放在 private 池中;如果 private 不为空,则放在 shared 池中。有趣的是,在入池之前,该元素有 1/4 可能被丢掉

go常用工具(5) - 图4

:::

  1. // Get selects an arbitrary item from the Pool, removes it from the
  2. // Pool, and returns it to the caller.
  3. // Get may choose to ignore the pool and treat it as empty.
  4. // Callers should not assume any relation between values passed to Put and
  5. // the values returned by Get.
  6. //
  7. // If Get would otherwise return nil and p.New is non-nil, Get returns
  8. // the result of calling p.New.
  9. func (p *Pool) Get() any {
  10. if race.Enabled {
  11. race.Disable()
  12. }
  13. l, pid := p.pin()
  14. x := l.private
  15. l.private = nil
  16. if x == nil {
  17. // Try to pop the head of the local shard. We prefer
  18. // the head over the tail for temporal locality of
  19. // reuse.
  20. x, _ = l.shared.popHead()
  21. if x == nil {
  22. x = p.getSlow(pid)
  23. }
  24. }
  25. runtime_procUnpin()
  26. if race.Enabled {
  27. race.Enable()
  28. if x != nil {
  29. race.Acquire(poolRaceAddr(x))
  30. }
  31. }
  32. if x == nil && p.New != nil {
  33. x = p.New()
  34. }
  35. return x
  36. }
  37. // Put adds x to the pool.
  38. func (p *Pool) Put(x any) {
  39. if x == nil {
  40. return
  41. }
  42. if race.Enabled {
  43. if fastrandn(4) == 0 {
  44. // Randomly drop x on floor.
  45. return
  46. }
  47. race.ReleaseMerge(poolRaceAddr(x))
  48. race.Disable()
  49. }
  50. l, _ := p.pin()
  51. if l.private == nil {
  52. l.private = x
  53. } else {
  54. l.shared.pushHead(x)
  55. }
  56. runtime_procUnpin()
  57. if race.Enabled {
  58. race.Enable()
  59. }
  60. }
  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "sync"
  7. )
  8. // 存放数据块缓冲区的临时对象
  9. var bufPool sync.Pool
  10. // 预定义定界符
  11. const delimiter = '\n'
  12. // 一个简易的数据库缓冲区的接口
  13. type Buffer interface {
  14. Delimiter() byte // 获取数据块之间的定界符
  15. Write(contents string) (err error) // 写入一个数据块
  16. Read() (contents string, err error) // 读取一个数据块
  17. Free() // 释放当前的缓冲区
  18. }
  19. // 实现一个上面定义的接口
  20. type myBuffer struct {
  21. buf bytes.Buffer
  22. delimiter byte
  23. }
  24. func (b *myBuffer) Delimiter() byte {
  25. return b.delimiter
  26. }
  27. func (b *myBuffer) Write(contents string) (err error) {
  28. if _, err = b.buf.WriteString(contents); err != nil {
  29. return
  30. }
  31. return b.buf.WriteByte(b.delimiter)
  32. }
  33. func (b *myBuffer) Read() (contents string, err error) {
  34. return b.buf.ReadString(b.delimiter)
  35. }
  36. func (b *myBuffer) Free() {
  37. bufPool.Put(b)
  38. }
  39. func init() {
  40. bufPool = sync.Pool{
  41. New: func() interface{} {
  42. return &myBuffer{delimiter: delimiter}
  43. },
  44. }
  45. }
  46. // 获取一个数据库缓冲区
  47. func GetBuffer() Buffer {
  48. return bufPool.Get().(Buffer) // 做类型转换
  49. }
  50. func main() {
  51. buf := GetBuffer()
  52. defer buf.Free()
  53. buf.Write("写入第一行,") // 写入数据
  54. buf.Write("接着写第二行。") // 写入数据
  55. fmt.Println("数据已经写入,准备把数据读出")
  56. for {
  57. block, err := buf.Read()
  58. if err != nil {
  59. if err == io.EOF {
  60. break
  61. }
  62. panic(fmt.Errorf("读取缓冲区时ERROR: %s", err))
  63. }
  64. fmt.Print(block)
  65. }
  66. }

:::color2 临时对象池的一个应用场景

  • 收到http请求,创建一个parser可以使用临时对象池
    • http是短连接
    • 总创建parser也很耗时,这时就可以引入对象池来提高效率
  • 返回response时,也可以使用对象池,重新装配

:::