Loading...
墨滴

Bayanbulake

2021/12/12  阅读:32  主题:自定义主题1

Go语言之goroutine和通道

goroutine

在Go里,每一个并发执行的活动称为goroutine

如果你是一名Java程序员,可以把goroutine比作为线程,但是goroutine和线程在数量上有很大的差别,原因在于Go语言引入了协程的概念,协程相比于线程是一种用户态的线程,协程更加轻量,实用更加经济,因此同样的服务器可以开销的协程数量要比线程多很多。

简单的示例代码如下:

f() //调用f();等它返回
go f() // 新建一个调用分()的goroutine,不用等待

在下面的例子中,主goroutine计算第45个斐波那契数。因为它使用非常低效的递归算法,因此需要大量的时间来执行,在此期间我们提供一个可见的提示,显示一个字符串”spinner“来指示程序依然在运行。

package main

import (
 "fmt"
 "time"
)

func spinner(delay time.Duration) {
 for {
  for _, r := range `-\|/` {
   fmt.Printf("\r%c", r)
   time.Sleep(delay)
  }
 }
}

func fib(x int) int {
 if x < 2 {
  return x
 }
 return fib(x-1) + fib(x-2)
}

func main() {
 go spinner(100 * time.Microsecond)
 const n = 45
 fibN := fib(n) // slow
 fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)

}

若干秒后,fib(45)返回结果,如下图所示:

然后main函数返回,所有的goroutine都暴力地直接终结,然后程序退出。

通道

如果说goroutine是Go程序并发的执行体,通道就是它们之间的连接。通道是可以让一个goroutine发送特定值到另一个goroutine的通信机制。每一个通道是一个具体类型的导管,叫做通道的元素类型。一个有int类型元素的通道写为chan int

使用内置的make函数来创建一个通道:

ch := make(chan int) // ch的类型是 chan int

map一样,通道是一个使用make创建的数据结构的引用。当复制或者作为参数传递到一个函数时,复制的是引用,这样调用者和被调用者都引用同一份数据结构。和其他引用类型一样,通道的零值是nil

通道有两个主要的操作:发送接收,这两者统称为通信。send语句从一个goroutine传输一个值到另一个在执行接收表达式的goroutine 。两个操作都使用<-操作符书写。发送语句中,通道和值分别在<- 的左右两边。在接收表达式中,<-放在通道操作数的前面。

具体书写格式如下:

ch <- x //发送语句
x = <- ch // 赋值语句中的接收表达式
<- ch // 接收语句,丢弃结果

通道支持第三个操作:关闭,它设置一个标志位来指示值当前已经发送完毕,这个通道后面没有值了,关闭后的发送操作将导致宕机。在一个已经关闭的通道上进行接收操作,将获取所有已经发送的值,直到通道为空,这是任何接收操作会立即完成,同时获取到一个通道元素类型对应的零值。

调用内置的close函数来关闭通道:

close(ch)

无缓冲通道

使用简单的make调用创建的通道叫做无缓冲通道,但make还可以接受第二个可选参数,一个表示通道容量的整数。如果容量是0,make创建一个无缓冲的通道。

