Go中(goroutine)并发与并行

336 阅读2分钟

简述

Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。

goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

go 函数名( 参数列表 )

例如:

go eat(x, y)

并发与并行

image.png

并行是让不同的代码片段同时在不同的物理处 理器上执行。并行的关键是同时做很多事情,

并发是指同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事情了。

调度器与逻辑处理器

操作系统会在物理处理器上调度线程来运行,而 Go 语言的运行时会在逻辑处理器上调度 goroutine来运行。每个逻辑处理器都分别绑定到单个操作系统线程。Go语言的运行时默认会为每个可用的物理处理器分配一个逻辑处理器

image.png

图6-2 可以看到操作系统线程、逻辑处理器和本地运行队列之间的关系。如果创建一 个 goroutine 并准备运行,这个 goroutine 就会被放到调度器的全局运行队列中。之后,调度器就将这些队列中的 goroutine 分配给一个逻辑处理器,并放到这个逻辑处理器对应的本地运行队列中。本地运行队列中的 goroutine 会一直等待直到自己被分配的逻辑处理器执行中。本地运行队列中的 goroutine 会一直等待直到自己被分配的逻辑处理器执行。

goroutine 并发

package main

import (
	"fmt"
	"runtime"
	"sync"
)

func main() {
	runtime.GOMAXPROCS(1) // 分配一个逻辑处理器给调度器使用

	var wg sync.WaitGroup
	wg.Add(2) // 计数加 2,表示要等待两个 goroutine

	fmt.Println("Start Goroutines")

	// 声明一个匿名函数,并创建一个 goroutine
	go func() {
		defer wg.Done() // 在函数退出时调用 Done 来通知 main 函数工作已经完成

		// 显示字母表 3 次
		for count := 0; count < 3; count++ {
			for char := 'a'; char < 'a'+26; char++ {
				fmt.Printf("%c ", char)
			}
		}
	}()

	// 声明一个匿名函数,并创建一个 goroutine
	go func() {
		defer wg.Done() // 在函数退出时调用 Done 来通知 main 函数工作已经完成

		// 显示字母表 3 次
		for count := 0; count < 3; count++ {
			for char := 'A'; char < 'A'+26; char++ {
				fmt.Printf("%c ", char)
			}
		}
	}()

	fmt.Println("Waiting To Finish")
	wg.Wait() // 等待 goroutine 结束

	fmt.Println("\nTerminating Program")
}

runtime.GOMAXPROCS(1) :这里十分关键,我们只分配一个逻辑处理器给调度器使用,也就是说我们只有一个人而要去干两份活

Start Goroutines
Waiting To Finish
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K
L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V
W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g
h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r
s t u v w x y z 
Terminating Program

第一个 goroutine 完成所有显示需要花时间太短了,以至于在调度器切换到第二个 goroutine 之前,就完成了所有任务。这也是为什么会看到先输出了所有的大写字母,之后才输出小写字母。

goroutine 实现并行

我们在上面那个例子进行简单的修改,就可让程序并行执行。

runtime.GOMAXPROCS(2) 分配 2 个逻辑处理器给调度器使用,意思就是找两个人一起来干活

修改后的执行结果:

Start Goroutines
Waiting To Finish
A B C a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f D E F G H I J K L M N
O P Q R S T U V W X Y g h i j k l m n o p q r s t u v w x y z a b c d Z A B C D E F G H I J
K L M N O e f g h i j k l m n o p q r s t u v w x y z P Q R S T U V W X Y Z A B C D E F G H 
I J K L M N O P Q R S T U V W X Y Z 
Terminating Program
Start Goroutines
Waiting To Finish
A a b c B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q
R S T d e f g U V W X Y Z A B C D E F G H I J K L M N O h i j k l m n o p q r s t u v w x y 
z a P Q R S T b c d e f g h i U V W X Y Z j k l m n o p q r s t u v w x y z a b c d e f g h 
i j k l m n o p q r s t u v w x y z 
Terminating Program

可以看到执行的结果大小写字母是交替出现的,同时每次的执行结果不一定相同!

runtime.GOMAXPROCS()

如果省略了 runtime.GOMAXPROCS(2) 这段代码,那么程序将使用默认值来分配逻辑处理器。在 Go 1.5 及其以后的版本中,如果没有设置 GOMAXPROCS 环境变量或调用 GOMAXPROCS 函数,那么程序会使用运行时可用的所有 CPU 核心。这意味着,默认情况下将使用多个逻辑处理器来执行 goroutine


参考书籍《Go语言实战》