分类 GO 下的文章

  • reflect.TypeOf返回一个接口值对应的动态类型
  • 基础类型

    • Bool
    • String
    • 各种数字类型
  • 聚合类型

    • Array
    • Struct
  • 引用类型

    • Chan
    • Func
    • Ptr
    • Slice
    • Map
  • 接口类型

    • Interface
  • Invalid 类型
  • 注意、即使非导出字段在反射下也是可见的
x := 2
a := reflect.ValueOf(2)
b := reflect.ValueOf(x)
c := reflect.ValueOf(&x)
d := c.Elem()
  • a 里边的值是不可寻址的, 它包含的仅仅是整数2的一个副本
  • 可以通过CanAddr方法来询问reflect.Value 变量是否可寻址
  • 从一个可寻址的reflect.Value()获取变量需要三步,首先调用addr(), 返回一个Value, 其中包含一个指向变量的指针,接下来在这个Value调用Interface(),会返回一个包含这个指针的interface{}值,最后如果我们知道变量的类型,我们可以使用类型断言来把接口内容转换为一个普通指针,之后就可以通过这个指针来更新变量了
  • CanAddr来检查并不能保证正确,CanSet方法才能正确地报告一个reflect.Value是否可寻址且更改
  • 要调用req.ParseForm() req.Form才会有内容
  • 使用reflect.Value.Call 方法可以调用Func类型的Value
  • 反射注意事项

    • 反射代码脆弱,不能再编译时完成检测
    • 在包的API避免使用reflect.Value 尽量使用特定的类型来确保输入是合法的值,如果做不到这点就需要在每个危险操作前都做额外的动态检查
    • 类型其实也算某种形式的文档,反射相关的操作无法做静态类性检查,所以大量使用反射的代码是很难理解的。
    • 反射很慢
  • 32位上字的长度是4字节 64位是8字节
  • 语言规范并没有要求成员的顺序对应内存中的布局顺序
  • 将相同类型的成员定义在一起可以更节约内存空间(内存对齐)
  • unsafe.Alignof 报告它参数类型是所要求的对齐方式
  • unsafe.Offsetof 计算成员f相对于x其实地址的偏移值
  • 不要把指针转换成uintptr 再进行 操作 建议一次执行完毕,因为有可能在执行的过程中发生Go gc导致内存地址变更 而uintptr无法及时更新
  • go调用c

    • 利用cgo
    • 利用SWIG

  • *_test.go 文件里包含了三类函数

    • 功能测试函数 以Test前缀命名的函数
    • 基准测试函数 以Benchmark 前缀命名的函数
    • 示例函数 以Example 前缀命名的函数
  • 功能测试函数必须以Test开头 可选的后缀名称必须以大写字母开头
  • 比较好的实践是先写测试然后发现它触发的错误和用户bug报告里面的一致,只有这个时候我们才能确信我们修复的内容是针对这个出现的问题
  • 选项-run 的参数是一个正则表达式,它可以使德go test 只运行那些测试函数名称匹配给定模式的函数
  • t.Errorf 并不会终止测试 而是记录下来 继续执行测试 而t.Fatal 会终止测试 这些函数的调用必须和Test函数在同一个goroutine中
  • 测试错误消息一般格式是"f(x) = y, want z"
  • 随机测试(对拍) 可以写一个低效但是清晰的算法 然后采用随机算法生成参数 检测两算法的输出是否一致
  • 外部测试包 例子:net/url 的测试引用了 net/http 但是 net/http也引用了net/url go不允许出现循环依赖 所以 net/url的测试包名改成了url_test 来单独编译
  • 有时候外部测试包需要测试内部类 可以新建一个export_test.go 里面:
    比如
package fmt
var IsSpace = isSpace

然后测试包引用这个包即可

  • 覆盖率测试 go test -run=Coverage 打上这个标记 测试会统计代码覆盖率 原理是对每个代码段都加入一个变量 当访问到此代码段 则对这个变量赋值 运行完成之后对变量进行汇总
  • 基准测试
go test -bench=.
PASS
BenchmarkIsPalindrome-8 1000000 1035 ns/op

会匹配test里所有的基准测试函数
基准测试名称的数字后缀8表示GOMAXPROCS的值
报告告诉我们每次调用的平均耗时

  • 基准内存测试
    内存分配统计
go test -bench=. -benchmen
PASS
BenchmarkIsPalindrome 10000000 1026 ns/op 304 B/op 4 allocs/op
  • 性能剖析

    • 不要过早优化
    • CPU性能剖析识别出执行过程需要最多的函数, 每个CPU上面执行的县城每隔几毫秒会定期地被操作系统中断,每次中断过程中记录一个性能剖析事件,然后恢复正常执行 为了性能着想 所以保存的都是地址, 运行完成后go会把程序内存映射到本地上 比如: foo.test
    • 可以使用性能剖析GUI工具来进行分析 比如:GraphViz
  • 示例函数有三个目的

    • 文档
    • godoc 演示
    • godoc 运行样例

  • 每一个OS的线程都有一个固定的栈大小(通常是2MB) 而goroutine的栈不是固定的大小,可以按需增大和缩小,大小限制可以到达1GB
  • GOMAXPROCS Go调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行GO代码,默认数值是CPU的数量
  • 包因为没有循环依赖 所以可以整理出一条依赖链 对互不相关的链可以进行并行编译
  • 有时候我们想执行包的init方法但是却又不用他里面的方法 则可以用空导入来触发init
  1. _ 包名
  • 或者开启可选参数 比如image包 支持png则可以:
