go语言学习笔记-基础篇

一、go语言

1、go支持语言级的并发,并发的最小逻辑单位是goroutine,goroutine就是go为了实现并发提供的用户态线程这 种用户态线程是运行在内核态线程(OS线程)之上,当我们创建大量的goroutine并且同时运行在一个或多个内核态线程上时(内核线程与goroutine是m:n的对应关系),就需要一个调度器来维护管理这些goroutine,确保所有goroutine的都有相对公平的机会使用CPU

2、goroutine和内核OS线程的映射关系是m:n,这样多个goroutine就可以在多个内核线程上运行,goroutine的

切换大部分的场景下都没有走OS线程的切换所带来的开销,这样整个运行效率相比OS线程的调度会高很多,但随之带来的问题就是goroutine的调度模型非常复杂

二、调度模型scheduler

1、go的调度模型主要有几个主要的实体:G M P schedt

1.1、G:代表一个goroutine实体,他有自己的栈内存,instruction和pointer和一些相关信息(比如等待的

channel等等),是用于调度器调度的实体

1.2、M:代表一个真正的内核OS线程,和POSIX里的thread查不多,属于真正执行指令的人

1.3、P:代表M调度的上下文,可以吧他看做一个局部的调度器,调度协程go代码在一个内核线程上跑。P是实现协程与内核线程的m:n映射关系的关键,P的上限是通过系统变量runtime.GOMAXPROCS (numLogicalProcessors)来控制的。go启动是更新这一个值,一般不修改,P的数量也代表了go代码执行的并发度,即可以并发多少个goroutine

1.4、schedt:runtime全局调度是使用的数据结构,这其实是个壳,里面主要有M的全局idle队列,P的全局idle队列,一个全局的就绪的G队列以及一个runtime全局调度器级别的锁。当对M或P等做一些费局部调度器的操作是,一般需要先锁住全局调度器

2、我们通过 go func()来创建一个goroutine;

3、有两个存储goroutine的队列,一个是局部调度器P的local queue、一个是全局调度器数据模型schedt的global queue。新创建的goroutine会先保存在local queue,如果local queue已经满了就会保存在全局的global queue;

4、goroutine只能运行在M中,一个M必须持有一个P,M与P是1:1的关系。M会从P的local queue弹出一个Runable状态的goroutine来执行,如果P的local queue为空,就会执行work stealing;

5、一个M调度goroutine执行的过程是一个loop;

6、当M执行某一个goroutine时候如果发生了syscall或则其余阻塞操作,M会阻塞,如果当前有一些G在执行,runtime会把这个线程M从P中摘除(detach),然后再创建一个新的操作系统的线程(如果有空闲的线程可用就复用空闲线程)来服务于这个P;

7、当M系统调用结束时候,这个goroutine会尝试获取一个空闲的P执行,并放入到这个P的local queue。如果获取不到P,那么这个线程M会park它自己(休眠), 加入到空闲线程中,然后这个goroutine会被放入schedt的global queue。

8、M本身是无状态的,M是否是空闲态仅根据他是否存在于调度器的空闲M列表

9、runtime管辖的M会在GC任务执行的时候被停止,这个时候系统会对M的属性做某些必要的重置,并吧M放置入全局调度器的空闲M列表因为调度器在需要一个未被使用的M时,运行是系统会先去这个空闲列表获取M,只有都没有时才会创建M。注意:空闲列表不是那个全局列表

10、P是使G能够在M中运行的关键,GO的runtime适当地让P与不同的M建立或者断开联系,以似得P中的那些可运行的G能够在需要的时候获得运行时机

11、每个P中都有一个可运行G队列及自由G队列。自由G队列包含了很多已经完成的G,随着被运行完成的G的积攒到一定程度后,runtime会把其中的部分G转移到全局调度器的自由G队列

12、G需要绑定在M上才能运行,M需要绑定P才能运行

三、用户级协程Goroutine

0、foroutine类似与线程,但并非线程。可以理解成虚拟线程,go运行是会参与调度goroutine,并将goroutine合理分配到每个CPU中,最大限度地使用CPU的性能

1、go语言的并发是基于goroutine

2、goroutine是建立在线程之上的轻量级抽象。他允许我们以非诚低的代价在同一个地址空间中并行执行多个函数或方法相比于线程,他的创建和销毁的代价要小很多,并且他的调度 是独立于线程的,在go中创建一个goroutine非常简单使用“go”关键子即可

