go语言的成神之路-筑基篇-并发

目录

一、go协程

二、GMP

示例:

[GMP 的工作流程:](#GMP 的工作流程:)

[使用 GMP 的好处:](#使用 GMP 的好处:)

注意事项:

三、runtime包

Gosched

Goexit

调用Goexit之前

调用Goexit之前运行的结果

调用Goexit之后

调用Goexit之后运行的结果

GOMAXPROCS

执行时间不同的原因:

思考:更改参数n的值会使执行时间相同吗?

修改方案

部分代码解析


一、go协程

在 Go 语言中,使用 goroutine 来实现并发。goroutine 是一种轻量级的线程,可以通过 go 关键字来启动。

Go 复制代码
package main

import (
	"fmt"
	"time"
)

func hello() {
	fmt.Println("hello goroutine")
}
func main() {
	go hello()
	fmt.Println("main goroutine")
	time.Sleep(time.Second)
}
  • func main() {...}:这是 Go 程序的入口函数,程序从这里开始执行。
  • go hello():使用 go 关键字启动一个新的 goroutine 来调用 hello 函数。goroutine 是 Go 语言中的轻量级线程,它允许并发执行代码。当执行到这一行时,会创建一个新的执行流,该执行流将调用 hello 函数,而不会阻塞当前的执行流程。
  • fmt.Println("main goroutine"):在主 goroutine 中打印出 "main goroutine" 字符串。
  • time.Sleep(time.Second):让主 goroutine 暂停执行一秒钟。这是为了确保程序不会立即退出,因为新启动的 goroutine 可能还没有机会执行完 hello 函数。如果没有这行代码,程序可能会在 hello 函数执行之前就结束,因为主 goroutine 会直接结束,导致整个程序终止。
Go 复制代码
package main

import (
	"fmt"
	"time"
)

func hello(i int) {
	fmt.Println("hello goroutine", i)
}
func main() {
	for i := 0; i < 10; i++ {
		go hello(i)
	}
	fmt.Println("main goroutine")
	time.Sleep(time.Second)
}

仔细观察一下不难看出,这两次输出的结果都不相同,原因如下:

在 Go 语言中,goroutine 的执行顺序是不确定的。当你使用 go 关键字启动多个 goroutine 时,它们会被调度到 Go 的运行时环境中并发执行。

以下是导致打印顺序不同的原因:

  • 调度器的随机性 :Go 的调度器会根据系统资源和其他因素来决定 goroutine 的执行顺序。不同的 goroutine 可能在不同的系统线程上执行,并且它们的执行顺序取决于调度器的决策,而不是它们被创建的顺序。
  • 并发执行 :由于 goroutine 是并发执行的,它们可能在不同的时间点开始和完成,这取决于系统的负载、CPU 核心的可用性等因素。

思考题

二、GMP

  • G (Goroutines):Goroutines 是 Go 语言中的轻量级线程,它们是并发执行的基本单元。可以使用 go 关键字启动一个新的 Goroutine,例如 go func() {...}()。Goroutines 非常轻量,可以轻松创建大量的 Goroutines,并且它们由 Go 的运行时系统管理。
  • M (Workers):M 代表工作线程,它们是操作系统线程,由 Go 的运行时系统创建和管理。M 负责执行 Goroutines,一个 M 可以执行多个 Goroutines。
  • P (Processors):P 是逻辑处理器,它是一个抽象的概念,代表执行上下文。每个 M 都需要绑定到一个 P 上才能执行 Goroutines。P 维护一个本地的 Goroutine 队列,当一个 Goroutine 被创建时,它会被放入一个 P 的队列中等待执行。

示例:

Go 复制代码
package main

import (
	"fmt"
	"sync"
	"time"
)

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Printf("Worker %d starting\n", id)
	time.Sleep(time.Second)
	fmt.Printf("Worker %d done\n", id)
}

func main() {
	var wg sync.WaitGroup
	// 启动多个 goroutines
	for i := 1; i <= 5; i++ {
		wg.Add(1)
		go worker(i, &wg)
	}
	// 等待所有 goroutines to complete
	wg.Wait()
}
  • var wg sync.WaitGroup:创建一个 sync.WaitGroup 实例,用于等待一组 Goroutines 完成。
  • wg.Add(1):在启动每个 Goroutine 之前,调用 wg.Add(1) 来增加等待组的计数,表示有一个新的 Goroutine 要等待。
  • go worker(i, &wg):启动一个新的 Goroutine 来执行 worker 函数,并将 wg 的指针传递给它。
  • defer wg.Done():在 worker 函数中,使用 defer wg.Done() 来通知 WaitGroup 该 Goroutine 已经完成。
  • wg.Wait():在主函数中,调用 wg.Wait() 会阻塞,直到 WaitGroup 的计数为 0,即所有 Goroutines 都已完成。

GMP 的工作流程

  1. 当你启动一个新的 Goroutine 时,它会被放入一个 P 的本地队列中。
  2. M 从 P 的队列中取出 Goroutines 并执行它们。
  3. 如果一个 P 的队列是空的,M 可以从全局队列中获取 Goroutines 或者从其他 P 的队列中窃取 Goroutines(work stealing)。
  4. 当一个 Goroutine 执行 I/O 操作或阻塞时,M 可以切换到另一个 Goroutine 执行,提高并发性能。

使用 GMP 的好处

  • 高效的并发:Go 的 GMP 模型允许高效地创建和管理大量的并发任务,而不会像传统线程那样消耗大量的系统资源。
  • 自动调度:Go 的运行时系统自动调度 Goroutines,根据系统资源和任务的需求进行优化,提高性能。

注意事项

  • 避免 Goroutine 泄漏:确保 Goroutines 最终会结束,否则可能会导致资源泄漏。
  • 避免过度创建 Goroutines:虽然 Goroutines 很轻量,但创建过多可能会导致性能问题,尤其是在资源有限的系统上。

三、runtime包

Gosched

Go 复制代码
package main

import (
	"fmt"
	"runtime"
)

func main() {
	// 启动一个匿名 goroutine,接收一个字符串参数 s
	go func(s string) {
		// 循环两次
		for i := 0; i < 2; i++ {
			// 打印传入的字符串 s
			fmt.Println(s)
		}
	}("hello")

	// 主 goroutine
	for i := 0; i < 2; i++ {
		// 让出时间片,让其他 goroutine 有机会执行
		runtime.Gosched()
		// 打印主 goroutine 信息
		fmt.Println("main goroutine")
	}
}

runtime.Gosched():这个函数调用会使当前 goroutine(在这种情况下是主 goroutine)让出处理器,允许其他 goroutine 运行。它放弃自己的时间片并重新进入可运行的 goroutine 队列,以便其他 goroutine 有机会执行。

Goexit

  • runtime.Goexit() 是 Go 语言 runtime 包中的一个函数,它的主要作用是立即终止当前正在执行的 goroutine,而不会影响其他 goroutine 的执行。
  • runtime.Goexit() 被调用时,它会执行当前 goroutine 中已注册的 defer 函数,然后终止该 goroutine,但不会执行 goroutineruntime.Goexit() 之后的代码。

调用Goexit之前

调用Goexit之前运行的结果

调用Goexit之后

调用Goexit之后运行的结果

GOMAXPROCS

  • runtime.GOMAXPROCS(n int) 是 Go 语言 runtime 包中的一个函数,它用于设置可以同时执行的最大 CPU 核心数,也就是可以同时运行的 goroutine 的最大数量。
  • 参数 n 表示要使用的 CPU 核心数。如果 n 小于 1,则表示不限制 CPU 核心数,Go 运行时会根据系统的 CPU 核心数自动调整。

可以看出当参数为1的时候执行的时间不同 ,那么原因是什么?

首先对代码部分进行简单分析

Go 复制代码
package main

import (
	"fmt"
	"runtime"
	"time"
)

func a() {
	for i := 0; i < 5; i++ {
		fmt.Println("A: ", i)
	}
	fmt.Println(time.Now())
}
func b() {
	for i := 0; i < 5; i++ {
		fmt.Println("B: ", i)
	}
	fmt.Println(time.Now())
}
func main() {
	runtime.GOMAXPROCS(1)
	go a()
	go b()
	time.Sleep(time.Second)
}
  • runtime.GOMAXPROCS(1):将最大的可同时执行的 goroutine 数量设置为 1。这意味着在任何给定的时间,只有一个 goroutine 会在 CPU 上执行,其他 goroutine 会等待。
  • go a()go b():启动两个 goroutine,分别执行函数 ab
  • time.Sleep(time.Second):让主 goroutine 暂停执行一秒钟,以等待 ab 两个 goroutine 有机会执行。

执行时间不同的原因:

  • 尽管 runtime.GOMAXPROCS(1) 限制了并发执行的 goroutine 数量为 1,但 goroutine 的调度顺序仍然是不确定的。
  • ab 两个 goroutine 被创建后,它们会被放入 goroutine 队列中等待调度。由于 goroutine 的调度是由 Go 的运行时系统决定的,所以 ab 哪个先被执行是不确定的。
  • 即使 a 先被调度,它可能会在执行过程中被暂停,然后 b 被调度,或者反之。这取决于 Go 运行时的调度决策,包括当前系统的负载、其他 goroutine 的状态等因素。

思考:更改参数n的值会使执行时间相同吗?

  • runtime.GOMAXPROCS(2) 会允许最多 2 个 goroutine 同时运行,但这并不保证 ab 函数的执行顺序会固定。
  • 即使有 2 个 CPU 核心可用,Go 的运行时调度器仍然会根据各种因素(如系统负载、其他 goroutine 的状态等)来决定哪个 goroutine 先执行,以及它们的执行顺序。
  • 每个 goroutine 的执行时间可能会受到系统资源分配、操作系统调度、其他进程的影响,因此即使有更多的 CPU 资源,也不能保证 ab 的执行顺序和时间是固定的。

修改方案

可以通过管道来同步时间

Go 复制代码
package main

import (
	"fmt"
	"time"
)

func a(ch chan bool) {
	for i := 0; i < 5; i++ {
		fmt.Println("A: ", i)
	}
	fmt.Println(time.Now())
	ch <- true
}

func b(ch chan bool) {
	<-ch
	for i := 0; i < 5; i++ {
		fmt.Println("B: ", i)
	}
	fmt.Println(time.Now())
}

func main() {
	ch := make(chan bool)
	go a(ch)
	go b(ch)
	time.Sleep(time.Second)
}

部分代码解析

  • ch := make(chan bool):创建一个布尔类型的通道。
  • func a(ch chan bool):函数 a 执行完后会向通道 ch 发送一个 true 值。
  • func b(ch chan bool):函数 b 会从通道 ch 接收一个值,这会阻塞 b 的执行,直到 a 发送一个值到通道。这样可以确保 ba 完成后开始执行。
相关推荐
啦啦右一6 分钟前
Spring Boot | (一)Spring开发环境构建
spring boot·后端·spring
森屿Serien7 分钟前
Spring Boot常用注解
java·spring boot·后端
量子-Alex32 分钟前
【多模态聚类】用于无标记视频自监督学习的多模态聚类网络
学习·音视频·聚类
轻口味34 分钟前
命名空间与模块化概述
开发语言·前端·javascript
吉大一菜鸡37 分钟前
FPGA学习(基于小梅哥Xilinx FPGA)学习笔记
笔记·学习·fpga开发
晓纪同学1 小时前
QT-简单视觉框架代码
开发语言·qt
威桑1 小时前
Qt SizePolicy详解:minimum 与 minimumExpanding 的区别
开发语言·qt·扩张策略
飞飞-躺着更舒服2 小时前
【QT】实现电子飞行显示器(简易版)
开发语言·qt
明月看潮生2 小时前
青少年编程与数学 02-004 Go语言Web编程 16课题、并发编程
开发语言·青少年编程·并发编程·编程与数学·goweb
明月看潮生2 小时前
青少年编程与数学 02-004 Go语言Web编程 17课题、静态文件
开发语言·青少年编程·编程与数学·goweb