Go 并发基础

二次信任 提交于 2021-01-04 11:58:14




协程(Goroutine)

我们知道 Go 中,存在一个 defer 关键字用于修饰一个函数或者方法,使得该函数或者方法在返回前才会执行,也就说被延迟执行,但又一定会执行。但其实 Go 中也存在类似的异步,或者说多线程的概念,但在 Go 中不叫作线程,而是叫协程。

协程相对于线程来说,是一个非常轻量级的东西,它在一个程序中,可以启动很多个。协程也称为 goroutine。goroutine 被 Go runtime 所调度,这一点和线程不一样。也就是说,Go 语言的并发是由 Go 自己所调度的,自己决定同时执行多少个 goroutine,什么时候执行哪几个。这些对于我们开发者来说很透明,只需要在编码的时候告诉 Go 语言要启动几个 goroutine,至于如何调度执行,我们不用关心。

启动一个 goroutine 简单,Go 语言为我们提供了 go 关键字,相比其他编程语言简化了很多,如代码:

func main() {

   go fmt.Println("码疯窝在香嗝喱辣")

   fmt.Println("I am main goroutine")

   time.Sleep(time.Second)

}

这样就启动了一个 goroutine,用来调用 fmt.Println 函数,打印"码疯窝在香嗝喱辣",所以这段代码里,其实有两个 goroutine,一个是 main 函数启动的 main goroutine,一个是通过 go 关键字启动的 goroutine。

也就是说,启动一个协程的关键字 go 即可,语法:

go function()

go 函数执行体

go 关键字后跟一个方法或者函数的调用,就可以启动一个 goroutine,让方法在这个新启动的 goroutine 中运行。运行以上示例,可以看到如下输出:

I am main goroutine

#待一秒的同时输出下面

码疯窝在香嗝喱辣

从输出结果也可以看出,程序是并发的,go 关键字启动的 goroutine 并不阻塞 main goroutine 的执行,所以我们看到如上打印。

在 Go 中,既然有了协程,那么这些协程之间如何通信呢?Go 提供了一个 channel(通道) 来解决。

声明一个 channel

在 Go 语言中,声明一个 channel 非常简单,使用内置的 make 函数即可,如下:

ch:=make(chan string)

其中 chan 是一个关键字,表示是 channel 类型。后面的 string 表示 channel 里的数据是 string 类型。通过 channel 的声明也可以看到,chan 是一个集合类型。

定义好 chan 后就可以使用了,一个 chan 的操作只有两种:发送和接收:

  • 发送:向 chan 发送值,把值放在 chan 中,操作符为 chan <-

  • 接收:获取 chan 中的值,操作符为 <- chan

示例:

package main

import "fmt"

func main() {

 ch := make(chan string)

 go func() {

  fmt.Println("码疯窝在香嗝喱辣")

  ch <- "发送数据者:码疯窝在香嗝喱辣"

 }()

 fmt.Println("I am main goroutine")

 v := <- ch

 fmt.Println("接收到的chan中的值为:",v)

}

我们先来执行看看打印结果:

I am main goroutine

码疯窝在香嗝喱辣

接收到的chan中的值为:送数据者:码疯窝在香嗝喱辣

从运行结果可以看出:达到了使用 time.Sleep 函数的效果。

相信应该明白为什么程序不会在新的 goroutine 完成之前退出了,因为通过 make 创建的 chan 中没有值,而 main goroutine 又想从 chan 中获取值,获取不到就一直等待,等到另一个 goroutine 向 chan 发送值为止。

无缓冲 channel

上面的示例中,使用 make 创建的 chan 就是一个无缓冲 channel,它的容量是 0,不能存储任何数据。所以无缓冲 channel 只起到传输数据的作用,数据并不会在 channel 中做任何停留。这也意味着,无缓冲 channel 的发送和接收操作是同时进行的,它也被称为同步 channel。

有缓冲 channel

有缓冲 channel 类似一个可阻塞的队列,内部的元素先进先出。通过 make 函数的第二个参数可以指定 channel 容量的大小,进而创建一个有缓冲 channel,如:

cacheCh := make(chan int,5)

定义了一个容量为 5 的元素为 int 类型的 chan。

一个有缓冲 channel 具备以下特点:

  • 有缓冲 channel 的内部有一个缓冲队列

  • 发送操作是向队列的尾部插入元素,如果队列已满,则阻塞等待,直到另一个 goroutine 执行,接收操作释放队列的空间

  • 接收操作是从队列的头部获取元素并把它从队列中删除,如果队列为空,则阻塞等待,直到另一个 goroutine 执行,发送操作插入新的元素

cache := make(chan int,5)

cache <- 2

cache <- 3

fmt.Println("容量:",cap(cache),",元素个数:",len(cache))

无缓冲 channel 其实就是一个容量大小为 0 的 channel。比如 make(chan int,0)


关闭 channel

通过内置函数 close 即可关闭 channel。如果一个 channel 被关闭了,就不能向里面发送数据了,如果发送的话,会引起 painc 异常。但是还可以接收 channel 里的数据,如果 channel 里没有数据的话,接收的数据是元素类型的零值。

单向 channel

所谓单向,即可要不发送,要么只能接收。所以单向 channel 的声明也很简单,只需要在声明的时候带上 <- 操作符即可,如下:

send := make(chan <- int)
receive := make(<- chan int)




云原生社区合肥站



云原生社区合肥站正式启动啦,欢迎Base合肥、关注云原生、长期从事云原生的同志们踊跃加入,云原生社区合肥站会因为你们的加入而变得更加美好~

详情参见Issue:https://github.com/cloudnativeto/community/issues/107




欢迎关注个站



往期回顾

微服务自动化部署CI/CD

如何利用k8s拉取私有仓库镜像

个站建设基础教程

ArrayList、LinkedList&nbsp;你真的了解吗?

大佬整理的mysql规范,分享给大家

如果张东升是个程序员

微服务架构设计之解耦合

浅谈负载均衡

Oauth2的认证实战-HA篇

Oauth2的授权码模式《上》

浅谈开发与研发之差异

浅谈&nbsp;Java&nbsp;集合&nbsp;|&nbsp;底层源码解析

基于 Sentinel 作熔断 | 文末赠资料

基础设施服务k8s快速部署之HA篇

今天被问微服务,这几点,让面试官刮目相看

Spring cloud 之多种方式限流(实战)

Spring cloud 之熔断机制(实战)

面试被问finally 和 return,到底谁先执行?

Springcloud Oauth2 HA篇

Spring Cloud Kubernetes之实战一配置管理

Spring Cloud Kubernetes之实战二服务注册与发现

Spring Cloud Kubernetes之实战三网关Gateway



关注公众号,回复入群,获取更多惊喜!公众号(程序猿Damon)里回复 ES、Flink、Java、Kafka、MQ、ML、监控、大数据、k8s 等关键字可以查看更多关键字对应的文章。





点击 "damon8.cn获取更好的阅读体验!


          
          
❤️ 给个 「在看」 ,是对我最大的支持❤️

本文分享自微信公众号 - 程序猿Damon(Damon4X)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!