Go 开发

0、参数传递永远是值传递,地址也是一种值

1、go 开发环境的配置

2、import 包的几种形式:

  1)_,默认导入一个包时,会将包中内容导入再执行包中的init()方法,有时并不需要某个包,只是想将执行该包的init()方法,可以导入时在包名前加下划线。如:import _ "fmt"

  2).,点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名,也就是前面你调用的 fmt.Println(“hello world”)  可以省略的写成Println(“hello world”)

  3)别名,import( f “fmt” )  别名操作调用包函数时前缀变成了重命名的前缀,即 f.Println(“hello world”)

3、:=  和 = 不一样,一个是声明定义,一个是赋值

4、package 与 目录不一致时:package 包名 == 使用的包名,目录 == import 包的目录

5、type interface{} 定义,和接口的实现,func (c type) funcname() {},实现所属于 type 的方法,类似于类

6、make(只用于map,slice和channel,并且不返回指针,有初始化,因为这三种类型本质上为引用类型,它们在使用前必须初始化) 和 new(返回类型的指针,不初始化内存,只是将其置零。少用,同时也减少内存创建销毁)的差异

7、interface:

  • interface是方法的集合
  • interface是一种类型

使用接口变量是需要开销的:分配和重定向。所以,如果没有很明显的使用接口的原因,你可能不要使用接口。下面是在代码中是否使用接口的原则。

使用接口的情况:

  • 用户 API 需要提供实现细节的时候。
  • API 的内部需要维护多种实现。
  • 可以改变的 API 部分已经被识别并需要解耦。

不使用接口的情况:

  • 为了使用接口而使用接口。
  • 推广算法。
  • 当用户可以定义自己的接口时。

在底层,interface作为两个成员实现:一个类型和一个值。该值被称为接口的动态值, 它是一个任意的具体值,而该接口的类型则为该值的类型。对于 int 值3, 一个接口值示意性地包含(int, 3)。

只有在内部值和类型都未设置时(nil, nil),一个接口的值才为 nil。特别是,一个 nil 接口将总是拥有一个 nil 类型。若我们在一个接口值中存储一个 *int 类型的指针,则内部类型将为 *int,无论该指针的值是什么:(*int, nil)。 因此,这样的接口值会是非 nil 的,即使在该指针的内部为 nil。

接口之间可以强转,但是只能上转下,就是外部的接口可以强转嵌套内的接口,但是嵌套内的接口不能强转外部的接口,有点像继承子类强转父类,但是父类不能强转子类一样

8、变量逃逸:本应分配在栈中的变量分配到堆去,https://blog.csdn.net/weixin_38975685/article/details/79788273

  一个函数内的局部变量,不管是不是动态new出来的,它会被分配在堆还是栈,是由编译器做逃逸分析之后做出的决定。所以即使是new、make也有可能分配在栈上。

  即使没有变量逃逸,但是由于变量size过大,本来分配在栈上的也会分配到堆上。

  切片保存在堆上?

9、包的可见性,常量、变量、类型、函数名、结构字段等等都必须大写才能给包外文件使用

10、类型断言,x.(T),这里x必须是接口类型的值(interface{}),T表示一个类型(通常为接口类型的实例化类型),一个类型断言检查一个接口对象x的动态类型是否和断言的类型T匹配:

func echoArray(a interface{}){
    b,_:=a.([]int)//通过断言实现类型转换
  for _,v:=range b{
    fmt.Print(v," ")
  }
  fmt.Println()
  return
} 

func main(){
  a:=[]int{2,1,3,5,4}
  echoArray(a)
}

11、go 代码辅助工具

12、defer

13、panic、recover

14、pprof  性能分析

15、bytes.Buffer 用于 string 操作会提高效率,减少对象分配、拷贝等

下面是字符串拼接时通过 + 号和buffer操作时的测试效率和内存分配

Go 开发

func BenchmarkPrintOperator(b *testing.B) {
    m := "i am lin"
    n := "!"

    for i := 0; i < b.N; i++ {
        var s string
        for j := 0; j < 1; j++ {
            s += m + n
        }
    }
}

func BenchmarkPrintBuffer(b *testing.B) {
    m := "i am lin"
    n := "!"

    for i := 0; i < b.N; i++ {
        var l bytes.Buffer
        for j := 0; j < 1; j++ {
            l.WriteString(m)
            l.WriteString(n)
        }
        _ = l.String()
    }
}

循环次数由100到10到2到1

16、锁:互斥锁和读写锁,读写锁(RWMutex)是基于互斥锁(Mutex)实现的

17、原子操作,atomic,对于一个不能被取址的数值,是无法进行原子操作的

