首页 > 编程笔记 > Go语言笔记 阅读:31

Go语言创建协程(goroutine)的2种方法(附带实例)

在 Go语言中,协程(goroutine)不仅是轻量级线程,而且是用户级线程。

用户级线程是指由用户控制代码的执行流程,不需要操作系统进行调度和分派。也就是说,Go 程序智能地将协程中的任务合理分配给每个 CPU。

Go 语言不仅有协程,还有用于调度协程的、对接系统级线程的调度器。调度器主要负责统筹调配 Go 语言并发编程模型(即 GPM 模型)中的 3 个主要元素,它们分别是 G(goroutine 的缩写)、P(processor 的缩写)和 M(machine 的缩写)。

其中,M 指的是系统级线程;P 指的是能够使若干个 G 在恰当的时机与 M 对接,并得以运行的中介。

Go 程序在启动时,从 main 包的 main() 函数开始,为 main() 函数创建一个默认的协程。下面讲解两种创建协程方法:

为普通函数创建协程

在 Go 程序中,使用 go 关键字为普通函数创建协程。需要特别注意的是,可以为一个普通函数创建多个协程;但一个协程必定对应一个普通函数。

使用 go 关键字为普通函数创建协程的语法格式如下:
go 函数名称(parameter)
parameter 是参数列表。

当使用 go 关键字创建协程时,将忽略被调用函数的返回值。

【实例 1】实现并发编程。现有一辆只为上下车的乘客提供一个车门的公交车,这辆公交车行至某一站台后,有 5 位乘客下车,有 5 位乘客上车。无论是下车还是上车,乘客与乘客之间的时间间隔都是 1s。编写 Go 程序通过并发编程实现上述的上下车问题,代码如下:
package main //声明 main 包

import (
    "fmt"    //导入 fmt 包,用于打印字符串
    "time"   //导入 time 包,用于延时
)

func getOff(){
    //循环 5 次
    for i:= 5; i> 0; i--{
        fmt.Println("还有",i,"位乘客下车")
        //延时 1s
        time.Sleep(1 * time.Second)
    }
}

func main(){
    //执行并发程序
    go getOff() //循环 5 次
    for i:= 1; i< 6; i++{
        fmt.Println("第",i,"位乘客上车")
        //延时 1s
        time.Sleep(1 * time.Second)
    }
}
运行结果如下(不唯一):

第 1 位乘客上车
还有 5 位乘客下车
还有 4 位乘客下车
第 2 位乘客上车
第 3 位乘客上车
还有 3 位乘客下车
还有 2 位乘客下车
第 4 位乘客上车
第 5 位乘客上车
还有 1 位乘客下车

首先为 main() 函数创建一个协程,然后使用 go 关键字为 getOff() 函数创建另一个协程;Go 语言自动对这两个协程进行调度,以实现并发过程。

当使用 go 关键字创建协程时,因为忽略被调用函数的返回值,所以只考虑是否需要为被调用函数设置参数。

下例演示当创建协程时,如何为被调用函数设置参数。

【实例 2】确认上下车的乘客姓名。现有一辆只为上下车的乘客提供一个车门的公交车,这辆公交车行至某一站台后,有 5 位乘客下车,这 5 位乘客的姓名依次为“David”、“Leon”、“Steven”、“James”和“Tom”;有 5 位乘客上车,这 5 位乘客的姓名依次为“张三”、“李四”、“王五”、“赵六”和“周七”。不论是下车还是上车,乘客与乘客之间的时间间隔都是 1s。

编写 Go 程序,通过并发编程实现上述的上下车问题,并确认上下车的乘客姓名。代码如下:
package main //声明 main 包

import (
    "fmt"    //导入 fmt 包,用于打印字符串
    "time"   //导入 time 包,用于延时
)

func getOff(names []string){
    //遍历存储下车乘客的姓名的切片中的元素
    for i, name := range names {
        fmt.Println("第",i+1,"位乘客",name,"正在下车")
        //延时 1s
        time.Sleep(1 * time.Second)
    }
}

func main(){
    //存储下车乘客的姓名的切片
    var offNames = [5]string{"David","Leon","Steven","James","Tom"}
    //执行并发程序
    go getOff(offNames[:]) //存储上车乘客的姓名的切片
    var onNames = []string{"张三","李四","王五","赵六","周七"}
    //遍历存储上车乘客的姓名的切片中的元素
    for i, name := range onNames {
        fmt.Println("第",i+1,"位乘客",name,"正在上车")
        //延时 1s
        time.Sleep(1 * time.Second)
    }
}
运行结果如下(不唯一):

第 1 位乘客 张三 正在上车
第 1 位乘客 David 正在下车
第 2 位乘客 Leon 正在下车
第 2 位乘客 李四 正在上车
第 3 位乘客 王五 正在上车
第 3 位乘客 Steven 正在下车
第 4 位乘客 赵六 正在上车
第 4 位乘客 James 正在下车
第 5 位乘客 Tom 正在下车
第 5 位乘客 周七 正在上车

为匿名函数创建协程

在 Go 程序中,使用 go 关键字还可以为匿名函数创建协程。注意,go 关键字的后面须包含两个内容:一个是定义的匿名函数;另一个是匿名函数的调用参数。

使用 go 关键字为匿名函数创建协程的语法格式如下:
go func(parameter){
    func field
}(para)
参数说明如下:
【实例 3】演示为匿名函数创建协程。已知在使用左手画圆的同时,使用右手画方,并且圆和方的个数均为 3 个。定义一个用于实现“左手画圆”的匿名函数。编写 Go 程序,在为匿名函数创建协程后,并发执行程序,实现“左手画圆,右手画方”的效果,并记录圆和方的绘制过程。代码如下:
package main

import (
    "fmt"
    "time"
)

func main(){
    //为匿名函数创建协程
    go func(){
        //循环 3 次
        for i:= 0; i< 3; i++{
            fmt.Println("左手画圆")
            //延时 1s
            time.Sleep(1 * time.Second)
        }
    }() //匿名函数被调用时所需设置的参数
    //循环 3 次
    for i:= 0; i< 3; i++{
        fmt.Println("右手画方")
        //延时 1s
        time.Sleep(1 * time.Second)
    }
}
运行结果如下(不唯一):

右手画方
左手画圆
左手画圆
左手画圆
右手画方
右手画方
左手画圆


如果要求把圆绘制成红色,那么就需要使用有参数的匿名函数。这时,除了需要在 func 关键字后面的小括号内添加参数,还需要在匿名函数末端的小括号内为其设置被调用时所需的参数。修改上面的程序,“把圆绘制成红色”的代码如下:
package main

import (
    "fmt"
    "time"
)

func main(){
    //为匿名函数创建协程
    go func(color string){
        //循环 5 次
        for i:= 0; i < 3; i++ {
            fmt.Println("左手画圆,圆的颜色是" + color)
            //延时 1s
            time.Sleep(1 * time.Second)
        }
    }("红色")//匿名函数被调用时所需设置的参数
    //循环 5 次
    for i:= 0; i < 3; i++ {
        fmt.Println("右手画方")
        //延时 1s
        time.Sleep(1 * time.Second)
    }
}
运行结果如下(不唯一):

右手画方
左手画圆,圆的颜色是红色
左手画圆,圆的颜色是红色
左手画圆,圆的颜色是红色
右手画方
右手画方
左手画圆,圆的颜色是红色

相关文章