一些用法

struct

可以用struct定义一个固定结构的结构体

type Person struct {
  name string
  age int
}

type Singer struct {
  Person // 继承Person,直接(Singer).age就能访问到Person.age,也可以通过(Singer).Person.age来访问
  name string
  songs []string
}

func main() {
  p1 := Person{"Alice", 18}
  s1 := Singer{p1, "Ali", []string{"hip", "hop"}}
  fmt.Print(s1.age) // 18 (直接返回了p1的age)
  fmt.Print(s1.name) // Ali (s1有自己的name时覆盖p1的name,返回自身的name)
  fmt.Print(s1.Person.name) // Alice (可通过命名来访问p1的name)
}

可以为struct定义一些方法

type Person struct {
  name string
  age int
}

// 接收者为Person引用
func (p *Person) Grow(year int) int {
  p.age += year
  return p.age
}

// 接收者为Person副本
func (p Person) GrowCopy(year int) int {
  p.age += year
  return p.age
}

func main() {
  p1 := Person{"Alice", 18}
  fmt.Print(p1.GrowCopy(2)) // 20
  fmt.Print(p1) // {Alice 18}
  fmt.Print(p1.Grow(2)) // 20
  fmt.Print(p1) // {Alice 20}
}

struct比较

全部key可比才可比,有slice和map则不可比。(可比struct才能用于做map的key,slice和map可用reflect.DeepEqual比较)

内存对齐

struct的最终占用大小并非struct里的每个key大小之和,go编译器会按key的顺序和大小调整每个key的offset来对齐内存。

因为CPU读取数据是按段读取的,如果不对齐,可能读一个key会读两次。

interface

接口类型可以用来统一管理拥有某一组共同方法的struct

type Person struct {
  name string
  age int
}

func (p *Person) Grow(year int) int {
  p.age += year
  return p.age
}

type Human interface {
  Grow(year int) int
}

func main() {
  p1 := Person{"Bob", 20}
  var h Human // 定义Human接口类型的h,如果某类型实现了Human定义的所有接口,那么这个类型才可以赋值给h
  h = &p1 // 因为前面的Grow方法里定义的接收者为指针类型(*Person),这里使用&p1与之对应,如果用p1因为Grow方法匹配不上而赋值失败
  h.Grow(1) // 通过h来调用p1上的Grow
  fmt.Print(p1.age) // 21
}

chan

chan类型是一种生产者-消费者结构,多用于协程间的通信

一个基本用法

func chProducer(ch chan<- int) {
  for {
    time.Sleep(1 * time.Second)
    ch <- 1
  }
}

func chConsumer(ch <-chan int) {
  for {
    fmt.Println(<-ch)
  }
}

func main() {
  ch := make(chan int)
  go chProducer(ch)
  chConsumer(ch)
}

chan是阻塞模式

生产者发生阻塞

func main() {
  // ch缓冲区容量为3
  ch := make(chan int, 3)

  ch <- 1
  ch <- 2
  ch <- 3

  // 此时已经有3条未消费数据了,到这一步会卡住
  // main在等待其他协程消费ch数据,才会继续往下执行,但此时已经没有活跃协程了,产生deadlock
  ch <- 4

  // 走不到这一步
  fmt.Println(<-ch)
}

消费者发生阻塞

func main() {
  ch := make(chan int, 3)

  ch <- 1

  fmt.Println(<-ch) // 1

  // 到这一步会卡住
  // main在等待其他协程生产ch数据,才会继续往下执行,但此时已经没有活跃协程了,产生deadlock
  fmt.Println(<-ch)

  // 执行不到这里
  fmt.Println(<-ch)
}

类型断言

interface类型可以使用断言语法转化为指定类型

type Person struct {
  Name string
  Age int
}

type Person2 struct {
  Name string
  Age int
}

func main() {
  p := Person{"A", 20}

  var i interface{}

  i = p

  p1, _ := i.(Person)
  // 断言使用该interface对应的类型,转化成功
  fmt.Println(p1) // {A, 20}

  p2, _ := i.(Person2)
  // 断言使用非对应的类型,转化失败
  fmt.Println(p2) // { }
}

类型强制转换

可以通过改写读取指针来强行改变某个变量的类型,需要unsafe包

import "unsafe"

type Person struct {
  Name string
  Age int
}

type Person2 struct {
  Name string
  Age int
}

func main() {
  var i float64

  i = 1

  // 强制以int的结构读取float64类型的数据
  i2 := *(*int)(unsafe.Pointer(&i))

  fmt.Println(i2) // 4607182418800017408

  p := Person{"A", 20}

  // 也可以用于struct,强制以Person2的结构来读取Person类型的数据
  // 使用强制转化时,两者结构完全对应的时候可以转化成功,否则可能读出奇怪的数据甚至panic
  p2 := *(*Person2)(unsafe.Pointer(&p))

  fmt.Println(p2) // {A, 20}
}

interface类型强制转换

某些场景下,可能你知道某个interface对应的原始类型,但无法引入这个原始类型,此时无法用正常的断言来转换类型。

可以自己构造一个对应类型,然后通过eface强制转换。

type Person struct {
  Name string
  Age  int
}

func main() {
  p := Person{"A", 20}

  var i interface{}

  i = p

  type PersonFake struct {
    Name string
    Age  int
  }

  // 这里如果直接使用类型强制转换会失败,&i指向的并不是那个原始Person结构体,因为interface把这个结构包了一层,&i指向的只是这个interface外壳
  // p2 := *(*PersonFake)(unsafe.Pointer(&i))

  // 需要借助eface结构体,做两次类型强制转换
  // eface即为interface类型的真实结构,定义于go/src/runtime/runtime2.go
  type eface struct {
    _type unsafe.Pointer
    data  unsafe.Pointer
  }
  // (*eface)(unsafe.Pointer(&i)) 这一步把interface类型的i转换为eface结构体,而(eface).data存储的就是指向i对应的那个原始结构的指针
  // *(*PersonFake)(原始结构体指针) 这一步就是正常的类型强制转换,使用PersonFake来强制读取Person类型的数据
  p2 := *(*PersonFake)((*eface)(unsafe.Pointer(&i)).data)

  fmt.Println(p2) // {A, 20}
}

debounce

package main

import (
	"context"
	"fmt"
	"reflect"
	"sync"
	"time"
)

func Debounce(fn interface{}, wait int) func(...interface{}) {
	var cf context.CancelFunc
	var mu sync.Mutex
	return func(args ...interface{}) {
		// 需注意并发安全,因为 cf 的判断 & 赋值不是原子操作,多协程同时访问可能有问题,比如协程 1 可能已经设置了新 cf,而协程 2 取消的是旧的 cf
		// 需要加锁以保证同时只有一个协程能对 cf 操作
		mu.Lock()
		if cf != nil {
			cf()
		}
		cf = setTimeout(fn, wait, args...)
		mu.Unlock()
	}
}

func setTimeout(fn interface{}, timeout int, args ...interface{}) context.CancelFunc {
	ctx, cancelFunc := context.WithCancel(context.Background())

	// 新起协程执行任务
	go func() {
		// 轮询以下情况
		select {
		// ctx 结束,直接退出
		case <-ctx.Done():
		// time 达到要求,调用 fn
		case <-time.After(time.Duration(timeout) * time.Second):
			callFunc(fn, args...)
		}
	}()

	return cancelFunc
}

// fn 的函数类型和参数不确定,需通过反射调用函数
func callFunc(fn interface{}, args ...interface{}) {
	f := reflect.ValueOf(fn)
	if len(args) != f.Type().NumIn() {
		panic("The number of arguments does not match the function")
	}

	inputs := make([]reflect.Value, len(args))
	for i := range args {
		inputs[i] = reflect.ValueOf(args[i])
	}

	f.Call(inputs)
}

func testDebounce() {
	foo := Debounce(func(id int) {
		fmt.Println("foo", id)
	}, 1)

	for i := range 100 {
		go foo(i)
	}

	time.Sleep(100 * time.Second)
}

func main() {
	testDebounce()
}