去年学了一遍 Golang,发现都给整忘了, 好饭不怕晚,再次二刷。 其实学好 Golang 并不难,关键是要找到它和其它语言不同和众里寻他千百度相通的微妙之处,就能很优雅地使用 Golang,以下会涉及较多知识点。
1: 空结构体 类型 struct{}
, 空结构体的实例 struct{}{}
2: 空接口 类型 interface{}
fun init(){} // 会自动执行
1: 需要传递匿名函数的地方:
- once.Do(func() { // 该函数只会执行一次
- fmt.Println("Create Obj")
- singleInstance = new(Singleton)
- })
2: 如果一个函数需要返回一个匿名函数:
- type retFunc func() // 通过 type 创建一个 别名
-
- func xxx() retFunc{
- return func(){
- return 1;
- }
- }
更凶残的匿名函数:
- writeDataFn := func() {
- var data []int // 往 slice 里 写100 个数;
- for i := 0; i < 100; i++ {
- data = append(data, i)
- }
- atomic.StorePointer(&shareBufPtr, unsafe.Pointer(&data)) // 写完后,将共享缓存的指针指向它;
- }
-
- readDataFn := func() {
- data := atomic.LoadPointer(&shareBufPtr)
- fmt.Println(data, *(*[]int)(data)) // 打印共享缓存里的值
- }
1: 注意接口是 type xxx interface{}
2: 传入接口的地方不能是指针 p *Programmer
:
- func writeFirstProgram2(p *Programmer) {
- fmt.Printf("%T %v\n", p, p.WriteHelloWorld())
- }
如果接口要传入这个,则必须定义返回值为 interface{}
的方法:
- type Programmer interface {
- WriteHelloWorld() interface{}
- }
注意: 1:父接口,只能是实例,不能是指针,可以看成父接口本身代表的就是指针实例 2:子类以 指针/实例 实现的接口,传入时,就得以对应的指针实例传入接口 3: 一个接口,通常 只定义 一个 方法,尽量保证 接口精简:
1:行为的定义时 type xxx struct{}
2:行为的方法实现,决定了最终传入的实例是什么
- type Programmer interface {
- WriteHelloWorld() string
- }
第一种: 子类实现 func (p *NoTypeProgrammer) WriteHelloWorld()
, 则 只能被 指针调用
- // 将 子行为 传入接口
- type NoTypeProgrammer struct {
- }
-
- // 标识,要看最终这里的实现 !!!
- func (p *NoTypeProgrammer) WriteHelloWorld() string {
- return "System.out.Println(\"Hello World!\")"
- }
-
- // 传入接口的 方法, 这里传 指针or实例都可以的
- func writeFirstProgram(p Programmer) {
- fmt.Printf("%T %v\n", p, p.WriteHelloWorld())
- }
方法前面传入对象,是为了通过对象.s 调用内部的成员变量or成员函数
调用:
- noTypeProg := new(NoTypeProgrammer)
- writeFirstProgram(noTypeProg)
- //writeFirstProgram(NoTypeProgrammer{}) // error
第二种:子类实现 func (p oTypeProgrammer) WriteHelloWorld()
, 则指针和实例都可以:
- // 标识,要看最终这里的实现 !!!
- func (p NoTypeProgrammer) WriteHelloWorld() string {
- return "System.out.Println(\"Hello World!\")"
- }
调用:
- noTypeProg := new(NoTypeProgrammer)
- writeFirstProgram(noTypeProg)
- writeFirstProgram(NoTypeProgrammer{}) // right
1: 空接口,代表 object 类型。需要通过断言判断类型
- func DoSomething(p interface{}) {
- switch v := p.(type) {
- case int:
- fmt.Println("Integer", v)
- case string:
- fmt.Println("String", v)
- default:
- fmt.Println("Unknow Type")
- }
- }
-
- // 使用
- DoSomething("10")
2:空接口可以接受任何类型的值:
var yzj interface{} // 空接口的使用,空接口类型的变量可以保存任何类型的值,空格口类型的变量非常类似于弱类型语言中的变量,未被初始化的 interface 默认初始值为 nil。
1:使用多返回值,错误码来判断程序的处理;类似于 C ,通过引用或者指针传出来错误码;【最佳的是返回码 + 引用】;
1:Communication sequential process: 进程间通过管道进行通信。
2:channel 的两种通信方式:
单通道: chan string
, chan int
, 一次只能放入一个值, 在 值 被取走前, 通道是阻塞的。
3: 创建一个协程,除了 go func(){}
还有更简洁的方式:
go agt.EventProcessGroutine() // 直接go 后面接一个 实名函数 也可以
协程是异步的, 主线程只会因为 通道阻塞。
- sequenceDiagram
- main ->> main:
-
- main ->> + AsyncService : 异步执行
- main ->> +otherTask: 和 AsyncService 同步执行
- otherTask-->>- main: return
- main ->> main: 等待 <-channel
- AsyncService ->> AsyncService: sleep
- AsyncService ->> AsyncService: sleep
- AsyncService ->>main: retCh <- ret, 通道在左边,是给通道赋值
- main ->> main: 唤起执行
- AsyncService ->>AsyncService: 继续执行
- main ->> main: End
- AsyncService ->>AsyncService: 继续执行
- AsyncService -->>- main: return
3:主线程可通过 var wg sync.WaitGroup()
管理多个协程的并发问题;
4: 生产者和消费者之间通过生产者的 close(chan)
来广播结束通道,
5: // 通道没关闭,其它协程会陷入死循环;
close(chan)
给其它协程广播的是 struct{}{}
空消息;
6: 可用于任意任务完成的场景。
一个 buffer 会阻塞其它协程的 写, N 个协程创建 N 个 Buffer, 就是非阻塞的了。
ch := make(chan string)
7: sync.WaitGroup()
等待所有协程返回:
8: 利用 channel 自身取的阻塞,有多少个协程,就循环多少次:
资料领取直通车:Golang云原生最新资料+视频学习路线
Go语言学习地址:Golang DevOps项目实战
1: 可通过父协程取消所有子协程(非 Channel 实现)。
1: sync.Once
的 Do()
方法,可保证匿名函数只被执行一次
1: 对象池中如果需要放入任意的数据类型,就放入 interface{}
1: 生命周期不可控,随时会被 GC
1:
2: bench 只能做参考,在不同的方式下,运行的次数不同。
3: bench 的使用
- func BenchSyncmap(b *testing.B) {
- // bench 需要传入一个函数
- b.Run("map with RWLock", func(b *testing.B) {
- hm := CreateRWLockMap()
- benchMap(b, hm)
- })
- }
1: 可反射成员方法,编写更灵活的代码;
2: 特别是在解析 json 的时候;
3: 名字可以不一样,但是类型一样即可。 【万能程序】
1: 类型转换
2: 可通过 atomic.StorePointer()
和 actomic.LoadPointer()
对多个协程并发读写:
实际上读和写是分开的两个 Buffer:
1: 定义接口
2: 定义每个接口Filter
的 工厂方法 + 数据处理方法 【工厂方法 在很多 库里都用到了,这种设计方式可成为定式 】
3: 串起来调用:
一个更常用的测试用例:
需要测试的接口,可以将各种实例放进去:
实现一个 接口的经典例子【golang 的类组织方式,采用的是更扁平化的方式】:
创建对应的接口,并最终调用它:
1: 微服务模式, 总类管理子类的过程;
1:通过 struct tag 来解析,相对比较简单,有点类似那个万能程序;
2:easyjson 需要手动生成 marsh 和 unmarsh 文件;
1: ROA:面向资源的架构
1: 常见性能调优过程:
2: 性能分析的常见指标:
3: prof 常用指令:
- go test -bench=.
- go test -bench=. -cpuprofile=cpu.prof
- go tool pprof cpu.prof top -cum list function
- go test -bench=.
- go test -bench=. -memprofile=mem.prof
- go tool pprof mem.prof
- top
- list function
也可调用 对应的API,对某个函数 精准的输出 prof 文件
4: 网络查看: http://localhost:8081/debug/pprof/
5: 影响性能的几个要点:
文本处理和字符串连接
协程锁:
GC 友好代码【尽量复用内存,减少内存分配】:
1: 隔离错误:
2: 重用和冗余的适当取舍;
3: 限流;
4: 快速拒绝比慢响应更优;(从web 的角度)
5: 不要无休止的等待;
6: 断路器: 利用好缓存,适当容错;
1: 注意僵尸进程: 池化资源耗尽 和 死锁;
2: Let it crash 大多数 比 recover 程序更优(尽早发现错误尽早解决);
3: 构建可恢复的系统:
4:与客户端协商访问频次, 类似限流;