标准库sync包中,内置了一些实用的数据结构,而且保证线程安全。比如:MutexRWMutexWaitGroupOnceCondMapPool

参考阅读:

https://zhuanlan.zhihu.com/p/138214620

sync.Pool

Go自带了很多实用的标准库,其中sync.Pool就是用于提高内存使用效率的缓存库,而且是协程安全的。这个库被广泛使用,但是如果没有搞懂其原理,很可能存在乱用错用的情况。下面我们用一个Web开发框架中常见的上下文(RequestContext)缓存池来做介绍。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
type httpContext struct {
	name string
}

type syncPoolTest struct {
	ctxPool sync.Pool
}

func TryPoolRelease() {
	go func() {
		me := myPool.ctxPool.Get().(*httpContext)
		fmt.Println(me.name)
		me.name = "chen11"
		fmt.Println(me.name)
		myPool.ctxPool.Put(me)
		me.name = "inter-hold."
		for i := 0; i < 8; i++ {
			fmt.Printf("first name is %s\n", me.name)
			time.Sleep(1 * time.Second)
		}
	}()

	time.Sleep(1 * time.Millisecond)

	go func() {
		me := myPool.ctxPool.Get().(*httpContext)
		fmt.Println(me.name)
		me.name = "chen22"
		fmt.Println(me.name)
		myPool.ctxPool.Put(me)
	}()

	time.Sleep(1 * time.Millisecond)

	go func() {
		me := myPool.ctxPool.Get().(*httpContext)
		fmt.Println(me.name)
		me.name = "chen33"
		fmt.Println(me.name)
		myPool.ctxPool.Put(me)
	}()

	time.Sleep(5000 * time.Millisecond)

	go func() {
		me := myPool.ctxPool.Get().(*httpContext)
		fmt.Println(me.name)
		me.name = "chen44"
		fmt.Println(me.name)
		myPool.ctxPool.Put(me)
	}()

	time.Sleep(60 * time.Second)
}

func (pl *syncPoolTest) initResourcePool() {
	pl.ctxPool.New = func() interface{} {
		c := &httpContext{name: "ch"}
		return c
	}
}

var myPool syncPoolTest

func init() {
	myPool.initResourcePool()
}

// ++++++++++++++++++++++++++++++
func main() {
	pool.TryPoolRelease()
}

一种可能的结果如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
ch
chen11
first name is inter-hold.
inter-hold.
chen22
chen22
chen33
first name is chen33
first name is chen33
first name is chen33
first name is chen33
chen33
chen44
first name is chen44
first name is chen44
first name is chen44

解读:这里先后启动了4个协程(故意设置了很短的间隔时间启动),分别从Pool从取一个对象,然后修改值,再释放到Pool;而且第一个协程特意持续打印其已经归还给Pool的对象内容,来验证其中的值是否被后续的协程取走并改写。

sync.Pool中的对象会随着GC的启动而被自动回收,这就带来了很有意思的问题。大家可以多次执行代码来观察不同的结果。

结论:

  1. Pool中的对象很容易被GC回收,故Pool不适合用在数据库连接池这种创建开销比较大的场景。
  2. 放入Pool中的对象如果还有外部程序引用,Pool是不知道的,导致被分配给新的调用者,这很危险。
  3. Pool中要放入对象的引用类型。不然是对象拷贝,起不到缓存池的作用。哪怕是切片,也要放入切片的指针。

参考阅读:

https://blog.csdn.net/EDDYCJY/article/details/122725794

https://www.jianshu.com/p/8fbbf6c012b2

sync.Map

Go标准库中的runtime.Map实现还是费尽心思的,网上博客总结的挺全面,可以参考:

https://www.jianshu.com/p/0a777dc7f7ae (go map深度解析)

https://www.zhihu.com/question/587887481/answer/2946963133

sync.Map其实就是加了锁访问的runtime.Map,不过实现的时候是加了优化的。

参考阅读:

https://juejin.cn/post/7156957188840226829

sync.Cond

cond是go语言sync提供的条件变量,通过cond可以让一系列的goroutine在触发某个条件时才被唤醒。每一个cond结构体都包含一个锁L。cond只提供了三个方法:

  • Signal:调用Signal之后可以唤醒单个goroutine。
  • Broadcast:唤醒等待队列中所有的goroutine。
  • Wait:会把当前goroutine放入到队列中等待获取通知,调用此方法必须先Lock,不然方法里会调用Unlock()报错。

下面是一个示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var myCond = sync.NewCond(&sync.Mutex{})

func condPrint(cpuIdx int, cond *sync.Cond) {
	cond.L.Lock()
	cond.Wait()
	fmt.Printf("Cond of: %d\n", cpuIdx)
	cond.L.Unlock()
}

func StartCond() {
	cpuNum := runtime.NumCPU()
	fmt.Printf("cpu core num %d \n", cpuNum)

	fmt.Println("start all cond")
	for i := 0; i < cpuNum; i++ {
		go condPrint(i, myCond)
	}

	time.Sleep(time.Second * 1)
	fmt.Println("send signal")
	myCond.Signal()
	time.Sleep(time.Second * 1)
	fmt.Println("send signal")
	myCond.Signal()

	time.Sleep(time.Second * 10)
	fmt.Println("Start broadcast 3 seconds late")
	time.Sleep(time.Second * 3)
	myCond.Broadcast()
	time.Sleep(time.Second * 3)
	fmt.Println("exit. bye bye...")
}

flag.String

flag库是Go语言标准库之一,提供了命令行参数解析的能力。

Go Flag 接受的参数类型

  • bool
  • int
  • int64
  • uint
  • uint64
  • string
  • float64
  • duration

除了以上8个类型外,flag允许用户自定义数据类型,但是该类型必须实现value接口。

八种类型的函数定义除了类型不一样外,其他参数都是一样的,具体定义如下:

1
2
3
4
5
6
7
8
func Bool(name string, value bool, usage string) *bool
func Int(name string, value int, usage string) *int
func Int64(name string, value int64, usage string) *int64
func Uint(name string, value uint, usage string) *uint
func Uint64(name string, value uint64, usage string) *uint64
func String(name string, value string, usage string) *string
func Float64(name string, value float64, usage string) *float64
func Duration(name string, value time.Duration, usage string) *time.Duration

实际应用场景中,比较典型的是程序运行的时候,需要加载配置文件。在控制台运行可执行文件,一般用-f等参数指定配置文件的路径,例如:

1
2
3
4
// 三个参数意义:参数名,默认值,描述说明
var cnfFile = flag.String("f", "../cf/env.yaml", "-f env.[yaml|yml|json]")
flag.Parse()
conf.MustLoad(*cnfFile, &Config)

运行的时候还可以加-h打印所有参数的说明。有些场景用到非string类型也是可以用到上面八种数据类型的。

(完)