ch = make(chan int// 无缓冲通道
ch = make(chan int0// 无缓冲通道
ch = make(chan int3// 容量为3的缓冲通道

无缓冲通道上的发送操作将会阻塞,直到另一个goroutine在对应的通道上执行接收操作,这时值传送完成,两个goroutine都可以继续执行。相反,如果接收操作先执行,接收方goroutine将阻塞,直到另一个goroutine在同一个通道发送一个值。

使用无缓冲通道进行通信导致发送和接收goroutine同步化。因此,无缓冲通道也称为同步通道。当一个值在无缓冲通道上传递时,接收值后发送方goroutine才被再次唤醒。

package main
import "fmt"
func main(){
 ch:=make(chan int//这里就是创建了一个channel,这是无缓冲管道注意
 go func(){                  //创建子go程
  for i:=0;i<=6;i++{
   ch<-i     //循环写入管道
   fmt.Println("写入",i)
  }
 }()

 for i:=0;i<6;i++{      //主go程
  num:=<-ch         //循环读出管道
  fmt.Println("读出",num)
 }
}

缓冲通道

缓冲通道有一个元素队列,队列的最大长度在创建的时候通过make的容量参数来设置。如下代码创建了一个带有10个字符串的缓冲通道:

ch = make(chan string,10)

缓冲通道上的发送操作在对列的尾部插入一个元素,接收操作从队列的头部移除一个元素。如果通道满了,发送操作会阻塞所在的goroutine直到另一个goroutine对它进行接收操作来腾出可用的空间。反过来,如果通道是空的,执行接收操作的goroutine阻塞,直到另一个goroutine在通道上发送数据。

现在,我们可以在通道上无阻塞的发送三个值,但是在发送第四个值的时候就会阻塞。

package main

func main() {
 ch := make(chan string3)
 ch <- "A"
 ch <- "B"
 ch <- "C"
 ch <- "D"
}

在我们向管道塞入第四个值的时候,程序爆出了死锁的异常,如下图:

但是当我们在执行第四次向通道塞值的时候,从通道取出一个值,就可以安全的进行第四次塞值了,并且成功的打印出了队列的第一个元素A,如下图:

管道

通道可以用来连接goroutine,这样一个具体的输出是另一个的输入。管道一般是由三个goroutine组成,使用两个通道连接起来。

如下代码所示:

package main

import "fmt"

func main() {
 naturals := make(chan int)
 squares := make(chan int)

 // counter
 go func() {
  for x := 0; x< 100; x++ {
   naturals <- x
  }
  close(naturals)
 }()

 // squares
 go func() {
  for {
   x,ok := <-naturals
   if !ok {
    break // 通道关闭并且读完
   }
   squares <- x * x
  }
  close(squares)
 }()

 // printer(在主goroutine中)
 for x := range squares{
  fmt.Println(x)
 }

}

结束时,关闭每一个通道不是必需的,只有在通知接收方goroutine所有数据都发送完毕的时候才需要关闭通道。通道也是可以通过垃圾回收器根据它是否可以访问来决定是否回收它,而不是根据它是否关闭。

不要将这个close操作和对于文件的close操作混淆。当结束的时候对每一个文件调用Close方法是非常重要的。

单向通道

Go也提供了单向通道类型,仅仅导出发送或者接收操作。类型chan <- int是一个只能发送的通道,允许接收但是不能发送。(<- 操作符相对于chan关键字的位置是一个帮助记忆的点)。

package main

import "fmt"

// 单向输出通道 chan<-
func counter(out chan<- int) {
 for x := 0; x < 100; x++ {
  out <- x
 }
}

// 单向输出通道 chan<-
func squarer(out chan<- int, in <-chan int) {
 for v := range in {
  out <- v * v
 }
}

// 单向输入通道 <-chan
func printer(in <-chan int) {
 for v := range in {
  fmt.Println(v)
 }
}

func main() {
 naturals := make(chan int)
 squares := make(chan int)

 go counter(naturals)
 go squarer(squares, naturals)
 printer(squares)
}

并行循环

为了演示并行循环,我们考虑生成一批全尺寸图像的缩略图,代码如下:

其中需要导入的gopl.io/ch8/thumbnail包,从下面的网站下载:

GitHub - adonovan/gopl.io: Example programs from "The Go Programming Language"](https://github.com/adonovan/gopl.io/)

生成后的结果如图所示,都是我喜欢的动漫图片,如果也喜欢,私信我给你发哈~

以上就是Go语言关于goroutine和通道的内容,关于goroutine和通道其实还有很多可以深挖的东西,我们后面会继续学习。希望这篇文章可以帮助到你~

Bayanbulake

2021/12/12  阅读:32  主题:自定义主题1

作者介绍

Bayanbulake