go并发编程

协程(Goroutines)

在Go语言中,每一个并发的执行单元叫作一个goroutine。,我们只需要通过 go 关键字来开启 goroutine 即可。goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

goroutine 语法格式:

go 函数名( 参数列表 )

示例

package mainimport “fmt”func f(from string) {for i := 0; i < 3; i++ {fmt.Println(from, ":", i)}}func main() {// 假设我们有一个函数叫做 f(s)。// 我们使用一般的方式调并同时运行。f("direct")// 使用 go f(s) 在一个 Go 协程中调用这个函数。// 这个新的 Go 协程将会并行的执行这个函数调用。go f("goroutine")// 你也可以为匿名函数启动一个 Go 协程。go func(msg string) {fmt.Println(msg)}("going")// 现在这两个 Go 协程在独立的 Go 协程中异步的运行,所以我们需要等它们执行结束。// 这里的 Scanln 代码需要我们在程序退出前按下任意键结束。var input stringfmt.Scanln(&input)fmt.Println("done")// 当我们运行这个程序时,将首先看到阻塞式调用的输出,然后是两个 Go 协程的交替输出。// 这种交替的情况表示 Go 运行时是以异步的方式运行协程的。}

通道(channel)

如果说goroutine是Go语言程序的并发体的话,那么channels则是它们之间的通信机制。通道(channel)是用来传递数据的一个数据结构。

通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。

ch <- v // 把 v 发送到通道 chv := <-ch // 从 ch 接收数据 // 并把值赋给 v

声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:

ch := make(chan int)

示例:

package mainimport (“fmt”)// 通道 是连接多个 Go 协程的管道。// 你可以从一个 Go 协程将值发送到通道,然后在别的 Go 协程中接收。func main() {// 使用 make(chan val-type) 创建一个新的通道。// 通道类型就是他们需要传递值的类型。messages := make(chan string)// 使用 channel <- 语法 发送 一个新的值到通道中。// 这里我们在一个新的 Go 协程中发送 "ping" 到上面创建的messages 通道中。go func() {messages <- "ping"}()// 使用 <-channel 语法从通道中 接收 一个值。// 这里将接收我们在上面发送的 "ping" 消息并打印出来。msg := <-messagesfmt.Println(msg)// 我们运行程序时,通过通道,消息 "ping" 成功的从一个 Go 协程传到另一个中。// 默认发送和接收操作是阻塞的,直到发送方和接收方都准备完毕。// 这个特性允许我们,不使用任何其它的同步操作,来在程序结尾等待消息 "ping"。}

通道缓冲区

通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:

ch := make(chan int, 100)

带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。

不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。

注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。

示例:

package mainimport “fmt”// 默认通道是 无缓冲 的,这意味着只有在对应的接收(<- chan)通道准备好接收时,才允许进行发送(chan <-)。// 可缓存通道允许在没有对应接收方的情况下,缓存限定数量的值。func main() {// 这里我们 make 了一个通道,最多允许缓存 2 个值。messages := make(chan string, 2)// 因为这个通道是有缓冲区的,即使没有一个对应的并发接收方,我们仍然可以发送这些值。messages <- "buffered"messages <- "channel"// 然后我们可以像前面一样接收这两个值。fmt.Println(<-messages)fmt.Println(<-messages)}

同步实现

我们可以通过channel实现同步,如下:

package mainimport “fmt”import “time”// 我们可以使用通道来同步 Go 协程间的执行状态。// 这里是一个使用阻塞的接受方式来等待一个 Go 协程的运行结束。func worker(done chan bool) {// 这是一个我们将要在 Go 协程中运行的函数。// done 通道将被用于通知其他 Go 协程这个函数已经工作完毕。fmt.Print(“working…”)time.Sleep(time.Second)fmt.Println(“done”)// 发送一个值来通知我们已经完工啦。done <- true}func main() {// 运行一个 worker Go协程,并给予用于通知的通道。done := make(chan bool, 1)go worker(done)// 程序将在接收到通道中 worker 发出的通知前一直阻塞。<-done// 如果你把 <- done 这行代码从程序中移除,程序甚至会在 worker还没开始运行时就结束了。}

通道方向示例

package mainimport “fmt”// 当使用通道作为函数的参数时,你可以指定这个通道是不是只用来发送或者接收值。// 这个特性提升了程序的类型安全性。func ping(pings chan <- string, msg string) {// ping 函数定义了一个只允许发送数据的通道。// 尝试使用这个通道来接收数据将会得到一个编译时错误。pings <- msg}func pong(pings <-chan string, pongs chan <- string) {// pong 函数允许通道(pings)来接收数据,另一通道(pongs)来发送数据。msg := <-pingspongs <- msg}func main() {pings := make(chan string, 1)pongs := make(chan string, 1)ping(pings, "passed message")pong(pings, pongs)fmt.Println(<-pongs)}

Go 遍历通道与关闭通道

Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:

v, ok := <-ch

如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。

通道选择器(select)

select {case <-ch1: // …case x := <-ch2: // …use x…case ch3 <- y: // …default: // …}

select语句的一般形式。和switch语句稍微有点相似,也会有几个case和最后的default选择支。每一个case代表一个通信操作(在某个channel上进行发送或者接收)并且会包含一些语句组成的一个语句块。一个接收表达式可能只包含接收表达式自身(译注:不把接收到的值赋值给变量什么的),就像上面的第一个case,或者包含在一个简短的变量声明中,像第二个case里一样;第二种形式让你能够引用接收到的值。

select会等待case中有能够执行的case时去执行。当条件满足时,select才会去通信并执行case之后的语句;这时候其它通信是不会执行的。一个没有任何case的select语句写作select{},会永远地等待下去。

示例:

package mainimport “time”import “fmt”// Go 的通道选择器 让你可以同时等待多个通道操作。// Go 协程和通道以及选择器的结合是 Go 的一个强大特性。func main() {// 在我们的例子中,我们将从两个通道中选择。c1 := make(chan string)c2 := make(chan string)// 各个通道将在若干时间后接收一个值,这个用来模拟例如并行的 Go 协程中阻塞的 RPC 操作go func() {time.Sleep(time.Second * 1)c1 <- "one"}()go func() {time.Sleep(time.Second * 2)c2 <- "two"}()// 我们使用 select 关键字来同时等待这两个值,并打印各自接收到的值。for i := 0; i < 2; i++ {select {case msg1 := <-c1:fmt.Println("received", msg1)case msg2 := <-c2:fmt.Println("received", msg2)}}// 我们首先接收到值 "one",然后就是预料中的 "two"了。// 注意从第一次和第二次 Sleeps 并发执行,总共仅运行了两秒左右。}

超时实现

package mainimport “time”import “fmt”// 超时 对于一个连接外部资源,或者其它一些需要花费执行时间的操作的程序而言是很重要的。// 得益于通道和 select,在 Go中实现超时操作是简洁而优雅的。func main() {c1 := make(chan string, 1)// 在我们的例子中,假如我们执行一个外部调用,并在 2 秒后通过通道 c1 返回它的执行结果。go func() {time.Sleep(time.Second * 2)c1 <- "result 1"}()// 这里是使用 select 实现一个超时操作。res := <- c1 等待结果,<-Time.After 等待超时时间 1 秒后发送的值。// 由于 select 默认处理第一个已准备好的接收操作,如果这个操作超过了允许的 1 秒的话,将会执行超时 case。select {case res := <-c1:fmt.Println(res)case <-time.After(time.Second * 1):fmt.Println("timeout 1")}// 如果我允许一个长一点的超时时间 3 秒,将会成功的从 c2接收到值,并且打印出结果。c2 := make(chan string, 1)go func() {time.Sleep(time.Second * 2)c2 <- "result 2"}()select {case res := <-c2:fmt.Println(res)case <-time.After(time.Second * 3):fmt.Println("timeout 2")}// 运行这个程序,首先显示运行超时的操作,然后是成功接收的。// 使用这个 select 超时方式,需要使用通道传递结果。// 这对于一般情况是个好的方式,因为其他重要的 Go 特性是基于通道和select 的。}

非阻塞选择器

package mainimport (“fmt”)// 常规的通过通道发送和接收数据是阻塞的。// 然而,我们可以使用带一个 default 子句的 select 来实现非阻塞 的发送、接收,甚至是非阻塞的多路 select。func main() {messages := make(chan string)signals := make(chan bool)// 这里是一个非阻塞接收的例子。// 如果在 messages 中存在,然后 select 将这个值带入 <-messages case中。// 如果不是,就直接到 default 分支中。select {case msg := <-messages:fmt.Println("received message", msg)default:fmt.Println("no message received")}// 一个非阻塞发送的实现方法和上面一样。msg := "hi"select {case messages <- msg:fmt.Println("sent message", msg)default:fmt.Println("no message sent")}// 我们可以在 default 前使用多个 case 子句来实现一个多路的非阻塞的选择器。// 这里我们试图在 messages和 signals 上同时使用非阻塞的接受操作。select {case msg := <-messages:fmt.Println("received message", msg)case sig := <-signals:fmt.Println("received signal", sig)default:fmt.Println("no activity")}}

分享不易~ 感谢大家阅读

郑重声明:本文内容及图片均整理自互联网,不代表本站立场,版权归原作者所有,如有侵权请联系管理员(admin#wlmqw.com)删除。
(0)
用户投稿
上一篇 2022年6月21日
下一篇 2022年6月21日

相关推荐

  • RW被EDGM让二追三,小夜重回打野位,迷之操作无法理解

    KPL夏季赛常规赛进入第五周,济南RW侠对阵上海EDGM。RW侠第一阶段还在A组,他们掉进B组之后,很多人都觉得他们还能打回来。这次RW侠更换了首发,小宋重新回到了首发位置,小夜变…

    2022年7月9日
  • 支付宝大额存款需3年提取?不实

    个人账户的高风险操作行为可能影响账户正常使用。为规避风险,用户须规范使用自己的账户,不要轻易与不明账户交易,不要把个人账户出借给他人使用,也不要进行网络刷单、跑分、虚拟币交易等操作…

    2022年8月1日
  • 电动车“每天充”和“用光再充”,哪个更伤车?修车师傅这样说

    随着时代的发展,各行各业都有新的产品出现,比如汽车,新能源汽车,就代替了传统的燃油汽车。除此之外,老百姓还非常喜欢购买两轮的电动车,这种车辆使用起来更加便宜,因为使用的是电力,相对…

    2022年8月3日
  • 技术标准-1技术架构框架

    技术架构框架包括:业务,应用,数据,技术,安全五个方面。其中业务是核心,是挣钱的根本,是技术,数据,应用,安全四个方面的根本,业务就是王道,业务指导技术,没有业务其他的四个方面什么…

    2022年6月15日
  • 幻影车神3魔盗激情的精彩动图

    本文动图很多,流量党慎看。 2013年上映的《幻影车神3魔盗激情》,由阿米尔汗主演。 剧情并不复杂,简单点来说,就是一对双胞胎兄弟为父报仇和警察斗智斗勇的故事。 阿米尔·汗一人分饰…

    2022年8月10日
  • Python基础必掌握的集合Set使用

    数学中对集合的严格定义可能是抽象的且难以掌握。但实际上可以将集合简单地认为是定义明确的不同对象的集合,通常称为元素或成员。 Python 提供了一个内置的集合类型来将对象分组到一个…

    2022年6月14日
  • 人才引进事业编和普通事业编哪个好?

    人才引进更好。 获得事业编的方式有很多种,最常见的是考试,也有政策性分配、转正、人才引进等等,无论通过哪种方式获得的编制,只要在同一个地区,本质上差别不大,区别主要在于附加福利上。…

    2022年4月19日
  • 微信小程序实现活动轨迹回放

    示例简介 本文介绍使用组件map和API的MapContext+wx.getLocation来实现活动轨迹回放。 最终效果: 实现过程 1、文件index.wxml代码如下,这一块…

    2022年6月14日
  • 华为笔记本电脑卖爆了!2022年Q1销量环比增加47%

    【CNMO新闻】日前,CNMO注意到,市场调研公司GFK发布了2022第一季度中国零售市场个人电脑行情的数据,其中华为笔记本电脑在中国笔记本电脑市场整体环比下行的情况下逆势反增。 …

    2022年6月22日
  • 2022年破壁机怎么选?静音、易操作、高颜值,一个都不能少

    什么是破壁机?破壁机其实是个“伪命名”,号称是可以打破细胞壁,并让营养成分都释放出来。当然,个人认为,打破细胞壁应该属于商家宣传需要的说法,但相比豆浆机、料理机和榨汁机,食材的细腻…

    2022年6月29日

联系我们

联系邮箱:admin#wlmqw.com
工作时间:周一至周五,10:30-18:30,节假日休息