18、select 函数是一个阻塞函数,当没有case来的时候,一直处于阻塞的状态,除非有default函数退出

19、omitempty

每个结构字段的编码可以通过结构字段标签中“json”键下存储的格式字符串来定制。格式字符串给出字段的名称,可能后跟逗号分隔的选项列表。名称可能为空,以指定选项而不覆盖默认字段名称。

“omitempty”选项指定如果字段具有空值,定义为false,0,零指针,nil接口值以及任何空数组,切片,映射或字符串,则该字段应从编码中或返回数据省略。

作为特殊情况,如果字段标签是“ - ”,则该字段总是被省略。请注意,名称为“ - ”的字段仍然可以使用标签“ - ,”生成。

若以json返回,omitempty类型表示字段值为空就不返回,- 表示字段值为不为空都不返回;以json输入,- 表示无论前端是否有字段也无法取到值,omitempty 表示有字段传入才有值。

type Person struct {
    Name  string     `json:"name"`
    Age     int         `json:"age"`
    Addr    string    `json:"addr,omitempty"`
}    
{}

{,"addr":"Japan"}

有了omitempty后,如果addr为空, 则生成的json中没有addr字段

20、slice、map、chan 是引用类型,通过 make 来分配内存,只声明直接使用会报错;所以很多函数修改这些变量类型会通过指针;这三种类型进行参数传递,需挖掘;

21、map 不是线程安全的,因此操作时需要加锁;map的值如果是array、struct 的话,无法修改,因为是值类型,必须通过指针修改;像 int、float 这种类型的属于赋值,即可以修改

22、go tool

23、gc 算法:标记-清除、引用计数-扫描

