您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > Go语言

go并发编程

时间:2022-06-20 15:39:02  来源:  作者:VT漫步

协程(Goroutines)

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

goroutine 语法格式:

go 函数名( 参数列表 )

示例:

package mAIn

import "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 string
	fmt.Scanln(&input)
	fmt.Println("done")
	// 当我们运行这个程序时,将首先看到阻塞式调用的输出,然后是两个 Go 协程的交替输出。
	// 这种交替的情况表示 Go 运行时是以异步的方式运行协程的。
}

通道(channel)

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

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

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

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

ch := make(chan int)

示例:

package main

import (
	"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 := <-messages
	fmt.Println(msg)
	// 我们运行程序时,通过通道,消息 "ping" 成功的从一个 Go 协程传到另一个中。
	// 默认发送和接收操作是阻塞的,直到发送方和接收方都准备完毕。
	// 这个特性允许我们,不使用任何其它的同步操作,来在程序结尾等待消息 "ping"。
}

通道缓冲区

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

ch := make(chan int, 100)

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

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

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

示例:

package main

import "fmt"

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

同步实现

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

package main

import "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 main

import "fmt"

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

import "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 main

import "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 main

import (
	"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")
	}
}


Tags:go   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Google成了“AI界汪峰”,全都怪OpenAI?
  2016 年,Google CEO 桑达尔&middot;皮查伊在 I/O 开发者大会上豪情满怀地向全世界宣布:Google 已经成为了搜索的代名词,Alphabet 未来将成为一家 AI 优先的公司。  凭借...【详细内容】
