Golang协程并发控制的几种方式

2019/12/25 golang

Golang 协程并发控制的几种方式

goroutine 协成的并发能提高程序的运行效率,在 IO 密集型的操作中,能大幅提高程序的运行效率

goroutine 的并发控制,主要可以通过以下几种方式

全局变量

这是并发控制最简单的实现方式,优点是实现比较简单,缺点是适用一些逻辑简单的场景,全局变量的信息较少,为了防止不同 goroutine 同时修改全局变量,需要加锁来解决

1、声明一个全局变量。

2、所有子 goroutine 共享这个变量,并不断轮询这个变量检查是否有更新;

3、在 main 协程中变更该全局变量;

4、子 goroutine 检测到全局变量更新,执行相应的逻辑。

package main

import (
   "fmt"
   "time"
)

func main() {
   open := true
   go func() {
      for open {
         println("goroutineA running")
         time.Sleep(1 * time.Second)
      }
      println("goroutineA exit")
   }()
   go func() {
      for open {
         println("goroutineB running")
         time.Sleep(1 * time.Second)
      }
      println("goroutineB exit")
   }()
   time.Sleep(3 * time.Second)
   open = false
   //等待子协程完成
   time.Sleep(1 * time.Second)
   fmt.Println("main fun exit")
}

运行结果

goroutineA running
goroutineB running
goroutineA running
goroutineB running
goroutineA running
goroutineB running
goroutineB exit
goroutineA exit
main fun exit

channel 通道

channel 是 goroutine 之间常见的通信方式,通过它可以在 goroutine 之间发送和接收消息。它是 Golang 在语言层面提供的 goroutine 间的通信方式。一般会和 select 搭配使用。

1、声明一个 stop 的 chan。

2、在 goroutine 中,使用 select 判断 stop 是否可以接收到值,如果可以接收到,就表示可以退出停止了;如果没有接收到,就会执行 default 里逻辑。直到收到 stop 的通知。

3、主程序发送了 stop<- true 结束的指令后。

4、子 goroutine 接到结束指令 case <-stop 退出 return。

  package main

   import (
       "fmt"
       "time"
   )

   func main() {
       stop := make(chan bool)
       go func() {
           for {
               select {
               case <-stop:
                   fmt.Println("goroutine exit")
                   return
               default:
                   fmt.Println("goroutine running")
                   time.Sleep(1 * time.Second)
               }
           }
       }()
       time.Sleep(3 * time.Second)
       stop <- true
       time.Sleep(1 * time.Second)
       fmt.Println("main fun exit")
   }

运行结果

goroutine running
goroutine running
goroutine running
goroutine exit
main fun exit

WaitGroup

waitgroup 由 sync 包提供,Sync.WaitGroup 是一种实现并发控制方式,WaitGroup 对象内部有一个计数器,最初从 0 开始,它有三个方法:Add(), Done(), Wait() 用来控制计数器的数量。

Add(n) 把计数器设置为 n 。 Done() 每次把计数器-1 。 wait() 会阻塞代码的运行,直到计数器地值减为 0。

这种控制并发的方式适用于,好多个 goroutine 协同做一件事情的时候,因为每个 goroutine 做的都是这件事情的一部分,只有全部的 goroutine 都完成,这件事情才算是完成,这是等待的方式。WaitGroup 相对于 channel 并发控制方式比较轻巧。

  package main

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

   func main() {
       //定义一个WaitGroup
       var wg sync.WaitGroup
       //计数器设置为2
       wg.Add(2)
       go func() {
           //计数器减1
           defer wg.Done()
           time.Sleep(2 * time.Second)
           fmt.Println("redis task finish")
       }()
       go func() {
           //计数器减1
           defer wg.Done()
           time.Sleep(1 * time.Second)
           fmt.Println("db task finish")
       }()
       //会阻塞代码的运行,直到计数器地值减为0。
       wg.Wait()
       fmt.Println("main fun exit")
   }

运行结果

db task finish
redis task finish
main fun exit

context

应用场景:在 Go http 包的 Server 中,每个 Request 都需要开启一个 goroutine 做一些事情,这些 goroutine 又可能会开启其他的 goroutine。所以我们需要一种可以跟踪 goroutine 的方案,才可以达到控制他们的目的,这就是 Go 语言为我们提供的 Context,称之为上下文。

控制并发的实现方式:

1、 context.Background():返回一个空的 Context,这个空的 Context 一般用于整个 Context 树的根节点。

2、context.WithCancel(context.Background()),创建一个可取消的子 Context,然后当作参数传给 goroutine 使用,这样就可以使用这个子 Context 跟踪这个 goroutine。

3、在 goroutine 中,使用 select 调用 <-ctx.Done() 判断是否要结束,如果接收到值的话,就可以返回结束 goroutine 了;如果接收不到,就会继续进行监控。

4、cancel(),取消函数(context.WithCancel()返回的第二个参数,名字和声明的名字一致)。作用是给 goroutine 发送结束指令。

  package main

   import (
       "fmt"
       "time"
       "context"
   )

   func main() {
       //创建一个可取消子context,context.Background(): 返回一个空的Context,这个空的Context一般用于整个Context树的根节点。
       ctx, cancel := context.WithCancel(context.Background())
       go func(ctx context.Context) {
           for {
               select {
               //使用select调用<-ctx.Done()判断是否要结束
               case <-ctx.Done():
                   fmt.Println("goroutine exit")
                   return
               default:
                   fmt.Println("goroutine running.")
                   time.Sleep(1 * time.Second)
               }
           }
       }(ctx)

       time.Sleep(3 * time.Second)
       //取消context
       cancel()
       time.Sleep(1 * time.Second)
       fmt.Println("main fun exit")

   }

运行结果

goroutine running.
goroutine running.
goroutine running.
goroutine exit
main fun exit

Search

    Table of Contents