golang之goroutine

一、进程和线程说明

  1. 进程就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位
  2. 线程是进程的一个执行实例,是程序执行的最小单位,他是比进程更小的能独立运行的基本单位
  3. 一个进程可以创建和销毁多个线程,同一个进程中的多个线程可以并发执行
  4. 一个程序至少有一个进程,一个进程至少有一个线程

二、并发和并行

  1. 多线程程序在单核上运行,就是并发(多任务作用在一个cpu上,在一个时间点上只有一个任务在执行)
  2. 多线程程序在多核上运行,就是并行(多任务作用在多个cpu上,在一个时间点上有多个任务在同时执行)

三、Go协程和Go主线程

  1. Go主线程(有程序直接称为线程/也可以理解成进程),一个Go线程上,可以起多个协程,可以这样理解,协程是轻量级的线程(编译器做优化)
  2. Go协程的特点
  • 有独立的栈空间
  • 共享程序堆空间
  • 调度由用户控制
  • 协程是轻量级线程

例子:

package main

import (
	"fmt"
	"time"
)

func test() {
	for i := 1; i <= 10; i++ {
		fmt.Println("test() hello world!")
		time.Sleep(time.Second)
	}
}

// 在主线程中开启一个gotoutine,该协程每隔1秒输出"hello world!"
// 在主线程中也每隔1秒输出"hello golang!",输出10次后,退出程序
// 要求主线程和goroutine同时协作
func main() {
	go test()
	for i := 1; i <= 10; i++ {
		fmt.Println("main() hello golang!")
		time.Sleep(time.Second)
	}
}

输出结果:

hello golang!
hello world!
hello world!
hello golang!
hello golang!
hello world!
hello world!
hello golang!
hello golang!
hello world!
hello world!
hello golang!
hello golang!
hello world!
hello world!
hello golang!
hello golang!
hello world!
hello world!
hello golang!

主线程和协程执行流程图
image

小结:

  1. 主线程是一个物理级的,直接作用在cpu上,是重量级的,非常耗费cpu资源
  2. 协程是从主线程开启的,是轻量级的线程,是逻辑态,对资源消耗相对小
  3. Golang的协程机制是重要的特点,可以轻松的开启千万个协程,其他编程语言的并发机制是一般基于线程的,开启过多的线程,资源消耗大,这里就突出了Golang在并发上的优势了。

四、goroutine的调度模型

  1. MPG模式基本介绍

image

  • M:操作系统主线程(是物理线程),代表着一个内核线程,也可以称为一个工作线程。goroutine就是跑在M之上的
  • P:上下文环境,代表着(Processor)处理器 它的主要用途就是用来执行goroutine的,一个P代表执行一个Go代码片段的基础(可以理解为上下文环境),所以它也维护了一个可运行的goroutine队列,和自由的goroutine队列,里面存储了所有需要它来执行的goroutine。
  • G:协程,代表着goroutine 实际的数据结构(就是你封装的那个方法),并维护者goroutine 需要的栈、程序计数器以及它所在的M等信息
  1. MPG运行状态1

image

  • 当前程序有是三个M,如果三个M都在一个CPU上运行,就是并发,如果在不同的CPU上运行就是并行
  • M1,M2,M3正在执行一个G,M1的协程队列有三个,M2的协程队列有三个,M3的协程队列有两个
  • 从上图可以看到:Go的协程是轻量级的线程,是逻辑态,Go可以容易的起上上万个协程
  • 其他程序程C/JAVA的多线程,往往是内核态的,比较重量级,几千个线程可能就耗光CPU
  1. MPG运行状态2

image

  • 分成两部分来看
  • 原来的情况是M0主线程正在执行G0协程,另外有三个协程在队列等待
  • 如果G0协程阻塞,比如读取文件或者数据库等
  • 这时就会创建M1主线程(也可能从已有的线程池取出M1),并且等待的3个协程挂到M1下开始执行,M0的主线程下的G0任然执行文件io读写
  • 这样的MPG调度模式,可以既让G0执行,同时也不会让队列的其他协程一直阻塞,任然可以并发/并行执行
  • 等到G0不阻塞了,M0会被放到空闲的主线程继续执行(从已有的线程池中取),同时G0又会被唤醒。

五、设置Golang运行的cpu数

为了充分的利用cpu的优势,在Golang程序中,设置运行的cpu数目

  • go1.8之后,默认让程序运行在多个核上,可以不用设置了
  • go1.8前,还是要设置一下,可以更高效的利用cpu

例子:

package main

import (
	"fmt"
	"runtime"
)

func main() {
	// func NumCPU() int
	// NumCPU返回本地机器的逻辑CPU个数。
	cpuNum := runtime.NumCPU()
	fmt.Println("cpuNum=", cpuNum)
	// func GOMAXPROCS(n int) int
	// GOMAXPROCS设置可同时执行的最大CPU数,并返回先前的设置。
	// 若 n < 1,它就不会更改当前设置。
	befCpuNum := runtime.GOMAXPROCS(cpuNum - 1)
	fmt.Println("befCpuNum=", befCpuNum)
}

输出结果:

cpuNum= 4
befCpuNum= 4
posted @ 2021-06-09 22:10  若雨蚂蚱  阅读(188)  评论(0编辑  收藏  举报