Go語言開發(十三)、Go語言常用標準庫三
一、sync
1、sync簡介
sync提供基本的同步原語,如sync.Mutex,sync.RWMutex,sync.Once,sync.Cond,sync.Waitgroup,除了Once和WaitGroup類型外,大多數類型都供低級庫使用。Go語言中,不要通過共享內存通信,而要通過通信共享內存,通過Channel和溝通可以更好地完成更高級別的同步。
type Locker interface {
Lock()
Unlock()
}
Locker提供了鎖的兩個操作方法,Lock、Unlock。
2、sync.Mutex
sync.Mutex是互斥鎖,是Locker的一種實現。
sync.Mutex使用註意:
(1)在首次使用後不要復制互斥鎖。
(2)對一個未鎖定的互斥鎖解鎖將會產生運行時錯誤。
使用示例:
package main import ( "fmt" "sync" "time" ) var locker sync.Mutex func mutexDemo() { var value int = 0 for i := 0; i < 100; i++ { go func(i int) { locker.Lock() defer locker.Unlock() fmt.Printf("Goroutine %d : value: %d\n", i, value) value++ }(i) } } func main() { mutexDemo() time.Sleep(time.Second) }
3、sync.Pool
sync.Pool?臨時對象池用於存儲臨時對象,將使用完畢的對象存入對象池中,在需要的時候取出來重復使用,目的是為了避免重復創建相同的對象造成GC負擔過重。如果對象不再被其它變量引用,存放的臨時對象可能會被GC回收掉。
type Pool struct {
// 創建臨時對象的函數
New func() interface{}
}
// 向臨時對象池中存入對象
func (p *Pool) Put(x interface{})
// 從臨時對象池中取出對象
func (p *Pool) Get() interface{}
從sync.Pool中取出對象時,如果Pool中沒有對象,將返回nil,但如果給 Pool.New字段指定一個函數,Pool將使用指定函數創建一個新對象返回。
sync.Pool使用示例如下:
package main
import (
"bytes"
"io"
"os"
"sync"
"time"
)
var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func Log(w io.Writer, key, val string) {
// 獲取臨時對象,沒有則自動創建
b := bufPool.Get().(*bytes.Buffer)
b.Reset()
b.WriteString(time.Now().Format(time.RFC3339))
b.WriteByte(‘ ‘)
b.WriteString(key)
b.WriteByte(‘=‘)
b.WriteString(val)
w.Write(b.Bytes())
// 將臨時對象放回到Pool中
bufPool.Put(b)
}
func main() {
Log(os.Stdout, "key", "value")
}
// output:
// 2018-12-31T15:57:27+08:00 key=value
4、sync.Once
sync.Once可以使得函數多次調用只執行一次。
type Once struct {
m Mutex
done uint32
}
func (o *Once) Do(f func())
用done來記錄執行次數,用互斥鎖m來保證僅被執行一次。只有一個Do方法,調用執行。
利用sync.Once實現單例模式代碼如下:
package Singleton
import (
"sync"
)
type Singleton map[string]string
var (
instance Singleton
once sync.Once
)
func New() Singleton{
once.Do(func() {
instance = make(Singleton)
})
return instance
}
使用示例如下:
package main
import (
"fmt"
"DesignPattern/Singleton"
)
func main() {
instance1 := Singleton.New()
instance1["name"] = "Jack Bauer"
instance2 := Singleton.New()
fmt.Println("My name is", instance2["name"])
}
// output:
// My name is Jack Bauer
5、sync.RWMutex
sync.RWMutex是針對讀寫操作的互斥鎖,讀寫鎖與互斥鎖最大的不同就是可以分別對讀、寫進行鎖定。一般用在大量讀操作、少量寫操作的情況。sync.RWMutex提供四種操作方法:
func (rw *RWMutex) Lock()
func (rw *RWMutex) Unlock()
func (rw *RWMutex) RLock()
func (rw *RWMutex) RUnlock()
RLock對讀操作進行鎖定,RUnlock對讀鎖定進行解鎖,Lock對寫操作進行鎖定,Unlock對寫鎖定進行解鎖。
sync.RWMutex鎖定規則如下:
(1)同時只能有一個goroutine能夠獲得寫鎖定。
(2)同時可以有任意多個gorouinte獲得讀鎖定。
(3)同時只能存在寫鎖定或讀鎖定(讀和寫互斥)。
(4)當有一個goroutine獲得寫鎖定,其它無論是讀鎖定還是寫鎖定都將阻塞直到寫解鎖;當有一個goroutine獲得讀鎖定,其它讀鎖定任然可以繼續;當有一個或任意多個讀鎖定,寫鎖定將等待所有讀鎖定解鎖後才能夠進行寫鎖定。
sync.RWMutex讀寫鎖使用註意:
(1)在首次使用之後,不要復制讀寫鎖。
(2)不要混用鎖定和解鎖,如:Lock和RUnlock、RLock和Unlock。對未讀鎖定的讀寫鎖進行讀解鎖或對未寫鎖定的讀寫鎖進行寫解鎖將會引起運行時錯誤。
使用示例代碼:
package main
import (
"fmt"
"sync"
"time"
)
var rw sync.RWMutex
func RWMutexDemo() {
var value int = 0
for i := 0; i < 10; i++ {
go func(i int) {
rw.Lock()
defer rw.Unlock()
fmt.Printf("Goroutine %d : Write value: %d\n", i, value)
value++
}(i)
go func(i int) {
rw.RLock()
defer rw.RUnlock()
fmt.Printf("Goroutine %d : Read value: %d\n", i, value)
}(i)
}
}
func main() {
RWMutexDemo()
time.Sleep(time.Minute)
}
6、sync.WaitGroup
sync.WaitGroup用於等待一組goroutine結束。
sync.WaitGroup操作方法如下:
func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg *WaitGroup) Wait()
Add用來添加goroutine的個數。Done執行一次數量減1。Wait用來等待結束。
sync.WaitGroup使用示例如下:
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func WaitGroupDemo() {
var value int = 0
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Printf("Goroutine %d : Write value: %d\n", i, value)
value++
}(i)
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Printf("Goroutine %d : Read value: %d\n", i, value)
}(i)
}
}
func main() {
WaitGroupDemo()
wg.Wait()
}
7、sync.Cond
sync.Cond實現一個條件等待變量,即等待或宣布事件發生的goroutine的會合點。
func NewCond(l Locker) *Cond
func (c *Cond) Broadcast()
func (c *Cond) Signal()
func (c *Cond) Wait()
NewCond用於根據Locker創建一個條件等待變量,Wait用於讓一個goroutine等待通知,Signal用於單次發送通知讓等待的goroutine繼續,Broadcast用於廣播通知讓所有等待的goroutine繼續。
sync.Cond條件等待變量實現生產者-消費者模式示例如下:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
var locker = new(sync.Mutex)
var cond = sync.NewCond(locker)
var capacity = 10
var consumerNum = 5
var producerNum = 2
func Produce(out chan<- int) {
for i := 0; i < producerNum; i++ {
go func(nu int) {
for {
cond.L.Lock()
for len(out) == capacity {
fmt.Println("Capacity Full, stop Produce")
cond.Wait()
}
num := rand.Intn(100)
out <- num
fmt.Printf("Producer %d produce: num %d\n", nu, num)
cond.L.Unlock()
cond.Signal()
time.Sleep(time.Microsecond * 500)
}
}(i)
}
}
func Consume(in <-chan int) {
for i := 0; i < consumerNum; i++ {
go func(nu int) {
for {
cond.L.Lock()
for len(in) == 0 {
fmt.Println("Capacity Empty, stop Consume")
cond.Wait()
}
num := <-in
fmt.Printf("Consumer %d: consume num %d\n", nu, num)
cond.L.Unlock()
time.Sleep(time.Second)
cond.Signal()
}
}(i)
}
}
func main() {
rand.Seed(time.Now().UnixNano())
quit := make(chan bool)
ProductPool := make(chan int, capacity)
Produce(ProductPool)
Consume(ProductPool)
<-quit
}
二、reflect
1、reflect簡介
在計算機科學領域,反射是指能夠自描述和自控制的應用。反射通過采用某種機制來實現對自己行為的描述(self-representation)和監測(examination),並能根據自身行為的狀態和結果,調整或修改應用所描述行為的狀態和相關的語義。
每種語言的反射模型都不同,並且某些語言不支持反射。Golang語言通過reflect包實現反射機制,在運行時動態的調用對象的方法和屬性。
Go語言中的變量包括(type, value)兩部分,type包括static type和concrete type。static typ是編碼時的類型(如int、string),concrete type是runtime的類型。
類型斷言能否成功,取決於變量的concrete type,而不是static type。因此,一個reader變量如果其concrete type實現了write方法,也可以被類型斷言為writer。
Golang指定類型變量的類型是靜態類型,在創建變量時類型就已經確定,因此,反射主要與Golang的interface類型相關(type是concrete type)。
在Golang的實現中,每個interface變量都有一個(value, type)對用於記錄變量的實際值和類型。value是變量的實際值,type是變量的實際類型。interface類型的變量包含2個指針,一個指針指向值的類型(concrete type),另一個指針指向實際的值(value)。
2、reflect接口
func TypeOf(i interface{}) Type
TypeOf用來動態獲取輸入參數接口中的值的類型,如果接口為空則返回nil。func ValueOf(i interface{}) Value
ValueOf用來獲取輸入參數接口中的數據的值,如果接口為空則返回0。
reflect.ValueOf(interface)得到一個relfect.Value變量,可以通過relfect.Value本身的Interface()方法獲得接口變量的真實內容,然後可以通過類型判斷進行轉換,轉換為原有真實類型。真實類型可能是已知原有類型,也可能是未知原有類型。
對於已知原有類型:
package main
import (
"fmt"
"reflect"
)
func main() {
var num float64 = 3.14
value := reflect.ValueOf(num)
pointer := reflect.ValueOf(&num)
fmt.Println("value: ", value.Interface().(float64))
fmt.Println("value: ", pointer.Interface().(*float64))
fmt.Println("type: ", reflect.TypeOf(num))
}
// output:
// value: 3.14
// value: 0xc42001e0e8
// type: float64
對於未知原有類型,需要進行遍歷探測其Filed:
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
ID int
Age int
}
func (student Student) Print() {
fmt.Printf("%s‘s ID is %d, %d years.", student.Name, student.ID, student.Age)
}
func handleFieldAndMethod(input interface{}) {
inputType := reflect.TypeOf(input)
fmt.Println("type: ", inputType.Name())
inputValue := reflect.ValueOf(input)
fmt.Println("value: ", inputValue)
// 獲取方法字段
// 1. 先獲取interface的reflect.Type,然後通過NumField進行遍歷
// 2. 再通過reflect.Type的Field獲取其Field
// 3. 最後通過Field的Interface()得到對應的value
for i := 0; i < inputType.NumField(); i++ {
field := inputType.Field(i)
value := inputValue.Field(i).Interface()
fmt.Printf("%s %v %v\n", field.Name, field.Type, value)
}
// 獲取方法
// 1. 先獲取interface的reflect.Type,然後通過.NumMethod進行遍歷
for i := 0; i < inputType.NumMethod(); i++ {
m := inputType.Method(i)
fmt.Printf("%s: %v\n", m.Name, m.Type)
}
}
func main() {
bauer := Student{"Bauer", 1, 130}
handleFieldAndMethod(bauer)
}
// output:
// type: Student
// value: {Bauer 1 130}
// Name string Bauer
// ID int 1
// Age int 130
// Print: func(main.Student)
func (v Value) MethodByName(name string) Value
MethodByName返回一個函數值對應的reflect.Value方法的名字。func (v Value) Call(in []Value) []Value
Call方法將最終調用真實的方法,參數必須一致。
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
ID int
Age int
}
func (student Student) Print() {
fmt.Printf("%s‘s ID is %d, %d years.", student.Name, student.ID, student.Age)
}
func CallMethod() {
student := Student{"Bauer", 1, 20}
value := reflect.ValueOf(student)
methodValue := value.MethodByName("Print")
// 對於無參函數
args := make([]reflect.Value, 0)
methodValue.Call(args)
}
func main() {
CallMethod()
}
// output:
// Print: func(main.Student)
三、runtime
1、runtime簡介
Go語言編譯器產生本地可執行代碼,可執行代碼仍舊運行在Go的 runtime中,runtime負責管理包括內存分配、垃圾回收、棧處理、goroutine、channel、切片(slice)、map 和反射(reflection)等。
runtime與C標準庫的作用一樣都是為了語言的跨平臺性,runtime可以運行在Windows和Unix平臺,可以運行在Intel或ARM處理器上。Go程序都附帶runtime,runtime負責與底層操作系統交互。
2、runtime常用方法
runtime.Gosched()
讓當前goroutine讓出?cpu?以讓其它goroutine運行,不會掛起當前線程,因此當前線程未來會繼續執行。func NumCPU() int
獲取當前系統的邏輯CPU?核數量runtime.GOMAXPROCS(i int)
設置最大的可同時使用的?CPU
通過runtime.GOMAXPROCS函數,應用程序何以在運行期間設置運行時系統中得邏輯處理器P最大數量。應在應用程序最早的調用。並且最好的設置P最大值的方法是在運行Go程序之前設置好操作程序的環境變量GOMAXPROCS,而不是在程序中調用runtime.GOMAXPROCS函數。
無論傳遞給函數的整數值是什麽值,運行時系統的P最大值總會在1~256之間。runtime.Goexit()
退出當前?goroutine(但defer語句會照常執行)
runtime.Goexit函數被調用後,會立即使調用他的Groution的運行被終止,但其他Goroutine並不會受到影響。runtime.Goexit函數在終止調用它的Goroutine的運行之前會先執行該Groution中還沒有執行的defer語句。runtime.NumGoroutine()
獲取正在執行和排隊的任務總數
runtime.NumGoroutine函數在被調用後,會返回系統中的處於特定狀態的Goroutine的數量。這裏的特指是指Grunnable\Gruning\Gsyscall\Gwaition。處於這些狀態的Groutine即被看做是活躍的或者說正在被調度。
註意:垃圾回收所在Groutine的狀態也處於這個範圍內的話,也會被納入該計數器。runtime.GOOS
獲取目標操作系統func GOROOT() string
獲取當前GOROOT
3、cgo
CGO是實現Go與C互操作的方式,包括Go調C和C調Go兩個過程。Go調用C需要在程序中引入的一個偽包,import “C”即為在Go中使用的偽包。C偽包會在編譯前被CGO工具捕捉到,並做一些代碼的改寫和樁文件的生成,不會被Go編譯器見到。
Go語言中調用C程序的方法如下:
//自定義函數調用
package main
/*
#include <stdio.h>
#include <stdlib.h>
void output(char *str) {
printf("%s\n", str);
}
*/
import "C"
import "unsafe"
func main() {
str := C.CString("hello cgo")
C.output(str)
C.free(unsafe.Pointer(str))
}
4、調度器
每一個Go程序都附帶一個runtime,runtime負責與底層操作系統交互,也都會有scheduler對goruntines進行調度。
四、plugin
1、plugin簡介
Golang是靜態編譯型語言,在編譯時就將所有引用的包全部加載打包到最終的可執行程序中,因此不能在運行時動態加載其它共享庫。Go 1.8開始,Go語言Linux和MacOS版本通過plugin包提供插件化加載共享庫機制,能夠在運行時動態加載外部功能。
2、plugin常用方法
type Plugin struct {
pluginpath string
err string // set if plugin failed to load
loaded chan struct{} // closed when loaded
syms map[string]interface{}
}
func Open(path string) (*Plugin, error)
func (p *Plugin) Lookup(symName string) (Symbol, error)
type Symbol interface{}
Open: 根據參數path提供的插件路徑加載插件,並返回插件結構的指針*Plugin
。
Lookup:?*Plugin
的惟一方法,通過名稱symName在插件中尋找對應的變量或方法,以Symbol的形式返回。從插件中找到的任何元素都是以Symbol形式(即interface{})返回,需要通過斷言的形式對結果進行判斷和轉換,得到需要的類型。
3、插件編譯方法
Go語言編譯器使用-buildmode=plugin標記編譯生成一個插件(共享對象庫文件)。Go包中導出的函數和變量被公開為ELF符號,可以使用plugin包在運行時查找並綁定ELF符號。go build -buildmode=plugin -o xxxplugin.so xxxplugin.go
如果要想更好的控制插件版本,實現熱更新插件,可以采用自動註冊插件方式。當新版本插件加載後,自動註冊插件版本號,插件平臺裏優先使用新版本插件的方法。
4、插件使用示例
插件編寫HelloPlugin.go:
package main
import (
"fmt"
)
func init() {
fmt.Println("Hello")
}
func Hello() {
fmt.Println("world")
}
插件編譯:go build -buildmode=plugin -o HelloPlugin.so HelloPlugin.go
插件調用main.go:
package main
import (
"fmt"
"plugin"
)
func main() {
open, err := plugin.Open("./HelloPlugin.so")
if err != nil {
fmt.Println(err.Error())
}
symbol, err := open.Lookup("Hello")
if err != nil {
fmt.Println(err)
}
symbol.(func())()
}
編譯運行:go run main.go
5、插件化編程
插件管理器實現:
package PluginManager
import "fmt"
// 插件容器
var Plugins map[string]Plugin
func init() {
Plugins = make(map[string]Plugin)
}
type Plugin interface {
Start()
}
// 啟動這個容器中所有的插件
func Start() {
for name, plugin := range Plugins {
go plugin.Start()
fmt.Printf("%s Plugin Start.\n", name)
}
}
// 插件做完之後必須得插入到容器中
func Register(name string, plugin Plugin) {
Plugins[name] = plugin
}
插件實現:
package HelloPlugin
import (
"GoExample/Plugin/PluginManager"
"fmt"
)
type HelloPlugin struct {
}
// 導入包時註冊插件
func init() {
plugin := HelloPlugin{}
PluginManager.Register("HelloPlugin", plugin)
}
func (this HelloPlugin) Start() {
fmt.Println("This is HelloPlugin.")
}
插件使用:
package main
import "GoExample/Plugin/PluginManager"
import _ "GoExample/Plugin/HelloPlugin"
func main() {
PluginManager.Start()
}
// output:
// HelloPlugin Plugin Start.
五、signal
1、signal簡介
os/signal包可以實現對信號的處理,Notify方法用來監聽收到的信號,Stop方法用來取消監聽。func Notify(c chan<- os.Signal, sig ...os.Signal)
Notify函數會將進程收到的系統Signal轉發給channel c,轉發哪些信號由可變參數決定,SIGKILL和SIGSTOP不能被攔截和處理。
參數c表示接收信號的channel,後續參數表示設置要監聽的信號,如果不設置表示監聽所有的信號。func Stop(c chan<- os.Signal)
Stop方法取消監聽通道上的所有信號。
2、signal示例
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
signalChannel := make(chan os.Signal, 1)
done := make(chan bool, 1)
// 監聽SIGINT、SIGTERM
signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM)
go func(ch chan os.Signal) {
// 接收信號
channel := <-ch
fmt.Printf("Received a signal: %s\n", channel)
done <- true
}(signalChannel)
// signal.Stop(signalChannel)
for {
fmt.Println("Waiting signal.")
<-done
fmt.Println("Exiting.")
}
}
Go語言開發(十三)、Go語言常用標準庫三