golang并发编程-05-同步-05-临时对象池(sync.Pool)

@

1. sync.Pool结构体

1.1 结构体定义

type Pool struct {
    noCopy     noCopy
    local      unsafe.Pointer
    localSize  uintptr
    victim     unsafe.Pointer
    victimSize uintptr
    New        func() interface{}
}

1.2 作用

创建一个对象池,协程调用线程池取出对象。
如果线程池中没有对象了,线程池会创建新的对象。

1.3 创建线程池(初始化实例)

myPool := &sync.Pool{New:func()interface{}}

实际就是初始化一个Pool类型的结构体,给结构体的成员New一个值(该值是一个返回interface{}的函数)
该结构体类型我们可以在上文中看到。

2. sync.Pool结构体的常用方法

2.1 获取实例

func (p *Pool) Get() interface{}

示例

	myPool := &sync.Pool{New:func()interface{}{return xxxx }}
	myPool.Get()

2.2 放回实例

func (p *Pool) Put(x interface{})

示例

	myPool := &sync.Pool{New:func()interface{}{return xxxx }}
	myPool.Put(xxxx)

3. 示例

3.1 守荆州——存入/取出

目的:
该示例仅为展示“放入”、“取出”对象的效果。(并不具有实战意义)

  • 荆州守将出战后不回城

说明:

  • 创建线程池荆州,默认没有守将
  • 先调用,放关羽镇守
  • 五次调用:
    第一次关羽出战,
    之后线程池中没有对象了,只能新创建的对象,于是四次“新兵”出战

func main()  {
	// 建立对象荆州,默认没守将
	var jingZhou = &sync.Pool{New:func()interface{}{return "新兵"}}

	// 定义关羽
	name := "关羽"

	//放关羽守荆州
	jingZhou.Put(name)
	fmt.Printf("【%s】 奉命驻守荆州 ===> \n",name)

	// 五次出战
	for i := 0 ;i<5;i++{
		a := jingZhou.Get().(string)
		fmt.Printf("荆州守将【%s】出战 \n",a)
	}
}

打印结果

【关羽】 奉命驻守荆州 ===> 
荆州守将【关羽】出战 
荆州守将【新兵】出战 
荆州守将【新兵】出战 
荆州守将【新兵】出战 
荆州守将【新兵】出战
  • 关羽每次出战都回城

func main()  {

	var jingZhou = &sync.Pool{New:func()interface{}{return "新兵"}}

	name := "关羽"

	jingZhou.Put(name)
	fmt.Printf("【%s】 奉命驻守荆州 ===> \n",name)

	for i := 0 ;i<5;i++{
		a := jingZhou.Get().(string)
		fmt.Printf("荆州守将【%s】出战 \n",a)
		jingZhou.Put(a)
		fmt.Printf("荆州守将【%s】回城 \n",a)
	}
}
  • 打印结果
【关羽】 奉命驻守荆州 ===> 
荆州守将【关羽】出战 
荆州守将【关羽】回城 
荆州守将【关羽】出战 
荆州守将【关羽】回城 
荆州守将【关羽】出战 
荆州守将【关羽】回城 
荆州守将【关羽】出战 
荆州守将【关羽】回城 
荆州守将【关羽】出战 
荆州守将【关羽】回城 

对象关羽反复被调出、放入,因此始终没有创建新对象。

如果对是否创建新对象有质疑,可以用atomic 添加一个原子计数器(下个例子,在协程中我们会用到)

3.2 丞相叫我来巡城——多协程使用对象池

说明:

  • 启动10000个协程分别调兵巡城;
  • 创建招兵函数,军营池兵力不足则招兵。
  • 统计调兵次数和招兵次数
// 用来统计招兵次数
var recruitNum int32

func recruit() interface{}{
	num := atomic.AddInt32(&recruitNum, 1)
	fmt.Printf("==== 第 %d 次 招兵 ====\n",num)
	buffer := make([]byte,1024)
	return &buffer
}

func main() {
	// 兵营,兵力不足则招兵
	barracks := &sync.Pool{
		New: recruit,
	}
	// 多协程并发,实现巡逻任务
	numWorkers := 10000
	var wg sync.WaitGroup
	wg.Add(numWorkers)
	for i := 0; i < numWorkers; i++ {
		tmp := i
		go func(tmp int) {
			defer wg.Done()
			buffer := barracks.Get()
			//fmt.Printf("第 %d 次 调兵\n",tmp)
			_ = buffer.(*[]byte)
			//fmt.Printf("第 %d 次 调兵回营\n",tmp)
			defer barracks.Put(buffer)
		}(tmp)
	}
	wg.Wait()
	fmt.Printf("======== [ 统计 ] ========\n调兵次数:%d \n招兵次数:%d \n", numWorkers,recruitNum)
}

说明:
为了效果明显,协程调兵中注释掉了两次打印,因为打印需要耗时,占用兵力时间过长,导致招兵次数大大增加。

  • 结果打印
==== 第 3 次 招兵 ====
==== 第 1 次 招兵 ====
==== 第 5 次 招兵 ====
==== 第 6 次 招兵 ====
==== 第 7 次 招兵 ====
==== 第 8 次 招兵 ====
==== 第 9 次 招兵 ====
==== 第 10 次 招兵 ====
==== 第 11 次 招兵 ====
==== 第 12 次 招兵 ====
==== 第 13 次 招兵 ====
==== 第 14 次 招兵 ====
==== 第 15 次 招兵 ====
==== 第 16 次 招兵 ====
==== 第 17 次 招兵 ====
==== 第 4 次 招兵 ====
==== 第 18 次 招兵 ====
==== 第 19 次 招兵 ====
==== 第 20 次 招兵 ====
==== 第 21 次 招兵 ====
==== 第 22 次 招兵 ====
==== 第 2 次 招兵 ====
==== 第 23 次 招兵 ====
==== 第 24 次 招兵 ====
==== 第 25 次 招兵 ====
==== 第 26 次 招兵 ====
======== [ 统计 ] ========
调兵次数:10000 
招兵次数:26 

posted on 2022-05-15 23:10  运维开发玄德公  阅读(11)  评论(0编辑  收藏  举报  来源

导航