由goroutine引出的问题

概念

进程

  • 操作系统为了执行程序,给他分配文本域、数据区域和堆栈,让程序活动起来,活动的程序可以叫做进程。
  • 文本区域存储处理器执行的代码。
  • 数据区域存储变量和进程执行期间使用的动态分配的内存。
  • 堆栈区域存储着活动过程调用的指令和本地变量。

线程

  • 线程是操作系统能够进行运算调度的最小单位,是进程中的实际运作单位。
  • 一个进程中可以并发多个线程,每条线程并行执行不同的任务。
  • 同一进程中的多条线程将共享该进程中的全部系统资源,但同一进程中的多个线程有各自的调用栈,自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
  • 线程间通信主要通过共享内存,上下文切换很快,资源开销较少。

协程

  • 协程不是由操作系统管理,而是完全由程序控制(代码怎么写的)
  • 一个线程可以包含多个协程,但这些协程一定是串行的。
  • 一个协程实际上是一个函数,这个函数可以在某个地方挂起,并且可以重新在挂起处外继续运行。
  • 协程切换是由代码控制的,不会陷入内核态,所以效率高。

进程与线程关系

  • 一个进程至少包含一个线程,如果只包含一个线程,那么所有代码只能串行。
  • 每个进程的第一个线程会随着该进程的启动而被创建,可以被称为其所属进程的主线程。
  • 如果一个进程包含了多个线程,其中的代码就可以被并发的执行。除了进程的第一个线程之外,其他的线程都是由进程中已存在的线程创建的。即主线程之外的其他线程只能由代码显式地创建和销毁。

Go语言中的场景

  • 在Go程序,Go的运行时系统会帮助我们自动地创建和销毁系统级线程(可以理解为Go已经帮你把创建和销毁线程的代码写了)
  • 用户级线程指的是架设系统级线程之上的,由编写的程序完全控制的代码执行流程,用户级线程的创建、销毁、调度完全需要我们自己实现。(协程)
  • Go语言不但有着独特的并发编程模型(通过通信来控制),以及用户级线程goroutine,还拥有强大的用于调度goroutine,对接系统级线程的调度器。

调度器

  • Go语言调度器是运行时系统的重要组成部分,主要负责管理并发编程模型中的三个主要元素:G(goroutine)、P(processor)和M(machine)。
  • 其中的M指代的就是系统级线程,P指的是能够承载多个G,使G适时地与M对接,并得到真正运行的中介
  • 由于P的存在,G和M是多对多的关系。当一个正在与某个M对接并运行着的G,需要暂停(比如等待I/O或锁的解除),调度器会把G和M分开,让排队的其他G上。
  • 当一个G需要恢复运行,调度器会给他找空闲的计算资源(包括M),M不够用了,调度器会向操作系统申请新的M。当某个M没用了,调度器又会申请销毁他。

goroutine

  • Go语言的入口是main函数,主goroutine的函数就是这个main函数。
  • go函数(go关键字后面的函数)真正执行时间,总会和其所属go func语句被执行时间不同。即声明完了不会立马执行。
  • 程序执行到go func语句,Go运行时系统会先试图从空闲G的队列中拿一个(复用),如果没有空闲的,就去创建一个(创建成本很低,只需要上下文环境)。
  • 拿到G后,运行时系统用这个G去包装func中的代码,再把这个G追加到存放可运行G的队列中,由调度器安排。
  • 主goroutine中的代码执行完了,Go程序结束,其他goroutine再也不会运行。

package main

import "fmt"

func main() {
  for i := 0; i < 10; i++ {
    go func() {
      fmt.Println(i)
    }()
  }
}
  • 上面这个代码,绝大多数情况什么都不打印,即主goroutine结束,其他goroutine未运行。
  • 如果在末尾加上等待,打印出的i不再是声明时的i,即有可能是10个10,也有可能有其他数。
  • 如果将i作为参数传到func中,则会打印0-9,但是顺序无法保证。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值