GO语言-10了解Go并发的使用(上)

初心是记录和总结,自己学习Go语言的历程。如果能帮助到你,这是我的荣幸。

并发是属于比较难的内容,我还不足以把握到其精髓,只能分享一些基本使用和注意事项。

如何开启并发

很简单,只要在方法前使用go关键字,看示例代码。这里使用了匿名函数,匿名函数可以在方法中直接调用执行,通过{}后加上(),也可以传入参数,只需要func(参数)和最后的括号里加入传入(传入参数)对应即可。

package main

import "fmt"

func main() {
   go func() {
      fmt.Println("并发程序")
   }()
   fmt.Println("主程序")
}

程序运行后,会输出主程序,发现并没有打印并发程序。这是因为在Go中,主程序也算是一个并发的主协程,而主协程执行完毕后,会将其他的子协程(通过go关键字开启的)强制结束。这里出现了协程两个字,这是在go中特有的描述并发的,协程就是通过go关键字开启的,英文名叫:goroutine

主协程和子协程如何协调

这个协调是指:主协程能不能等我自己开启的协程结束后才结束。并发本身就是脱离了程序定义的顺序,它并不按照程序语句的顺序执行,而是通过计算机资源调度,和CPU有关系。那么我们如何进行控制呢?

睡眠

睡眠的方式非常简单粗暴,强制该程序‘睡眠’,暂时停止执行。这样CPU就会执行其他协程了。
使用方法:

time.Sleep(d Duration)

Duration是指时间间隔,常用的有

  • time.Hour 一小时
  • time.Minute 一分
  • time.Second 一秒

使用WaitGroup

WaitGroup等待goroutine的集合完成。主程序调用Add来设置等待的gor例行程序的数量。然后每个goroutine运行并在完成时调用Done。同时,Wait可用于阻塞直到所有goroutine完成。

通过这段话我们可以捕获三个点

  • 在主程序中调用WaitGroup的Add方法添加字子协程的数量
  • 每个子协程运行完成时调用Done方法
  • 主程序通过使用Wait方法等待子协程运行结束
// 定义WaitGroup对象
var wg sync.WaitGroup

func main() {
   wg.Add(1) // 添加一个子协程
   go func() {
      fmt.Println("并发程序")
      wg.Done() // 子协程结束后调用Done
   }()
   fmt.Println("主程序")
   wg.Wait() // 等待子协程运行结束,主协程才结束
}

数据传递 - 通道

并发执行时第一个重要的点:数据传递

模拟A协程和B协程,双方进行数据传输,在go语言中,协程数据的传输推荐的是使用通道,这里出现了一个新的数据类型:chan,使用它只需要关心的是:传输什么类型的数据,所以它的定义方法是:
var chan1 int = make(chan int)或者是简易法:chan1 := make(chan int)

通道的传递和接收语法

// 定义了一个传输数值型的通道
chan1 := make(chan int)

// 从通道中接收内容
x := <-chan1

// 往通道中传入一个值:1
chan1 <- 1

阻塞式通道

chan1 := make(chan int)

这种没有指定缓冲区的通道,默认是阻塞式通道。

理解阻塞:通道的使用有发送方和接收方两个角色。

  • 发送方发送数据时,当接收方没有准备好接收数据时,通道对于发送方是阻塞的。
  • 接收方,当通道中数据为空的时候,通道对于接收方是阻塞的。

看见一个非常形象的例子:京东快递送货上门,当没有暂存站点时,它会打电话给你,如果你方便接收,那它会给你送过来,如果你不能接收,则不会给你送过来。

常见错误,阻塞式通道兼任两个角色:

阻塞式非常注重发送方和接收方这两个角色,同个协程不允许兼任两个身份

func main() {
   chan1 := make(chan int)
   // 接收
   x := <-chan1
   //发送
   chan1 <- 1
   fmt.Println(x)
}

改正,接收方改为新的协程;Ps:这里涉及到资源抢夺的问题,因为并没有使用WaitGroup

