Day2-nil、并发编程 、Sync | 青训营笔记

106 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天

3e5ea3e3fb624e129e4a437dc5a520f1.jpg

nil

nil,在Go中较为常见的一个标识符(注意不是关键字,可用定义未nil的变量),常用来判断是否出现错误

在Go中,通常的错误处理如下:

ret, err := CustomFunc()  
if err != nil {
  panic(err)
}

那么为什么是err ≠ nil时表示未出现错误(最开始我还以为nil表示null,是出现错误呢....)

在Go中,如果一个指针未赋值(unset),那么初始值是nil

func main() {
  var p *int
  fmt.Println(p)
}
输出:<nil>

当没有出现错误时,不向返回的err赋值,或者说直接赋值一个nil返回来表示未出现错误

同时,nil并不能直接进行比较

fmt.Println(nil == nil)
//output: invalid operation: nil == nil (operator == not defined on untyped nil)

更加详细的可用参考下面两个博客:

Go语言nil:空值/零值 (biancheng.net)

Golang中的nil,没有人比我更懂nil! - 知乎 (zhihu.com)

并发编程

在Go语言中,每一个并发的执行单元叫作一个goroutine。

通常的使用:使用go关键字,然后后面跟需要执行的函数体即可如下:


func main() {
  var wait sync.WaitGroup
  wait.Add(1)
  go loopPrint(10, &wait)
  fmt.Println("i am main")
  wait.Wait()
}

func loopPrint(n int, wait *sync.WaitGroup) {
  for i := 0; i < n; i++ {
    fmt.Println(i)
  }
  fmt.Println("over")
  wait.Done()
}
/*输出
i am main
0
1
2
3
4
5
6
7
8
9
over
*/

这里面多了一点东西sync.WaitGroup,这个主要是为了让主线程等待goroutine结束,不然如果主线程提前结束了,而goroutine还没结束的话,goroutine将提前结束(主线程没了,goroutine也就没了)

如何获取goroutine执行后的返回值或者说执行后的结果呢

使用Channel可用与goroutine之间的通信,通过make创建存放指定数据的channel

channel = make(chan string, 2) => 创建缓存为2的channel

channel使用:

  • 将值存入channel: channel <- "ok"
  • 取出一个值赋给指定变量: a := <-channel
  • 取出值但不使用: <-channel

使用channel接收goroutine的处理后的结果:


var syncChan = make(chan string)

func main() {
  var wait sync.WaitGroup
  wait.Add(1)
  go loopPrint(10, &wait)
  fmt.Println("i am main")
  fmt.Println(<-syncChan)
  fmt.Println("get the channel value")
  close(syncChan) // close channel
  wait.Wait()
}

func loopPrint(n int, wait *sync.WaitGroup) {

  for i := 0; i < n; i++ {
    fmt.Println(i)
  }
  fmt.Println("over")
  syncChan <- "ok"
  wait.Done()
}

同时从channel中取值似乎也是阻塞的,参考如下代码,在goroutine中使用time.Sleep使得goroutine睡眠一定时间,同时主线程的取值也在等待


var syncChan = make(chan string)

func main() {
  var wait sync.WaitGroup
  wait.Add(1)
  go loopPrint(10, &wait)
  fmt.Println("i am main")
  fmt.Println(<-syncChan)
  fmt.Println("get the channel value", time.Now())
  close(syncChan)
  wait.Wait()
}

func loopPrint(n int, wait *sync.WaitGroup) {

  for i := 0; i < n; i++ {
    fmt.Println(i)
  }
  fmt.Println("over", time.Now())
  time.Sleep(time.Second * 2)
  syncChan <- "ok"
  wait.Done()
}

/*output
i am main
0
1
2
3
4
5
6
7
8
9
over 2023-01-16 21:13:44.2619655 +0800 CST m=+0.003986801
ok
get the channel value 2023-01-16 21:13:46.2810119 +0800 CST m=+2.023033201
*/

Sync

互斥锁-sync.Mutex

使用互斥锁实现多线程或者多协程下同一资源的互斥访问。当一个共享代码块被加上互斥锁,只有一个goroutine可以访问,其他goroutine只有等使用者释放锁后才可以抢夺这个锁并去访问这个共享代码块,防止并发下产生的一些问题,参考如下加了互斥锁与未加互斥锁后结果


var LOCK sync.Mutex
var count = 0
var WAIT sync.WaitGroup

func main() {
  WAIT.Add(4)
  go countNum()
  go countNum()
  go countNum()
  go countNum()
  WAIT.Wait()
  fmt.Println(count)
}

func countNum() {
  LOCK.Lock()
  for i := 0; i < 10000; i++ {
    count++
  }
  LOCK.Unlock()
  WAIT.Done()
}
/*output
加锁结果:40000
未加锁结果(将LOCK.Lock和LOCK.Unlock注释掉):30189
*/

单例模式/懒加载-sync.Once

通常情况下实现懒加载如下


type Instance struct {
  data int
  desc string
}

var INSTANCE *Instance

func getInstance() Instance {
  if INSTANCE == nil {
    INSTANCE = &Instance{0, "0"}
  }
  return *INSTANCE
}

但是这种在并发下会出现一些问题,比如两个线程获取到不同的实例,解决这个就需要进行加锁如下

func getInstance() Instance {
  LOCK.Lock()
  if INSTANCE == nil {
    INSTANCE = &Instance{0, "0"}
  }
  LOCK.Unlock()
  return *INSTANCE
}

但是这样直接加锁将对效率有着较大的影响,可以考虑使用双检测锁提高效率


func getInstance() Instance {
  if INSTANCE == nil {
    LOCK.Lock()
    if (INSTANCE == nil) {
      INSTANCE = &Instance{0, "0"}
    }
    LOCK.Unlock()
  }
  return *INSTANCE
}

但是容易写错(😅),所以,Go帮你做了个更好的,而且超简单

func getInstance() Instance {
  LOAD_ONCE.Do(func() {
    INSTANCE = &Instance{0, "0"}
  })
  return *INSTANCE
}

sync.Onc 是在代码运行中需要的时候执行,且只执行一次


func main() {
  for i := 0; i < 100; i++ {
    LOAD_ONCE.Do(func() {
      fmt.Println("hello")
    })
  }
}
/*output
hello
*/

信号量-WaitGroup

在上面已经多次用到了,其实也非常简单,主要是三个方法

  • Add(int) 添加指定值信号量
  • Done() 将信号量减一
  • Wait()阻塞直到信号量为0

参考:

Go语言nil:空值/零值 (biancheng.net)

Golang中的nil,没有人比我更懂nil! - 知乎 (zhihu.com)

Goroutines · Go语言圣经 (studygolang.com)