import (
    "image"
    "image/jpeg"
    _ "image/png" //注册png解码器
)
  • 不要把一个辅助工具包命名为util 使用imageutil 或ioutil等名称更具体和清晰
  • 标准包bytes、errors和strings使用复数来避免覆盖响应的预声明类型
  • 通常一些包可以用来描述为单一类型的包 提供一个New函数用来创建实例
  • GOPATH 工作目录
  • GOROOT GO目录
  • go build 所有依赖会重新编译
  • go install 所有依赖如果编译过则不会再次编译 编译后的包存放在pkg/系统架构 目录下
  • go 可以对目标文件增加一个构件标签的特殊注释如文件包含以下注释:
    // + build linux drawin

则go build 只会构建Linux或 Mac OS X系统应用的时在会对它进行编译

  • go doc工具输出在命令行上指定的内容的声明和整个文档注释
  • godoc -http :8000 启动一个http文档服务器
  • 内部包
    internal目录下的包仅允许internal目录的父目录为根目录的树中

如:
snet/http/inetrnal/chunked 可以从 net/http/httputil或net/http导入 但是不能从net/url进行导入

可以通过类型分支来判定类型

func sqlQuote(x interface{}) string {
    switch x := x.(type) {
    case nil:
        return "NULL"
    case int, uint:
        return fmt.Sprinf("%d", x)
    }

}

无缓冲通道上的发送操作将会阻塞直到被消费
如果我们通道只用来通知可用
done <- struct{}{}
比较短

x, ok := <- 通道名
可以获得通道值以及通道是否关闭
关闭通道可以用close(通道名)

也可以通过range来 循环一个通道直到通道关闭
如:
for x := range 通道名 {}

试图关闭一个已关闭的通道会导致宕机

如果一个通道没有被释放完直接return 将会导致goroutine泄漏

使用select实现多路复用

select {
case <- ch1:
//...
case x := <- ch2:
//...
case ch3 <- y: 
//...
}

select会一直等待直到任何一个case获得值

实现输出0 2 4 6 8

ch := make(chan int, 1)
for i := 0; i < 10; i++ {
    select{
    case v := <- ch:
        fmt.Println(i)
    case ch <- i:
    }
}

go语言可以对代码段进行标签比如:

loop:
    for {
        for {
            break loop; //直接终止到loop
        }
    }

简单实现一个令牌获取:

var sema = make(chan struct{}, 20)
for dirents(dir string) []os.FileInfo {
    sema <- struct{}{}
    defer func(){  <- sema }()
}

互斥锁:
sync.Mutex
读写锁:
sync.RWMutex (多读单写)
初始化锁:
var once sync.Once
once.Do(func)
竞态检测器:
增加-race参数 输出一份报告

函数声明包含了函数名,形参列表,函数体和返回值。 func 函数名(形参列表) (返回值) {
函数体
}

返回值可以像形参一样命名,每一个返回的命名值会声明为一个局部变量,并根据变量类型初始化为相应的0值。

如果几个形参或返回值的函数类型相同,那么类型只需要写一次。如:

func f(i, j, k int, s, t string)

函数的类型称作函数签名,当两个函数拥有相同的形参列表和返回列表时,认为这两个函数的类型或签名都是相同的。

go语言没有默认参数值也不能指定实参名

go的实参是按值传递的

如果提供的实参包含引用类型(指针,slice,map,函数或者通道)那么函数使用这些变量将会简介的修改实参内容

go语言的垃圾回收机制将回收未使用的内存,但不能指望他会释放未使用的操作系统资源

一个多值调用可以作为单独的实参传递给拥有两个形参的函数中
比如:

func findLinksLog(url string) ([]string, error) {return nil, nil}

fmt.Println(findLinksLog(url));

一个函数如果有命名的返回值,可以省略return语句的操作数称为裸返回
func test() (a string, b int) {
a = "1"
b = 2
return //相当于 return a, b
}

函数变量不能比较所以不能用在map的键中

函数字面量在任何表达式内指定函数变量,函数字面量就像函数声明(就是函数变量)

拓展符
可以通过以下调用:

values := []int{1,2,3,4}
fmt.Println(sum(values...))

defer 以倒序进行

一个典型的宕机发生时goroutine中的所有延迟函数会执行,

可以利用recover函数对宕机进行恢复
比如:

func Parse(input string) (s *Syntax, err error){
    defer func(){
        if p := recover(); p!=nil {
        err = fmt.Errorf("internal error: %v", p)
    }
}

}

接口的nil有分 动态值nil 和 动态类型nil

比如:
var s interface{}
此时动态值和动态类型都为nil

如果是:
var s *bytes.Buffer
此时动态值为空,但是动态类型不是空 所以如下: 会为真
if s != nil {

}
当你实现了一个接口 然后调用此函数时,本质上go是调用了实现此接口的方法,然后给了这个接口对应的接收者地址。