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 
#include 
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.")
   }
}