func main() {
   chan1 := make(chan int)
   go func() {
      // 接收
      x := <-chan1
      fmt.Println(x)
   }()
   //发送
   chan1 <- 1
}
常见错误,接收未准备好就发送数据:

先有接收状态才能往通道发送内容!这是阻塞式通道的原则。

func main() {
   chan1 := make(chan int)

   //发送
   chan1 <- 1

   go func() {
      // 接收
      x := <-chan1
      fmt.Println(x)
   }()
}

改正:把go协程放在发送注释上方即可。

常见错误,往里存了2个数
// 接收一个

chan1 := make(chan int)

chan1 <- 1
chan1 <- 2

新的输入无法在通道非空的情况下传入,即传送数据时,通道必须是空的。

非阻塞式通道

chan1 := make(chan int,1)

相比阻塞式通道,这里就多指明了一个参数,这个参数是一个数值型,表示的是缓存区,数据在通道中传输时可以保存1个,拿上面常见错误的最后一个例子来说:

// 接收一个

chan1 := make(chan int,1)

chan1 <- 1
chan1 <- 2

这里我们缓冲了一个,被接收了一个,所以再放值的时候不会报错。

非阻塞通道还有这些特点:

  • 只要缓存区运行,没有接收方也不会报错。
  • 因为缓存区的存在,打破了接收方和发送方的限制,在一个协程中发送和接收是被允许的,甚至不讲顺序。

这两点可以自己去尝试一下。

遍历通道的内容

方法一

使用无穷循环,配上判断;无穷循环不断的去执行获取chan中的内容,它会返回两个值,第一个值是实际值,第二个值是通道是否关闭,若通道没关闭则进行相应的判断

for {
   v, ok := <-chan1
   fmt.Println(v)
   fmt.Println(ok)
}

方法二

不用关心通道是否关闭,通道关闭时自动退出循环。

for v := range chan1 { // 优雅的从通道循环取值
   fmt.Println(v)
}

【重点】 方法一和方法二的取舍:

  • 方法一 不需要告知通道关闭,因为通道在垃圾回收的机制内。
  • 方法二 需要告诉通道关闭来控制退出循环

综合例子一:传输数字,直到…

往通道里传输数字,直到传到10位置

package main

import (
   "fmt"
   "sync"
)

var wg sync.WaitGroup

func main() {
   var chan1 chan int = make(chan int, 10)
   i := 0

   wg.Add(1)
   go func() {
      for v := range chan1 { //如果通道关闭会自动退出循环,如果没有告知通道关闭,会报错
         fmt.Println(v)
      }
      defer wg.Done()
   }()

   for {
      if i == 10 {
         // 告知通道关闭了
         close(chan1)
         break
      } else {
         chan1 <- i
         i++
      }
   }

   wg.Wait()
}

综合例子二:筛选偶数

package main

import (
   "fmt"
   "sync"
)

var wg sync.WaitGroup

func main() {
   var in chan []int = make(chan []int, 10)
   var out chan []int = make(chan []int, 10)
   x := make([]int, 0)
   x = append(x, 1, 2, 3, 4, 5, 6, 7)
   out <- x
   close(out) // 关闭通道

   wg.Add(1)
   go odd(in, out)

   for v := range in { //同样需要告诉通道关闭了
      fmt.Println(v)
   }
   //time.Sleep(time.Second * 2000)
   wg.Wait()
}

func odd(in chan<- []int, out <-chan []int) {
   tmp := make([]int, 0)
   for res := range out { // 用range需要告诉通道关闭了
      //fmt.Println(res)
      for _, value := range res {
         if value%2 == 0 {
            //fmt.Println(value)
            tmp = append(tmp, value)
         }
      }
   }
   in <- tmp
   close(in)
   wg.Done()
}

// 助记
//1. chan<- int是一个只能发送的通道,可以发送但是不能接收;
//2. <-chan int是一个只能接收的通道,可以接收但是不能发送。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值