1. 程式人生 > >Go語言開發(十三)、Go語言常用標準庫三

Go語言開發(十三)、Go語言常用標準庫三

quit method 類型判斷 unix mgo lookup broadcast 可執行 適用於

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的一種實現。

一個互斥鎖只能同時被一個goroutine鎖定,其它goroutine將阻塞直到互斥鎖被解鎖(重新爭搶對互斥鎖的鎖定)。
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可以安全的在多個goroutine中並行使用,但並不適用於所有空閑對象,應該用來管理並發的例程共享的臨時對象,而不應該管理短壽命對象中的臨時對象。
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&lt;- os.Signal, sig ...os.Signal)
Notify函數會將進程收到的系統Signal轉發給channel c,轉發哪些信號由可變參數決定,SIGKILL和SIGSTOP不能被攔截和處理。
參數c表示接收信號的channel,後續參數表示設置要監聽的信號,如果不設置表示監聽所有的信號。
func Stop(c chan&lt;- 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語言常用標準庫三