学习Go:并发模式、Goroutines和Channels介绍

220 阅读4分钟

欢迎来到第一篇关于Go中的并发性的文章,在这篇文章中,我将向你描述Go中处理并发性的基元,特别是goroutines、channel和所有必要的关键字,以便很好地利用它们。


什么是并发性?

并发是指独立执行的进程的组合,换句话说,它是指同时处理很多事情。并发通常与平行主义相混淆,后者是同时执行(可能相关的)计算,它是关于一次做很多事情

一个现实生活中的比喻是一家咖啡店,在并发的情况下,我们指的是使用相同的资源来处理一些任务,在咖啡店中,这将是关于思考要泡什么咖啡(计算),并在做出决定时使用咖啡机(CPU)。

What is Concurrency?

并行的情况下,我们采取类似的方法,但不是共享咖啡机,而是每一行的顾客都有一台专用的咖啡机,因此这两行人都可以独立地冲泡咖啡。

What is Parallelism?

在Go中,有两个概念用来处理并发问题:goroutineschannels

什么是Goroutine?

Goroutine 是一个独立执行的函数,使用go 关键字,最简单的例子是这样的。

func main() {
	go hello()
}

func hello() {
	fmt.Println("it's most likely you will never see this")
}

其中函数hello 是作为一个goroutine在main 函数中执行的。如果你运行这个例子,你会注意到信息在大多数情况下不会被打印出来(或者根本不会被打印出来!),这是因为mainhello 完成其执行之前就退出了。

这种行为的原因是在代码中我们没有办法指示main 等待hello 完成,要做到这一点,我们需要使用Go中的另一个概念,即channels

什么是通道?

Channel 是一种机制,通过它我们可以发送和接收价值。要在Go中创建一个通道,我们使用make 关键字,并指出该通道将使用的值的类型,类似于mapslice 的类型。

当创建一个通道时,我们也可以指出一个长度,指出一个长度可以创建Buffered Channels ,不指出一个长度可以创建Unbuffered Channels 。两者的区别在于,当达到容量或没有更多的元素可供接收时,那些发送或接收值的阻塞方式。

默认情况下,如果对方没有准备好,发送方和接收方都会阻塞,在缓冲通道的情况下,向一个满的通道发送会阻塞,而从它那里接收只有在空的时候才会阻塞。

为了使用通道,我们必须使用一个新的操作符<- ,即箭头,箭头相对于通道变量的位置将表示一个接收发送动作,例如,如果箭头在右边。

它表示我们正在发送v 到通道ch ,如果箭头在左边。

表示我们从通道ch 接收一个值,并将其分配给v

一个更完整的例子看起来像。

func main() {
	ch := make(chan string)

	go func() {
		fmt.Println(time.Now(), "taking a nap")

		time.Sleep(2 * time.Second)

		ch <- "hello"
	}()

	fmt.Println(time.Now(), "waiting for message")

	v := <-ch

	fmt.Println(time.Now(), "received", v)
}

我们使用一个通道,ch ,从我们启动的goroutine中接收一个值,"hello"

在Go中,有三个关键字允许我们使用通道。

  • close: 表示一个通道不再能用于发送数值。
  • range:在一个通道中持续接收数值,直到它被关闭,以及
  • select: 作用类似于switch关键字,但适用于多个通道。

一个更完整的例子是这样的。

func main() {
	ch := make(chan int, 2)
	exit := make(chan struct{})

	go func() {
		for i := 0; i < 5; i++ {
			fmt.Println(time.Now(), i, "sending")
			ch <- i
			fmt.Println(time.Now(), i, "sent")

			time.Sleep(1 * time.Second)
		}

		fmt.Println(time.Now(), "all completed, leaving")

		close(ch)
	}()

	go func() {
		for {
			select {
			case v, open := <-ch:
				if !open {
					close(exit)
					return
				}

				fmt.Println(time.Now(), "received", v)
			}
		}
	}()

	fmt.Println(time.Now(), "waiting for everything to complete")

	<-exit

	fmt.Println(time.Now(), "exiting")
}

第一个goroutine正在向ch ,第二个goroutine正在接收这些值并打印出来。在第一个goroutine中,当for完成后,close 的使用是可见的,然后在第二个goroutine中,open 的值变为false,表明通道ch 被关闭,没有其他东西可读,触发另一个close ,在这种情况下,exit 通道的使用是为了等待两个goroutine完成,最终退出程序。

总结

这篇文章为Go中的并发模式系列拉开了序幕。