2024-04-15  Search: go  点击:(5)  评论:(0)  加入收藏
机械设备B2B工厂外贸网站如何做Google谷歌SEO优化关键字排名?
在今天的全球化市场中,机械设备行业正面临着激烈的竞争。要在这一领域脱颖而出,拥有一个优化良好的B2B外贸网站至关重要。通过精准的谷歌SEO关键字排名,您的网站可以吸引更多海...【详细内容】
2024-04-08  Search: go  点击:(8)  评论:(0)  加入收藏
Google搜索引擎索引的网页数量有多少?谷歌官方提供数据进行参考
Google搜索引擎索引的网页数量有多少?二十世纪九十年代,网页的索引数量成了一个各大搜索引擎相互对比的指标。小编记得2000年谷歌搜索引擎的首页搜索框上方,还标记着谷歌索引的...【详细内容】
2024-03-27  Search: go  点击:(22)  评论:(0)  加入收藏
在Java应用程序中释放峰值性能:配置文件引导优化(PGO)概述
译者 | 李睿审校 | 重楼在Java开发领域,优化应用程序的性能是开发人员的持续追求。配置文件引导优化(Profile-Guided Optimization,PGO)是一种功能强大的技术,能够显著地提高Ja...【详细内容】
2024-03-18  Search: go  点击:(31)  评论:(0)  加入收藏
宝藏级Go语言开源项目——教你自己动手开发互联网搜索引擎
DIYSearchEngine 是一个能够高速采集海量互联网数据的开源搜索引擎,采用 Go 语言开发。Github 地址:https://github.com/johnlui/DIYSearchEngine运行方法首先,给自己准备一杯...【详细内容】
2024-03-12  Search: go  点击:(29)  评论:(0)  加入收藏
Go Gin框架实现优雅地重启和停止
在Web应用程序中,有时候我们需要重启或停止服务器,无论是因为更新代码还是进行例行维护。在这种情景下,我们需要保证应用程序的可用性和数据的一致性。这就需要优雅地关闭和重...【详细内容】
2024-01-30  Search: go  点击:(71)  评论:(0)  加入收藏
如何让Go程序以后台进程或daemon方式运行
本文探讨了如何通过Go代码实现在后台运行的程序。最近我用Go语言开发了一个WebSocket服务,我希望它能在后台运行,并在异常退出时自动重新启动。我的整体思路是将程序转为后台...【详细内容】
2024-01-26  Search: go  点击:(61)  评论:(0)  加入收藏
深入Go底层原理,重写Redis中间件实战
Go语言以其简洁、高效和并发性能而闻名,深入了解其底层原理可以帮助我们更好地利用其优势。在本文中,我们将探讨如何深入Go底层原理,以及如何利用这些知识重新实现一个简单的Re...【详细内容】
2024-01-25  Search: go  点击:(76)  评论:(0)  加入收藏
支付宝宣布更换Logo
鞭牛士 1月19日消息,今日,支付宝宣布更新Logo,此次最大的变化在于去掉了外框与文字,仅保留最具辨识度的“支”字标识。据了解,这是支付宝时隔4年再次更换Logo。支付宝App目前已用...【详细内容】
2024-01-19  Search: go  点击:(72)  评论:(0)  加入收藏
Go 内存优化与垃圾收集
Go提供了自动化的内存管理机制,但在某些情况下需要更精细的微调从而避免发生OOM错误。本文将讨论Go的垃圾收集器、应用程序内存优化以及如何防止OOM(Out-Of-Memory)错误。Go...【详细内容】
2024-01-15  Search: go  点击:(65)  评论:(0)  加入收藏
▌简易百科推荐
宝藏级Go语言开源项目——教你自己动手开发互联网搜索引擎
DIYSearchEngine 是一个能够高速采集海量互联网数据的开源搜索引擎,采用 Go 语言开发。Github 地址:https://github.com/johnlui/DIYSearchEngine运行方法首先,给自己准备一杯...【详细内容】
2024-03-12  OSC开源社区    Tags:Go语言   点击:(29)  评论:(0)  加入收藏
Go Gin框架实现优雅地重启和停止
在Web应用程序中,有时候我们需要重启或停止服务器,无论是因为更新代码还是进行例行维护。在这种情景下,我们需要保证应用程序的可用性和数据的一致性。这就需要优雅地关闭和重...【详细内容】
2024-01-30  源自开发者  微信公众号  Tags:Go   点击:(71)  评论:(0)  加入收藏
如何让Go程序以后台进程或daemon方式运行
本文探讨了如何通过Go代码实现在后台运行的程序。最近我用Go语言开发了一个WebSocket服务,我希望它能在后台运行,并在异常退出时自动重新启动。我的整体思路是将程序转为后台...【详细内容】
2024-01-26  Go语言圈  微信公众号  Tags:Go程序   点击:(61)  评论:(0)  加入收藏
深入Go底层原理,重写Redis中间件实战
Go语言以其简洁、高效和并发性能而闻名,深入了解其底层原理可以帮助我们更好地利用其优势。在本文中,我们将探讨如何深入Go底层原理,以及如何利用这些知识重新实现一个简单的Re...【详细内容】
2024-01-25  547蓝色星球    Tags:Go   点击:(76)  评论:(0)  加入收藏
Go 内存优化与垃圾收集
Go提供了自动化的内存管理机制,但在某些情况下需要更精细的微调从而避免发生OOM错误。本文将讨论Go的垃圾收集器、应用程序内存优化以及如何防止OOM(Out-Of-Memory)错误。Go...【详细内容】
2024-01-15  DeepNoMind  微信公众号  Tags:Go   点击:(65)  评论:(0)  加入收藏
Go函数指针是如何让你的程序变慢的?
导读Go 语言的常规优化手段无需赘述,相信大家也能找到大量的经典教程。但基于 Go 的函数值问题,业界还没有太多深度讨论的内容分享。本文作者根据自己对 Go 代码的使用与调优...【详细内容】
2024-01-15  腾讯云开发者  微信公众号  Tags:Go函数   点击:(92)  评论:(0)  加入收藏
Go编程中调用外部命令的几种场景
在很多场合, 使用Go语言需要调用外部命令来完成一些特定的任务, 例如: 使用Go语言调用Linux命令来获取执行的结果,又或者调用第三方程序执行来完成额外的任务。在go的标准库...【详细内容】
2024-01-09  suntiger    Tags:Go编程   点击:(124)  评论:(0)  加入收藏
Go 语言不支持并发读写 Map,为什么?
Go语言的map类型不支持并发读写的主要原因是并发读写会导致数据竞态(data race),这意味着多个 goroutine 可能同时访问并修改同一个 map,从而引发不确定的结果。在Go语言的设计...【详细内容】
2024-01-05  Go语言圈  微信公众号  Tags:Go 语言   点击:(83)  评论:(0)  加入收藏
Go微服务入门到容器化实践
Go微服务入门到容器化实践Go 是一门高效、现代化、快速增长的编程语言,非常适合构建 Web 应用程序。而 Docker 是一种轻量级的容器化技术,能够使得您的应用程序在任何地方运行...【详细内容】
2024-01-01  大雷家吃饭    Tags:Go微服务   点击:(70)  评论:(0)  加入收藏
你是否想知道如何应对高并发?Go语言为你提供了答案!
并发编程是当前软件领域中不可忽视的一个关键概念。随着CPU等硬件的不断发展,我们都渴望让我们的程序运行速度更快、更快。而Go语言在语言层面天生支持并发,充分利用现代CPU的...【详细内容】
2023-12-29  灵墨AI探索室  微信公众号  Tags:Go语言   点击:(115)  评论:(0)  加入收藏
站内最新
站内热门
站内头条