3、注意:和线程一样,go的主函数其实也是跑在一个goroutine中,并不会等待其他goroutine结束,如果主goroutine结束了,所有其他goroutine都将结束

四、goroutine和线程的区别

1、goroutine相比与线程运行不会更快,他只是增加了更多的并发性,当一个goroutine被阻塞(比如IO等待),go的scheduler会调度其他可执行的goroutine运行

2、相较于线程来说,goroutine有一下一些优点

goroutine所需要的内存通常只有2kb,而线程则需1MB

创建与销毁开销更小

切换开销更小,这也是goroutine能实现高并发的主要原因,线程的调度方式是抢占式的,如果一个线程的执行时间超过了分配给他的时间,就会被其他可执行的线程抢占,在切换过程中需要保存/恢复所有寄存器的信息,比如16个通用寄存器。而goroutine的调度是协同模式的,他不直接与操作系统内核打交道,当goroutine进行切换的时候,只有很少量的寄存器需要保存和恢复

3、goroutine的调度方式是协同模式的,在协同调度中没有时间片的概念,为了并行执行goroutine,调度器会在已下几个时间段进行切换

channel接受或这发送会造成阻塞的消息

当一个新的goroutine被创建时

可以造成阻塞的系统调用,如文件和网络操作

垃圾回收

五、通信管道channel


1、goroutine运行在相同的地址空间,因此访问共享内存必须做同步,goroutine通过通信来共享内存,而不是共享内存来通信

2、channel是一个对应make创建的底层数据结构的引用,当我们复制一个channel用于函数参数传递时,我们只是拷贝了一个channel

引用,因此调用者和被调用者将引用同一个channel对象

3、定义一个channel时,也需要定义发送到channel的值的类型,channel可以使用内置的make函数来创建

4、channel分两类,一非缓冲类型,即channel无法缓冲元素,发送一个必须接收一个。如果连续发送两个

第二次一定会被阻塞,对于缓冲类型怎要宽松些

5、有关channel的关闭

关闭一个未初始化的channel会产生panic

重复关闭同一个channel会产生panic

向一个已关闭的channel中发送消息会产生panic

从已关闭的channel读取消息不会产生panic,且能独处channel中还未被读取的消息,

若消息均已读出。则会读到类型的0值。从一个已经关闭的channel中读取消息永远

不会阻塞,并且会返回一个为false的ok-idiom,可以判断channel是否关闭

关闭channel会产生一个广播机制,所有向channel读取消息的goroutine都会收到消息

六、依赖管理

1、godep是go语言官方提供的管理工具

2、安装godep工具:go get github.com/tools/godep 下载成功后会在GOPATH的bin目录下生成godep.exe可执行文件,需配置环境变量

3、如何使用:godep help [命令名称]

命令 作用

godep save 将依赖包的信息保存到 Godeps.json 文件中

godep go 使用保存的依赖项运行 go 工具

godep get 下载并安装指定的包

godep path 打印依赖的 GOPATH 路径

godep restore 在 GOPATH 中拉取依赖的版本

godep update 更新选定的包或 go 版本

godep diff 显示当前和以前保存的依赖项集之间的差异

godep version 查看版本信息

4、GOPROXY代理服务配置,翻墙下载依赖包

5、代理服务地址:goproxy.io goproxy.cn

Windows配置:go env -w GOPROXY=https://goproxy.cn,direct

macos或Linux:export GOPROXY=https://goproxy.cn

6、使用go get命令下载指定版本的依赖包

执行go get 命令,在下载依赖包的同时还可以指定依赖包的版本。

运行go get -u命令会将项目中的包升级到最新的次要版本或者修订版本;

运行go get -u=patch命令会将项目中的包升级到最新的修订版本;

运行go get [包名]@[版本号]命令会下载对应包的指定版本或者将对应包升级到指定的版本。

七、编译和运行

1、go build 命令可以将GO语言程序代码编译成二进制的可执行文件,但需要我们手动运行该二进制文件

2、go run 命令则是在编译后会直接运行go语言程序,编译过程中会产生一个临时文件,但不会生成可执行文件

很适合用来调试程序

八、Goland安装

http://c.biancheng.net/view/6124.html


分享到:


相關文章: