go并发之路(一)——goroutine

663 阅读3分钟

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

通过通信来共享内存,而不是通过共享内存来通信

这句话听起来有些绕口,实际上,由于在各种复杂环境时实现对共享变量的正确访问容易出错,因此go语言鼓励我们用通信来代替共享内存。go语言并发同步模型来自于通信顺序进程CSP(Communicating Sequential Processes)的范型,但在其外在表现上,更加接近于Unix管道的一种泛化。

当然。如果只是对一个整数型变量的引用计数,自然也没必要特意去浪费资源建立通道,只需要对该变量加个互斥的锁即可。

goroutine

Go语言中的goroutine就是这样一种机制,goroutine的概念类似于线程,但 goroutine是由Go的runtime调度和管理的。Go程序会智能地将 goroutine 中的任务合理地分配给每个CPU。Go语言之所以被称为现代化的编程语言,就是因为它在语言层面已经内置了调度和上下文切换的机制。

当你需要让某个任务并发执行的时候,你只需要把这个任务包装成一个函数,开启一个goroutine去执行这个函数就可以了,就是这么简单粗暴。

举个例子

func main(){
   runtime.GOMAXPROCS(1)
   go func() {
      for count := 0; count < 26; count++ {
         fmt.Printf("%c ",'a' + count)
      }
   }()
   fmt.Println("main done")
}

输出是:

main done

Process finished with the exit code 0

我们发现,匿名函数里的代码并没有被运行,或者说并没有看到有字母输出,这是因为main函数过早的结束,导致主线程结束运行,因此我们将代码稍稍修改下,让程序多执行一秒钟:

func main(){
   runtime.GOMAXPROCS(1)
   go func() {
      for count := 0; count < 26; count++ {
         fmt.Printf("%c ",'a' + count)
      }
   }()
   fmt.Println("main done")
   time.Sleep(time.Second)
}

当然了,在实际应用中我们不可能用这种方式来保证一个协程被执行完成。因此,无论是类似的场景还是更实际的多个协程共同协作的场景(例如,多个子task并发完成一部分任务,主task等待他们最后结束。),我们都需要一种方法来保证协程在运行的时候被正确执行。

通过WaitGroup保证同步

先上代码:

func main(){
   runtime.GOMAXPROCS(1)
   var wg sync.WaitGroup
   // 
   wg.Add(2)
   go func() {
   // 在函数退出时调用Done来通知main函数工作已经完成。
      defer wg.Done()
      for count := 0; count < 26; count++ {
         time.Sleep(time.Millisecond)
         fmt.Printf("%c ",'a' + count)
      }
   }()
   go func() {
      defer wg.Done()
      for count := 0; count < 26; count++ {
         time.Sleep(time.Millisecond)
         fmt.Printf("%c ",'A' + count)
      }
   }()
   fmt.Println("main done")
   wg.Wait()
}

WaitGroup 对象内部有一个计数器,最初从0开始,它有三个方法:Add(), Done(), Wait() 用来控制计数器的数量。Add(n) 把计数器设置为n ,Done() 每次把计数器-1 ,wait() 会阻塞代码的运行,直到计数器地值减为0

输出为:

main done
a A B b c C D d e E F f g G H h i I J j k K L l m M N n o O P p q Q R r s S T t u U V v w W X x y Y Z z 
Process finished with the exit code 0