Goroutines 和并发
编程中的并发性是计算机程序一次执行多条指令/任务的能力。通过并发,长时间运行的任务不会阻碍程序中的其他任务,因此长时间运行的任务可以单独运行而不是阻塞,而程序的其余部分继续运行。总之,并发是指一个任务不必等到另一个任务完成后再运行。这确保了程序的快速高效执行。
不同的编程语言有不同的处理并发的方法 Go 使用 goroutines 来处理,goroutine 是 Go 编程语言中的轻量级执行线程,是与主程序流程并发执行的函数。与传统线程(也可以处理并发)相比,它们对您的程序的开销更少,因此是 Go 编程中的流行选项。
创建协程
Go routines 是普通函数,但是用“go”关键字调用,基本上任何函数都可以成为 goroutine,
例子:
代码语言:javascript复制func helloWorld(str string) {
for i := 0; i < 6; i {
fmt.Println(str, ":", i)
}
}
func main() {
go helloWorld("Hello World")
time.Sleep(1 * time.Second)
}
这里我们有一个包含三个 goroutines 的程序,自动成为例程的 main 函数和使用关键字在 main 函数内部调用的两个 helloWorld 函数go
。helloWorld goroutine 分别打印五次“Hello World”和“hello world”。
注意time.Sleep(1 * time.Second)
在“main”函数中,它将函数延迟一秒钟,因为如果没有它,“main”函数将不会等待我们的“helloWorld”goroutine 完成执行,然后再转到下一行并完成程序。
什么是CHANNELS,它们有什么用?
通道就像 goroutine 之间的管道,它们为 goroutine 之间提供了一种有效通信的方式,通道是一种将特定类型的数据从一种数据类型发送到另一种数据类型的方式。
我们使用make
方法创建通道,类型chan
后跟您希望通道在 make() 方法中作为参数发送的数据类型;
var channel = make(chan int)
这是一个正在使用的频道的示例程序;
代码语言:javascript复制package main
import (
"fmt"
func sendInteger(myIntChannel chan int){
for i:=1; i<6; i {
myIntChannel <- i // sending value
}
}
func main() {
myIntChannel := make(chan int)
go sendInteger(myIntChannel)
for i:=1; i<6; i {
message := <-myIntChannel //receiving value
fmt.Println(message)
}
}
使用通道发送和接收值
myIntChannel
我们在 main 函数中创建通道,然后将其传递给sendInteger
goroutine,在 goroutine 中我们使用通道发送数字 1-5,通道在特殊的左指箭头的左侧(<-)
,我们要发送的值在右侧箭头的一侧。然后我们在 main 函数中接收从 goroutine 发送的值sendInteger
,但这次通道在箭头的右侧,发送的值在两个函数同时运行时打印出来。
使用通道发送和接收数据时要注意的一件事是“阻塞”,即阻塞程序,message := <-myIntChannel
通过通道接收数据的语句将阻塞,直到它们接收到数据,发送数据的语句也会阻塞,直到接收者准备好。
CHANNELS定向
通道可以被定向,即指定发送或接收数据,我们可以<-
在要使用的函数的参数中使用箭头和 chan 关键字来做到这一点。
func sendInteger(myIntChannel chan <- int){ //send-only channel---the left arrow on the right side of the "chan" keyword in the function's arguments specifies a send only channel
for i:=1; i<=50; i {
myIntChannel <- i // sending value
}
}
func printer(myIntChannel <- chan int) { //receive-only channel---the left arrow on the left side of the "chan" keyword in the function's arguments specifies a receive only channel
for i:=1; i<=50; i {
message := <-myIntChannel //receiving value
fmt.Println(message)
}
}
func main() {
myIntChannel := make(chan int)
go sendInteger(myIntChannel)
go printer(myIntChannel)
time.Sleep(1 * time.Second)
}
SELECT语句
select 语句几乎与 Go 中的 switch 语句相同,它们都用于语句的条件执行目的,但 select 语句更针对通道,它们有助于在通道满足条件时执行操作。
例子:
代码语言:javascript复制func sendValuesFast(myStringChannel1 chan string){
time.Sleep(5 * time.Millisecond)
myStringChannel1 <- "Fast" // sending value
}
func sendValuesSlow(myStringChannel2 chan string){
time.Sleep(30 * time.Millisecond)
myStringChannel2 <- "Slow" // sending value
}
func main() {
myStringChannel1 := make(chan string)
myStringChannel2 := make(chan string)
go sendValuesFast(myStringChannel1)
go sendValuesSlow(myStringChannel2)
select{
case res:= <- myStringChannel1:
fmt.Println("sendValuesFast finished first",res)
case res:= <- myStringChannel2:
fmt.Println("sendValuesSlow finished first",res)
}
}
在此示例中,我们在 sendValuesFast 和 sendValuesSlow goroutine 中有两个通道,我们time.Sleep()
对 myStringChannel2 使用了延迟,因此 myStringChannel 首先完成,并且满足 select 语句中的第一种情况并执行它的操作。
BUFFERED CHANNELS
到目前为止,我们一直在使用所谓的无缓冲通道,我们之前说过它们会阻塞,直到在通道上发送或接收数据,这是因为无缓冲通道没有存储空间来存储通过它发送的数据,因此它们必须等到在再次发送之前有一个接收它的声明。
另一方面,缓冲通道是通过方法中的内存分配创建的make()
,并且仅在通道已满(发送时)或通道为空(接收时)时才会阻塞。它允许您存储创建时指定的数据量,例如channel:=make(chan int, 5)
创建一个可以存储 5 个整数的通道,如果发送第 6 个整数,通道将阻塞,直到通道中的消息被读取。
func bufferFunction(bufferChannel chan int) {
for i := 1; i<=6; i {
bufferChannel <- i
time.Sleep(1 * time.Second)
fmt.Println("Channel sent", i)
}
}
func main() {
bufferChannel:= make(chan int,5)
bufferFunction(bufferChannel)
}
这里我们有一个示例缓冲通道,其空间可容纳 5 个整数,我们使用 for 循环尝试发送数字 1-6,我们在没有接收语句的情况下执行此操作,因此当循环尝试发送第 6 个整数时,通道阻塞并且程序结束。