1. 程式人生 > >Go語言sync.Pool(執行緒池)使用

Go語言sync.Pool(執行緒池)使用

前言

Go 1.3 的sync包中加入一個新特性:Pool。

這個類設計的目的是用來儲存和複用臨時物件,以減少記憶體分配,降低CG壓力。

1

2

3

4

type Pool

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

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

New func() interface{}

1、基本使用:

(1)簡單的資料儲存。如果沒有資料,返回執行緒池指定的資料。

package main

import (
	"fmt"
	"sync"
)
func main() {
	p:=&sync.Pool{
		New: func() interface{}{
			return 0
		},
	}
	p.Put("jiangzhou")
	p.Put(123456)
	fmt.Println(p.Get())
	fmt.Println(p.Get())
	fmt.Println(p.Get())
}

2、處理臨時變數

垃圾回收一直是Go語言的一塊心病,在它執行垃圾回收的時間中,你很難做什麼。

在垃圾回收壓力大的服務中,GC佔據的CPU有可能超過2%,造成的Pause經常超過2ms。垃圾嚴重的時候,秒級的GC也出現過。

如果經常臨時使用一些大型結構體,可以用Pool來減少GC。

package main
import (
	"sync"
	"time"
	"fmt"
)

type structR6 struct {
	B1 [100000]int
}
var r6Pool = sync.Pool{
	New: func() interface{} {
		return new(structR6)
	},
}
func usePool() {
	startTime := time.Now()
	for i := 0; i < 10000; i++ {
		sr6 := r6Pool.Get().(*structR6)
		sr6.B1[0] = 0
		r6Pool.Put(sr6)
	}
	fmt.Println("pool Used:", time.Since(startTime))
}
func standard() {
	startTime := time.Now()
	for i := 0; i < 10000; i++ {
		var sr6 structR6
		sr6.B1[0] = 0
	}
	fmt.Println("standard Used:", time.Since(startTime))
}
func main() {
	standard()
	usePool()
}

一個含有100000個int值的結構體,在標準方法中,每次均新建,重複10000次,一共需要耗費188.4969ms;

如果用完的struct可以廢物利用,放回pool中。需要新的結構體的時候,嘗試去pool中取,而不是重新生成,重複10000次僅需要997.8us。

這樣簡單的操作,卻節約了99.65%的時間,也節約了各方面的資源。最重要的是它可以有效減少GC CPU和GC Pause。

(2)用途

  • sync.Pool是一個可以存或取的臨時物件集合

  • sync.Pool可以安全被多個執行緒同時使用,保證執行緒安全

  • 注意、注意、注意,sync.Pool中儲存的任何項都可能隨時不做通知的釋放掉,所以不適合用於像socket長連線或資料庫連線池。

  • sync.Pool主要用途是增加臨時物件的重用率,減少GC負擔。

func main() {
	//我們建立一個Pool,並實現New()函式
	sp := sync.Pool{
		//New()函式的作用是當我們從Pool中Get()物件時,如果Pool為空,則先通過New建立一個物件,插入Pool中,然後返回物件。
		New: func() interface{} {
			return make([]int, 16)
		},
	}
	item := sp.Get()
	//列印可以看到,我們通過New返回的大小為16的[]int
	fmt.Println("item : ", item)

	//然後我們對item進行操作
	//New()返回的是interface{},我們需要通過型別斷言來轉換
	for i := 0; i < len(item.([]int)); i++ {
		item.([]int)[i] = i
	}
	fmt.Println("item : ", item)

	//使用完後,我們把item放回池中,讓物件可以重用
	sp.Put(item)

	//再次從池中獲取物件
	item2 := sp.Get()
	//注意這裡獲取的物件就是上面我們放回池中的物件
	fmt.Println("item2 : ", item2)
	//我們再次獲取物件
	item3 := sp.Get()
	//因為池中的物件已經沒有了,所以又重新通過New()建立一個新物件,放入池中,然後返回
	//所以item3是大小為16的空[]int
	fmt.Println("item3 : ", item3)

	//測試sync.Pool儲存socket長連線池
	//testTcpConnPool()
}

注意、注意、注意,sync.Pool中儲存的任何項都可能隨時不做通知的釋放掉,所以不適合用於像socket長連線或資料庫連線池,如下圖所示。

package main

import (
	"sync"
	"net"
	"fmt"
	"runtime"
)

func main() {
	sp2 := sync.Pool{
		New: func() interface{} {
			conn, err := net.Dial("tcp", "127.0.0.1:8888");
			if err != nil {
				return nil
			}
			return conn
		},
	}
	buf := make([]byte, 1024)
	//獲取物件
	conn := sp2.Get().(net.Conn)
	//使用物件
	conn.Write([]byte("GET / HTTP/1.1 \r\n\r\n"))
	n, _ := conn.Read(buf)
	fmt.Println("conn read : ", string(buf[:n]))
	//列印conn的地址
	fmt.Println("coon地址:",conn)
	//把物件放回池中
	sp2.Put(conn)
	//我們人為的進行一次垃圾回收
	runtime.GC()
	//再次獲取池中的物件
	conn2 := sp2.Get().(net.Conn)
	//這時發現conn2的地址與上面的conn的地址不一樣了
	//說明池中我們之前放回的物件被全部清除了,顯然這並不是我們想看到的
	//所以sync.Pool不適合用於scoket長連線或資料庫連線池
	fmt.Println("coon2地址",conn2)
}