46

关于go并发编程的总结

 4 years ago
source link: https://www.tuicool.com/articles/6vyiArr
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

关于go并发编程的总结

1.使用sync进行并发:

package main
  
  import (
      "fmt"
      "sync"
  )
  
  func main() {
  
      testSlice := []string{"test1", "test2", "test3"}
      wg := sync.WaitGroup{}
      for _, t := range testSlice {
          wg.Add(1)
          go printSlice(t, &wg)
      }   
      wg.Wait()
  }
  
  func printSlice(s string, wg *sync.WaitGroup) {
      defer wg.Done()
      fmt.Printf("this is %+v\n", s)
  }

sync包实现并发的核心就是waitgroup,初始化一个WaitGroup类型的指针,需要并发时,则使用Add方法,添加计数,并在需要进行并发的函数中将其作为参数传入,当这个函数操作完成后,调用Done方法减掉计数;在主协程中,Wait方法会一直监听计数器的数量,当计数器为0时,说明所有的并发函数都完成了,这时主协程就可以退出了;个人感觉这是最简单的实现并发的方式,在需要并发处理一个集合内的所有数据时尤其好用;

2.使用管道进行并发

管道是go原生支持的数据类型,使用它也能达到并发的效果

通常的思路是,在主协程取管道中的数据,这时管道会阻塞,在发送协程中向管道里塞数据,塞完数据后关闭掉管道;当主协程取不到数据,管道也关闭后,任务就完成了

package main

  import "fmt"

  var channel = make(chan int, 10)

  func main() {
      go func() {
          for i := 0; i < 10; i++ {
              channel <- i
          }
          close(channel) //放完数据后一定要关闭chan,否则会死锁;
      }()

      for v := range channel {
          fmt.Println(v)
      } //在主协程从chan里取数据,因为取不到会一直阻塞,这样main routine就不会退出;
      //如果另起一个协程取数据,在另一个协程里阻塞,但主协程并未阻塞,取数据协程还没取到,主协程就退出了;
  }

上面的例子其实并没有体现出并发执行,因为十个数按次序塞进管道中,主协程按次序从管道里取出了数据,还是一个串行的过程

进阶版

package main

  import (
      "fmt"
  )

  var channel = make(chan int)
  var result = make(chan int)

  func main() {
      go func() {
          for i := 0; i < 100; i++ {
              channel <- i
          }
          close(channel)
      }()

      ct := 0
      for c := range channel {
          go func(i int) {
              result <- i + i
          }(c)

      }

      for v := range result {
          ct++
          fmt.Println(v)
          if ct == 100 {
              break
          }
      }
  }

注意,关闭管道与监听管道取值需要是两个不同的协程,若两个操作都在一个协程,要么监听了一个已经关闭的协程,要么监听了一个没有被关闭的协程,都会产生异常

在这里,管道的容量是0,即每次往管道塞数据需要等里面的数据被消费后才能继续塞;有容量的协程则是可以塞进数量为容量数的数据,之后的数据需要阻塞,直到有空余;

3. select关键字

先看代码

start := time.Now()
    c := make(chan interface{})
    ch1 := make(chan int)
        ch2 := make(chan int)

    go func() {

        time.Sleep(4*time.Second)
        close(c)
    }()

    go func() {

        time.Sleep(3*time.Second)
        ch1 <- 3
    }()

      go func() {

        time.Sleep(3*time.Second)
        ch2 <- 5
    }()

    fmt.Println("Blocking on read...")
    select {
    case <- c:
        fmt.Printf("Unblocked %v later.\n", time.Since(start))
    case <- ch1:
        fmt.Printf("ch1 case...")
      case <- ch2:
        fmt.Printf("ch2 case...")
    default:
        fmt.Printf("default go...")
    }

运行上述代码,由于当前时间还未到3s。所以,目前程序会走default。

  • 如果有一个或多个关于管道操作可以完成,则Go运行时系统会随机的选择一个执行,否则的话,如果有default分支,则执行default分支语句,如果连default都没有,则select语句会一直阻塞,直到至少有一个管道操作可以进行.
  • 需要有真实的goroutine存在,若所有对管道操作的的子goroutine都已退出,select{}会报panic

部分参考: https://www.jianshu.com/p/2a1...


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK