Go语言进阶

依赖管理

包管理

注意:一个包对应一个目录,一个目录下不同文件之间可以相互公开,只有不同的包之间需要大写使其公开。
使用import之后编译运行一个文件就会连带着编译其import的包

GOPATH

一个目录包括三个子目录
  • src,不同的包的源代码
  • pkg,编译后的类库
  • bin,编译后的可执行程序
go install 命令将编译之后的结果文件:.a应用包或可执行文件导入到pkg或bin中
go get 命令将源码直接导入src中然后执行 go install

Go Modules

gopath改进之后通过go.mod文件进行包的管理
  • *通过命令 **go mod init [module name] 生成go.mod文件
go.mod文件样例:
  • *可以通过 **go mod tidy命令进行依赖的更新
最终下载的第三方库保存在GOMODCACHE中,即GOPATH/pkg/mod中
notion image

反射基础

一般不会自己写反射代码,理解原理即可
提供运行时对代码的访问和修改的能力
两个概念
  • Type:主要表示被反射变量的类型信息。
  • Value:表示被反射变量的类型信息
go语言反射的实现主要位于reflect包中

reflect.Type类型对象

  • *通过 **typeofname := reflect.Typeof(type name)方法获取一个变量的类型信息,存到一个reflect.Type的类型的变量中。
通过typeofname.kind()获得type name的种类(struct 等等)而typeofname是指哪个包里定义的类型packagename.typename

类型对象reflect.StructField和reflect.Method

StructField

reflect中存在structfield类型用于存储:结构体内字段的类型信息
以上述通过typeof(type name)方法获得的一个结构体类型对象为接收器可以使用以下三个方法获得该结构体对象下属的字段的structfield类型信息:
StructField中的内容

Method

reflect中还存在Method类型用于存储:接口下方法的方法类型对象
go语言中可以声明一个接口变量,并赋值以接收器:var person Person = &Hero{},其中Person是一个接口,Hero是一个结构体。
  • *通过相同的方法 **typeOfPerson := reflect.Typeof(person)可以获得一个接口Person的类型对象。
而以接口类型对象为接收器存在以下方法来获取Method类型对象
Method内的基本信息
Method.Type为func(接收器,剩余的原方法参数)

reflect.Value反射值对象

使用上述的Type类型对象,我们只能查看类型信息,不能对变量的值进行查看和修改,所以有了reflect.Value反射值对象。
可以通过reflect.ValueOf获取反射变量的信息Value,通过Value对变量的值进行查看和修改。

获取变量值

通过Value.Interface()方法获取变量的值
可以使用reflect.New(类型对象)创建一个相同类型的新变量,值以Value对象的形式返回。

改变变量值

对变量的修改可以通过方法Value.Set方法实现
例子如下:
注意:由于Value的值是原变量值的拷贝,即使ValueOf(&name),也只是指向name指针的拷贝,要寻址到原变量要使用#Elem方法。
可以通过方法#CanAddr方法判断是否可以寻址

结构体value

对于结构体的反射值对象,可以通过Value.CanSet判断某字段是否公开可以改变,结构体的value同时也有类似type的一些方法:#NumField,#FieldByIndex,#FieldByName来获取字段的value

反射接口方法调用

  • *使用函数 **func (v value) Call(in []value) []Value
也可以使用上边type中的Method.func调用,但要将接收器作为第一个参数传入

并发模型

并发与并行

  • 并发:同一时间段内,多条指令在CPU上同时执行
  • 并行:同一时刻上,多条指令在CPU上同时执行

CSP并发模型

go语言实现两种并发模型
  • 线程与锁并发模型:依赖于共享内存,程序出错不易排查
  • CSP(通信顺序进程模型):有两个关键概念
    • 并发实体:即执行线程,他们之间相互独立并发执行
    • 通道:并发实体之间使用通道发送信息
极易导致死锁

常见线程模型

线程是操作系统能够调度的最小单位分为用户线程和内核线程
  • 用户线程:由用户空间的代码创建、管理和销毁,调度由用户空间的线程库完成
  • 内核线程:由操作系统管理和调度,线程切换需要cpu切换为内核态
用户线程无法被操作系统感知,用户线程所属的进程或者内核线程才能被操作系统直接调度。
  1. 用户级线程模型
一个进程包含多个用户线程,对应一个内核线程
缺点:一个用户线程阻塞导致整个进程失去时间片
  1. 内核级线程模型
每个用户线程对应一个内核线程
缺点:线程切换从用户态到内核态资源消耗大
  1. 两级线程模型
上述两个模型相结合:一个进程对应多个内核线程,由进程中的调度器决定进程内的线程如何与申请的内核线程相对应。

GMP线程模型

go语言中的MPG线程模型对两级线程模型进行改进:
  • M:Machine,一个Machine对应一个内核线程,相当于内核线程在go语言进程中的映射
  • P:Processor,go代码片段执行所需的上下文环境,用户代码逻辑处理器
  • G:Goroutine,go代码片段的封装,是一种轻量级的用户线程
notion image
​​
notion image
如上图:M,P共同构成了一个基本的运行环境,此时G0中的代码处于运行的状态,右边G队列处于待执行的状态。
当没有足够的M来和P组合为G提供运行环境时,Go语言会创建新的M,在很多时候M的数量可能比P多。在单个Go语言进程中,P的最大数量决定了程序的并发规模,且P的最大数量由程序决定,可以通过修改环境变量GOMAXPROCS和调用函数runtime.GOMAXPROCS来设定P的最大值。

并发实践

协程goroutine

  • *goroutine是go语言中的轻量级进程,在运行的时候由runtine管理,我们编写main函数也是运行在goroutine之上,可以通过 **go 表达式语句 来启动一个新的goroutine
表达式语句可以是内建函数,也可以是自定义的方法和函数(命名或匿名都可)
注意:go语言不同的goroutine间的代码次序并不代表真正的执行顺序(不清楚真正的调度顺序),主goroutine结束,其创建的goroutine如果还没有执行那么会被销毁。

对比OS线程

  • os线程:固定栈内存:2MB
  • goroutine:栈内存不固定,从2KB到1GB

示例

通道channel

channel声明:
注意:创建channel时如果指定channel的长度,那么有缓冲区,缓冲区未满时不阻塞,如果没有指定长度,那么只会往里边写入一次之后就会阻塞
【实例】不断从终端中获取数据

select

使用select可以从多个channel中读取数据:

sync同步包

互斥锁:Mutex

读写锁:RWMutex

接口:(允许多读单写,读写互斥)

WaitGroup(并发等待组)

接口:

map(并发安全字典)

原生的字典map多个goroutine同时添加key-value的时候可能会发生数据的丢失
go语言中有sync.Map提供以下接口:

标准库

OS

读文件:
写文件:

Time

Strconv

Context

Context 包提供上下文机制在 goroutine 之间传递 deadline、取消信号(cancellation signals)或者其他请求相关的信息。

创建根context

根 context 不会被 cancel。这两个方法只能用在最外层代码中,比如 main 函数里。

派生context

示例

Flag

定义flag命令行参数,用来接收命令行输入的参数值,一般有以下两种方法
  • flag.TypeVar():先定义参数(实际上是指针),再定义flag.TypeVar将命令行参数存储(绑定)到前面参数的值的指针(地址)
  • flag.Type():用短变量声明的方式定义参数类型及变量名
flag包支持的命令行参数的类型有bool、int、int64、uint、uint64、floatfloat64、string、duration
解析参数方式:
-flag xxx
空格和一个-​符号
--flag xxx
空格和两个-​符号
-flag=xxx
等号和一个-​符号
--flag=xxx
等号和两个-​符号
flag的详细用法可参考flag包文档

Log

上一篇
构建GoWeb服务器
下一篇
Go语言基础
Loading...
文章列表

加载中