Go语言 并发-select 与 锁

1,148 阅读2分钟

select 基本使用

func generator() chan int {
   out := make(chan int)
   go func() {
      i := 0
      for {
         time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
         out <- i
         i++
      }

   }()
   return out
}

func main() {
   var c1, c2 = generator(), generator()
   for {
      select {
      case n := <-c1:
         fmt.Println("received from c1 ", n)
      case n := <-c2:
         fmt.Println("received from c2 ", n)
         //default:
         // fmt.Println("No value received")
      }
   }

}

image.png

看下执行结果,实际上 这个select 关键字的作用就是 先从哪个chan 拿到值 就走哪个case 仅此而已。

n个chan 里面 选择一个 最先到达的。

atomic

大部分的语言 都有原子操作和锁的概念。go也不例外

image.png

go也一样 提供了原生的 原子操作的 函数。这里就不演示了,和java其实差不多

我们具体看下go语言中的锁 怎么使用

我们来模仿一下这个atomic的实验

package main

import (
   "fmt"
   "time"
)

type atomicInt int

func (a *atomicInt) increase() {
   *a++
}

func (a *atomicInt) get() int{
  return int(*a)
}

func main() {
   var a atomicInt
   a.increase()
   go func() {
      a.increase()
   }()
   time.Sleep(time.Millisecond)
   fmt.Println(a.get())
}

显然这段程序 看起来是有问题的 对吧。 因为你的increase 和 get函数 这2个函数 没有加锁,看上去是可以并发执行的。 这当然会在高并发的时候 导致最终的结果不正确

image.png

但实际上我们跑起来看这段程序的时候 发现结果是2 是符合我们预期的,这是为啥? 这是因为 这个场景 在单个goroutine的情况下 比较难以模仿出 冲突的情况,但是通过一些命令是可以找到证据的。

image.png

我们使用go run -race 是可以查一下 程序的运行过程的

可以看出来 已经给出了警告提示 这里是有data race的。

并且告诉你了 15行和25行 一起读了 一起读问题不大 但是下面是一起写 那就有问题了

下面就利用mutex 来加锁

package main

import (
   "fmt"
   "sync"
   "time"
)

type atomicInt struct {
   value int
   lock  sync.Mutex
}

func (a *atomicInt) increase() {
   a.lock.Lock()
   defer a.lock.Unlock()
   a.value++

}

func (a *atomicInt) get() int {
   a.lock.Lock()
   defer a.lock.Unlock()
   return a.value
}

func main() {
   var a atomicInt
   a.increase()
   go func() {
      a.increase()
   }()
   time.Sleep(time.Millisecond)
   fmt.Println(a.get())
}

image.png

再看一下 就ok了