24、内存管理(https://my.oschina.net/renhc/blog/2236782)、内存数据布局

25、反射:运行时动态地知道变量的类型和值(https://studygolang.com/articles/12348?fr=sidebar),并且可以查看对应的方法来间接使用

26、goroutine 调度

27、channel 调度;关闭大于1次会引起panic;nil chan 关闭会panic;chan close掉可以读取到空值;chan <- int 只写,<- chan int 只读,通常用于函数参数或返回值,防止chan滥用;channel没关闭会被gc掉;

28、空 interface

29、结构体

  • 结构体的成员变量在内存存储上是一段连续的内存
  • 结构体的初始地址就是第一个成员变量的内存地址
  • 基于结构体的成员地址去计算偏移量。就能够得出其他成员变量的内存地址

  结构体都实现了一个空的接口,可以将interface的变量用于断言

30、unsafe.Pointer 慎用,指针是有数据类型的,不能随意转换,通过这个就可以,但是可能会造成未知的错误:(*uint)(unsafe.Pointer(&m))

31、var _ = proto.Marshal,const _ = proto.ProtoPackageIsVersion3

  这种形式的代码可以在有 import 这个包但是没使用的情况下使用,这样就不用时不时注释不必要的 import,避免不使用的错误

32、内存对齐,结构体的变量位置设置好可减少内存使用:

https://github.com/EDDYCJY/blog/blob/master/golang/2018-12-26-%E5%9C%A8Go%E4%B8%AD%E6%81%B0%E5%88%B0%E5%A5%BD%E5%A4%84%E7%9A%84%E5%86%85%E5%AD%98%E5%AF%B9%E9%BD%90.md?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

  unsafe.Alignof 函数返回对应参数的类型需要对齐的倍数

33、slice,底层机制,赋值时根据 len 和 cap 来判断是否开启一块新的内存,若 cap 足够则赋值后的变量跟左边的变量指向同一个内存地址,那么修改会相互影响;除非扩容,两个才指向不同的地址

  不通过 make 来定义 slice,slice的 cap 和 len 等于声明时的值的个数,https://www.jianshu.com/p/030aba2bff41

34、除了 main 主协程,其他子协程、孙携程等都是平行关系,没有父子的依赖。协程共享堆,但不共享栈。父协程退出不会影响子协程的执行,但是main协程退出,则所有协程都退出。

35、操作系统对线程的调度是抢占式的,也就是说单个线程的死循环不会影响其它线程的执行,每个线程的连续运行受到时间片的限制。

  Go 语言运行时对协程的调度并不是抢占式的。如果单个协程通过死循环霸占了线程的执行权,那这个线程就没有机会去运行其它协程了,你可以说这个线程假死了。不过一个进程内部往往有多个线程,假死了一个线程没事,全部假死了才会导致整个进程卡死。

  启动的时候,会专门创建一个线程sysmon,用来监控和管理,在内部是一个循环:

  (1)记录所有P的G任务计数schedtick(schedtick会在每执行一个G任务后递增)

  (2)如果检查到 schedtick一直没有递增,说明这个P一直在执行同一个G任务,如果超过一定的时间(10ms),就在这个G任务的栈信息里面加一个标记

  (3)然后这个G任务在执行的时候,如果遇到非内联函数调用,就会检查一次这个标记,然后中断自己,把自己加到队列末尾,执行下一个G

  (4)如果没内联、没系统调用、没阻塞调用、没人工调度,那这个协程就会一直占着这个M

  每个线程都会包含多个就绪态的协程形成了一个就绪队列,如果这个线程因为某个别协程死循环导致假死,那这个队列上所有的就绪态协程是不是就没有机会得到运行了呢?Go 语言运行时调度器采用了 work-stealing 算法,当某个线程空闲时,也就是该线程上所有的协程都在休眠(或者一个协程都没有),它就会去其它线程的就绪队列上去偷一些协程来运行。也就是说这些线程会主动找活干,在正常情况下,运行时会尽量平均分配工作任务。

36、handler:

mux的ServeHTTP方法通过调用其Handler方法寻找注册到路由上的handler函数,并调用该函数的ServeHTTP方法。

mux的Handler方法对URL简单的处理,然后调用handler方法,后者会创建一个锁,同时调用match方法返回一个handler和pattern。

在match方法中,mux的m字段是map[string]muxEntry图,后者存储了pattern和handler处理器函数,因此通过迭代m寻找出注册路由的patten模式与实际url匹配的handler函数并返回。

返回的结构一直传递到mux的ServeHTTP方法,接下来调用handler函数的ServeHTTP方法,即IndexHandler函数,然后把response写到http.RequestWirter对象返回给客户端。

上述函数运行结束即`serverHandler{c.server}.ServeHTTP(w, w.req)`运行结束。接下来就是对请求处理完毕之后上希望和连接断开的相关逻辑。

至此,Golang中一个完整的http服务介绍完毕,包括注册路由,开启监听,处理连接,路由处理函数。

多数的web应用基于HTTP协议,客户端和服务器通过request-response的方式交互。一个server并不可少的两部分莫过于路由注册和连接处理。Golang通过一个ServeMux实现了的multiplexer路由多路复用器来管理路由。同时提供一个Handler接口提供ServeHTTP用来实现handler处理其函数,后者可以处理实际request并构造response。

ServeMux和handler处理器函数的连接桥梁就是Handler接口。ServeMux的ServeHTTP方法实现了寻找注册路由的handler的函数,并调用该handler的ServeHTTP方法。ServeHTTP方法就是真正处理请求和构造响应的地方。

37、协程控制:chan close掉可以读取到空值,空值是根据不同数据类型显示的,这样可以让子协程去监听chan来达到统一控制;context 可以生成子context传导下去,parent context可以控制child context。

38、不可改变的字符串——如果想通过索引运算符来更新一个字符串变量中的独立字符是会出现错误的,由于字符串是只读的字节片。正确做法是使用一个单字节片([]byte)进行操作而不是转成字符串类型进行操作。

39、字符串和索引运算符——字符串中的索引运算符返回的是字节值而不是字符。

40、rune

41、runtime.Gosched(),出让CPU调度,因为协程是挂在线程上跑完为止的,除非阻塞

42、runtime.GOMAXPROCS 用来设置GPM中的P,这样就实现同时有几个G可以被执行,Golang默认使用所有的cpu核

43、nil:是预定义的标识符,代表指针、通道、函数、接口、映射或切片的零值,(nil,nil)== nil

44、sync.pool:类似一个容量池,是可伸缩的,并发安全的。其大小仅受限于内存的大小,可以被看作是一个存放可重用对象的值的容器。 设计的目的是存放已经分配的但是暂时不用的对象,在需要用到的时候直接从pool中取。会被GC掉。

45、减少在堆上的内存分配(new、make)和变量逃逸,减少GC时间

46、struct{}的size为0,即不占内存,可用来某些操作:

  做set,map[string]struct{},由于struct{}是空,不关心内容,这样map便改造为set;chan struct{} 可以做退出,它不能被写入任何数据,只有通过close()函数进行关闭操作,才能进行输出操作,所以可以并行控制任务

47、微服务框架:go-micro、gokit、goa、SpringCloud(多语言)、Istio(Service Mesh)

48、64位机,字符串的size是16个字节,slice 是24个字节,指针是8个字节

49、协程func里必须要有defer、recover,recover只会在defer生效,其他返回nil。goroutine发生panic时,只会调用自身的defer,所以即便主goroutine里写了recover逻辑,也无法拯救到其它goroutine里的panic

上一篇:Linux内核系统体系概述


下一篇:POJ 2778 DNA Sequence (AC自己主动机 + dp)