Golang 中的 Goroutines


2022年1月17日, Learn eTutorial
2073

在本教程中,您将学习Go编程语言中使用的Goroutine。在Golang中,Goroutine用于实现并发。因此,我们首先将了解并发的概述,如何实现并发,Goroutine的用途等等。

Golang中的并发是什么?

  • 并发是指程序或算法的不同部分或单元能够独立执行,而不影响程序的终端(最终)输出的能力。
  • 当一个程序被分解成不同的部分,比如说“n”个部分时。
  • 最终输出不应依赖于这“n”个部分是如何执行的。
     

并发的好处

  • 并发允许并发单元的并行执行。
  • 它提高了执行的整体速度/性能。
     

让我们用一个更简单的方式来理解这个概念。考虑数学表达式

(4+ 2) *(1+4)
我们可以分3步解决这个表达式

GO :Goroutine

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

GO :Goroutine

从这个数学表达式的例子中,我们可以推断出,即使执行顺序不同,最终输出也将保持不变。
并发是通过线程实现的。

Golang中的线程是什么?

在Go编程语言中,Goroutine是由Go运行时控制的轻量级线程。Goroutine与任何其他编程语言中的线程类似。Go线程也称为“绿色线程”。绿色线程是不由底层操作系统(OS)管理,而是由用户程序、用户库或其他方式管理的线程。

Golang中的Goroutine是什么?

在Golang中,程序中并发执行的动作称为Goroutines。Golang不使用庞大且资源密集的线程,而是创建了一个名为Goroutine的线程抽象。Go运行时有一个调度器,它将Goroutine映射到OS线程上一段时间。

注意:程序员不与低级线程交互,而是与Golang运行时提供的高级Goroutine交互。

考虑一个简单的程序

  • main函数 main() 打印 “go program starts” & “go program end”
  • 在 main() 内部调用 hello(1) 函数,参数为整数数据类型 1。
  • 当程序开始执行时,控制从 main 开始,当它看到 hello() 时,会跳转到声明和初始化 hello 语句的代码处。
  • hello() 函数在for循环内打印 hello 5次
  • 当循环结束时,控制返回到 main & 打印 “go program end“ 语句。

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

Golang中线程与Goroutine的区别?

线程 Goroutine
线程由操作系统管理。 Goroutine方法由Golang运行时管理
线程依赖于硬件 Goroutine独立于硬件。
线程没有简单的通信媒介。 Goroutine有一个简单的通信媒介,称为通道。
线程没有可增长的分段堆栈。 Goroutine有可增长的分段堆栈。
线程是抢占式调度的。 Goroutine是协作式调度的

如何在Golang中引入Goroutine?

现在您将学习如何在Go程序中引入Goroutine,以及它将对上述程序造成什么变化。
Goroutine的语法


func  main (){
      …….
                go f(a,b)       //go keyword
}
                func  f(a,b){
} 

关键字 go 在普通函数或方法中定义一个Goroutine语句。

  • go f(a,b) 启动一个新的Goroutine运行f(a,b)。
  • 函数f(a,b)在当前的Goroutine中求值,而f的执行发生在新的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

  1. main() Goroutine 输出

    输出

    
    Go program starts
    Go program ends
    
  2. 运行hello(1)函数的Goroutine的输出

    输出

    
    1 hello 
    1 hello 
    1 hello 
    1 hello 
    1 hello
    

在Golang中,不需要像其他编程语言那样扩展线程类,也不需要实现任何可运行的类。
只需3个按键,即 go <space>,就足以实现并发。

Goroutine中的匿名函数

在下面的程序中,声明了一个名为匿名函数的未命名函数,并当场调用了它。函数后的开闭大括号{}表示函数调用本身。

//程序标题。如果没有标题,请删除h3标签


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以及匿名函数中的Goroutine访问。

GO : GOroutine

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

Goroutine的优势

  • Goroutine可以用很小的栈空间启动。
  • Goroutine可以轻松创建或销毁
  • Goroutine可以快速重新分配。
  • Go应用程序可以有成千上万个Goroutine。