设为首页 加入收藏

TOP

goroutine调度(一)
2023-07-23 13:29:03 】 浏览:79
Tags:goroutine 调度

0.1、索引

https://blog.waterflow.link/articles/1662974432717

1、进程

一个进程包含可以由任何进程分配的公共资源。这些资源包括但不限于内存地址空间、文件句柄、设备和线程。

一个进程会包含下面一些属性:

  • Process ID:进程ID
  • Process State:进程状态
  • Process Priority:进程优先级
  • Program Counter:程序计数器
  • General purpose register:通用寄存器
  • List of open files:打开的文件列表
  • List of open devices:打开的设备列表
  • Protection information:保护信息
  • List of the child process:子进程列表
  • Pending alarms:待定警告
  • Signals and signal handlers:信号和信号处理程序
  • Accounting information:记账信息

2、线程

线程是轻量级的进程,一个线程将在进程内的所有线程之间共享进程的资源,如代码、数据、全局变量、文件和内存地址空间。但是栈和寄存器不会共享,每个线程都有自己的栈和寄存器

线程的优点:

  • 提高系统的吞吐量
  • 提高响应能力
  • 由于属性更少,上下文切换更快
  • 多核CPU的有效利用
  • 资源共享(代码、数据、地址空间、文件、全局变量)

3、用户级线程

用户级线程也称为绿色线程,如:C 中的coroutine、Go 中的 goroutine 和 Ruby 中的 Fiber
http://image-1313007945.cos.ap-nanjing.myqcloud.com/image/1662974591.png

该进程维护一个内存地址空间,处理文件,以及正在运行的应用程序的设备和线程。操作系统调度程序决定哪些线程将在任何给定的 CPU 上接收时间

因此,与耗时和资源密集型的进程创建相比,在一个进程中创建多个用户线程(goroutine)效率更高。

4、goroutine

在Go中用户级线程被称作Goroutine,在创建goroutine时需要做到:

  • 易于创建
  • 轻量级
  • 并发执行
  • 可扩展
  • 无限堆栈(最大堆栈大小在 64 位上为 1 GB,在 32 位上为 250 MB。)
  • 处理阻塞调用
  • 高效 (work stealing)

其中阻塞调用可能是下面一些原因:

  • 在channel中收发数据
  • 网络IO调用
  • 阻塞的系统调用
  • 计时器
  • 互斥操作(Mutex)

为什么go需要调度goroutine?

Go 使用称为 goroutine 的用户级线程,它比内核级线程更轻且更便宜。 例如,创建一个初始 goroutine 将占用 2KB 的堆栈大小,而内核级线程将占用 8KB 的堆栈大小。 还有,goroutine 比内核线程有更快的创建、销毁和上下文切换,所以 go 调度器 需要退出来调度 goroutine。OS 不能调度用户级线程,OS 只知道内核级线程。 Go 调度器 将 goroutine 多路复用到内核级线程,这些线程将在不同的 CPU 内核上运行

什么时候会调度goroutine?

如果有任何操作应该或将会影响 goroutine 的执行,比如 goroutine 的启动、等待执行和阻塞调用等……

go调度 如何将 goroutine 多路复用到内核线程中?

1、1:1调度(1个线程对应一个goroutine)

  • 并行执行(每个线程可以在不同的内核上运行)
  • 可以工作但是代价太高
  • 内存至少?32k(用户堆栈和内核堆栈的内存)
  • 性能问题(系统调用)
  • 没有无限堆栈

2、N:1调度(在单个内核线程上多路复用所有 goroutine)

  • 没有并行性(即使有更多 CPU 内核可用,也只能使用单个 CPU 内核)

我们看下下面的例子,只为go分配了1个processer去处理2个goroutine:

package main

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

func main() {
	// 分配 1 个逻辑处理器供调度程序使用
	runtime.GOMAXPROCS(1)
	var wg sync.WaitGroup
	wg.Add(2)

	fmt.Println("Starting Goroutines")

	// 开一个go协程打印字母
	go func() {
		defer wg.Done()
		time.Sleep(time.Second)
		// 打印3次字母
		for count := 0; count < 3; count++ {
			for ch := 'a'; ch < 'a'+26; ch++ {
				fmt.Printf("%c ", ch)
			}
			fmt.Println()
		}
	}()

	// 开一个go协程打印数字
	go func() {
		defer wg.Done()
		// 打印3次数字
		for count := 0; count < 3; count++ {
			for n := 1; n <= 26; n++ {
				fmt.Printf("%d ", n)
			}
			fmt.Println()
		}
	}()

	// 等待返回
	fmt.Println("Waiting To Finish")
	wg.Wait()
	fmt.Println("\nTerminating Program")
}

看下结果:

go run main.go
Starting Goroutines
Waiting To Finish
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 
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为1,但是在goroutine中我们在不同的时机加入阻塞goroutine的时间函数time.Sleep,我们看下会有什么不同的结果。

package main

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

func main() {
	// 分配 1 个逻辑处理器供调度程序使用
	runtime.GOMAXPROCS(1)
	var wg sync.WaitGroup
	wg.Add(2)

	fmt.Println("Starting Goroutines")

	// 开一个go协程打印字母
	go func() {
		defer wg.Done()
		time.Sleep(time.Secon
首页 上一页 1 2 3 4 下一页 尾页 1/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Golang GMP原理(2) 下一篇分享Go书籍-《Go Web编程》

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目