JamesNan 2019-11-18
grape
原视频地址:https://biglive.xueersi.com/L...
Go语言最大的特色就是从语言层面支持并发(Goroutine),Goroutine是Go中最基本的执行单元。事实上每一个Go程序至少有一个Goroutine:主Goroutine。当程序启动时,它会自动创建。
首先了解什么是协程,什么是线程
协程:又称微线程与子例程(或者称为函数)一样,协程(coroutine)也是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。和线程类似,共享堆,不共享栈,协程的切换一般由程序员在代码中显式控制。它避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂。而Goroutine可以理解为一种Go语言的协程。同时它可以运行在一个或多个线程上。
线程:轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程的切换一般也由操作系统调度。
区别:协程是运行在线程上的,在代码中显示控制,没有上下文切换所消耗的资源,协程拥有栈空间来存储自己的上下文,如果栈空间不足,则会更改其他的协程。
Go实现了两种并发形式。第一种是大家普遍认知的:多线程共享内存。其实就是Java或者C++等语言中的多线程开发。另外一种是Go语言特有的,也是Go语言推荐的:CSP(communicating sequential processes)并发模型。
CSP并发模型:以通信的方式来共享内存,传统的多线程之间以共享内存来进行通信,GO语言则以通信的方式来共享内存,那么就来说一说这个CSP模型,它是通过goroutine和channel来实现的。
- goroutine 是Go语言中并发的执行单位。有点抽象,其实就是和传统概念上的”线程“类似,可以理解为”线程“。 - channel是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲,就是各个goroutine之间通信的”管道“,有点类似于Linux中的管道。
生成一个goroutine需要代码简简单单的go func()就可以生成,channel也是需要声明chan即可,同时,channel在使用时需要注意,在读和写的时候channel都是堵塞的,向管道里写或者读数据类似于流输入输出。
我们先从线程讲起,无论语言层面何种并发模型,到了操作系统层面,一定是以线程的形态存在的。而操作系统根据资源访问权限的不同,体系架构可分为用户空间和内核空间;内核空间主要操作访问CPU资源、I/O资源、内存资源等硬件资源,为上层应用程序提供最基本的基础资源,用户空间呢就是上层应用程序的固定活动空间,用户空间不可以直接访问资源,必须通过“系统调用”、“库函数”或“Shell脚本”来调用内核空间提供的资源。
我们现在的计算机语言,可以狭义的认为是一种“软件”,它们中所谓的“线程”,往往是用户态的线程,和操作系统本身内核态的线程(简称KSE),还是有区别的。也就是说无论你怎么去实现并发,其实根本上都是去调用内核态的线程,一切并发都是纸老虎~
线程模型的实现,可以分为以下几种方式:1.用户级线程模型,2.内核级线程模型,3.两级线程模型。而我们的GO线程模型就是一种特殊的两级线程模型(GPM调度模型)。
如果G被阻塞在某个system call操作上,那么不光G会阻塞,执行该G的M也会解绑P(实质是被sysmon抢走了),与G一起进入sleep状态。如果此时有idle的M,则P与其绑定继续执行其他G;如果没有idle M,但仍然有其他G要去执行,那么就会创建一个新M。