在本教程中,您将学习Go编程语言中使用的Goroutine。在Golang中,Goroutine用于实现并发。因此,我们首先将了解并发的概述,如何实现并发,Goroutine的用途等等。
让我们用一个更简单的方式来理解这个概念。考虑数学表达式
(4+ 2) *(1+4)
我们可以分3步解决这个表达式

计算相同表达式的另一种方法是

从这个数学表达式的例子中,我们可以推断出,即使执行顺序不同,最终输出也将保持不变。
并发是通过线程实现的。
在Go编程语言中,Goroutine是由Go运行时控制的轻量级线程。Goroutine与任何其他编程语言中的线程类似。Go线程也称为“绿色线程”。绿色线程是不由底层操作系统(OS)管理,而是由用户程序、用户库或其他方式管理的线程。
在Golang中,程序中并发执行的动作称为Goroutines。Golang不使用庞大且资源密集的线程,而是创建了一个名为Goroutine的线程抽象。Go运行时有一个调度器,它将Goroutine映射到OS线程上一段时间。
注意:程序员不与低级线程交互,而是与Golang运行时提供的高级Goroutine交互。
考虑一个简单的程序
package main
import "fmt"
func main() {
fmt.Println("Go program starts")
hello(1)
fmt.Println("Go program ends")
}
func hello(word int){
for i :=0; i<5 ; i++ {
fmt.Println(word,"hello ")
}
}
输出
Go program starts 1 hello 1 hello 1 hello 1 hello 1 hello Go program ends
| 线程 | Goroutine |
|---|---|
| 线程由操作系统管理。 | Goroutine方法由Golang运行时管理 |
| 线程依赖于硬件 | Goroutine独立于硬件。 |
| 线程没有简单的通信媒介。 | Goroutine有一个简单的通信媒介,称为通道。 |
| 线程没有可增长的分段堆栈。 | Goroutine有可增长的分段堆栈。 |
| 线程是抢占式调度的。 | Goroutine是协作式调度的 |
现在您将学习如何在Go程序中引入Goroutine,以及它将对上述程序造成什么变化。
Goroutine的语法
func main (){
…….
go f(a,b) //go keyword
}
func f(a,b){
}
关键字 go 在普通函数或方法中定义一个Goroutine语句。
在上述程序中,我们引入了一个新的关键字go来使这个程序并发。当普通的hello(1)函数被go hello(1)替换时。
在上述程序中顺序执行的hello(1)函数将开始在自己的一个独立的Goroutine中执行。当您运行代码时,它只打印main()函数内的两个打印语句。尽管调用了hello(1)函数,但在控制台中看不到任何输出。
package main
import "fmt"
func main() {
fmt.Println("Go program starts")
go hello(1)
fmt.Println("Go program ends")
}
func hello(word int){
for i :=0; i<5 ; i++ {
fmt.Println(word,"hello ")
}
}
输出
Go program starts Go program ends
为什么hello(1)函数的输出没有显示?
这是因为main()函数或主Goroutine在hello(1)函数执行之前就终止了。
对于main()函数或主Goroutine提前终止的解决方案是什么?
解决这个问题的办法是让主Goroutine休眠一段时间。为了实现这一点,我们需要在上述程序中添加一个新的语句
time.Sleep(100 * time.Millisecond)
让我们通过一个包含休眠的程序来理解。
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Go program starts")
go hello(1)
fmt.Println("Go program ends")
time.Sleep(100 * time.Millisecond)
}
func hello(word int){
for i :=0; i<5 ; i++ {
fmt.Println(word,"hello ")
}
}
输出
Go program starts Go program ends 1 hello 1 hello 1 hello 1 hello 1 hello
运行代码后,我们得到两个Goroutine
输出
Go program starts Go program ends
输出
1 hello 1 hello 1 hello 1 hello 1 hello
在Golang中,不需要像其他编程语言那样扩展线程类,也不需要实现任何可运行的类。
只需3个按键,即 go <space>,就足以实现并发。
在下面的程序中,声明了一个名为匿名函数的未命名函数,并当场调用了它。函数后的开闭大括号{}表示函数调用本身。
msg :="learn eTutorial"
go func(){
fmt.Println(msg)
}()
当我们运行下面的代码时,函数的输出如输出部分所示。即
Go program starts Go program ends learn eTutorial
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Go program starts")
msg :="learn eTutorial"
go func(){ //anonymous function
fmt.Println(msg)
}()
fmt.Println("Go program ends")
time.Sleep(100 * time.Millisecond)
}
输出
Go program starts Go program ends learn eTutorial
接下来,让我们看看当函数之后msg变量的值被改变时会发生什么。
msg :="learn eTutorial"
go func(){
fmt.Println(msg)
}()
msg := “hello users”
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Go program starts")
msg :="learn eTutorial"
go func(){ //anonymous function
fmt.Println(msg)
}()
msg = "hello users"
fmt.Println("Go program ends")
time.Sleep(100 * time.Millisecond)
}
输出
Go program starts Go program ends hello users
由于在函数中添加了最后一条语句,输出被修改了。比较上面讨论的两个程序的输出。
在刚才的代码中,我们引入了一个竞态条件。相同的代码从主Goroutine以及匿名函数中的Goroutine访问。

通常情况下,在程序中使用竞态条件是不安全的,因为它会导致很多错误或bug。