• 学Golang,看这一篇


    去年学了一遍 Golang,发现都给整忘了, 好饭不怕晚,再次二刷。 其实学好 Golang 并不难,关键是要找到它和其它语言不同和众里寻他千百度相通的微妙之处,就能很优雅地使用 Golang,以下会涉及较多知识点。

    特殊类型

    1: 空结构体 类型 struct{}, 空结构体的实例 struct{}{}

    2: 空接口 类型 interface{}

    会自动执行的函数

    fun init(){}  // 会自动执行
    
    • init 函数先于 main 函数自动执行,不能被其他函数调用;
    • init 函数没有输入参数、返回值;
    • 每个包可以有多个 init 函数;
    • 包的每个源文件也可以有多个 init 函数,这点比较特殊;
    • 同一个包的 init 执行顺序,golang 没有明确定义,编程时要注意程序不要依赖这个执行顺序。
    • 不同包的 init 函数按照包导入的依赖关系决定执行顺序。

    匿名函数

    1: 需要传递匿名函数的地方:

    1. once.Do(func() { // 该函数只会执行一次
    2. fmt.Println("Create Obj")
    3. singleInstance = new(Singleton)
    4. })

    2: 如果一个函数需要返回一个匿名函数:

    1. type retFunc func() // 通过 type 创建一个 别名
    2. func xxx() retFunc{
    3. return func(){
    4. return 1;
    5. }
    6. }

    更凶残的匿名函数:

    1. writeDataFn := func() {
    2. var data []int // 往 slice 里 写100 个数;
    3. for i := 0; i < 100; i++ {
    4. data = append(data, i)
    5. }
    6. atomic.StorePointer(&shareBufPtr, unsafe.Pointer(&data)) // 写完后,将共享缓存的指针指向它;
    7. }
    8. readDataFn := func() {
    9. data := atomic.LoadPointer(&shareBufPtr)
    10. fmt.Println(data, *(*[]int)(data)) // 打印共享缓存里的值
    11. }

    接口

    1: 注意接口是 type xxx interface{} 2: 传入接口的地方不能是指针 p *Programmer:

    1. func writeFirstProgram2(p *Programmer) {
    2. fmt.Printf("%T %v\n", p, p.WriteHelloWorld())
    3. }

    如果接口要传入这个,则必须定义返回值为 interface{} 的方法:

    1. type Programmer interface {
    2. WriteHelloWorld() interface{}
    3. }
    注意: 1:父接口,只能是实例,不能是指针,可以看成父接口本身代表的就是指针实例 2:子类以 指针/实例 实现的接口,传入时,就得以对应的指针实例传入接口 3: 一个接口,通常 只定义 一个 方法,尽量保证  接口精简

    行为

    1:行为的定义时 type xxx struct{}

    2:行为的方法实现,决定了最终传入的实例是什么

    1. type Programmer interface {
    2. WriteHelloWorld() string
    3. }

    第一种: 子类实现 func (p *NoTypeProgrammer) WriteHelloWorld(), 则 只能被 指针调用

    1. // 将 子行为 传入接口
    2. type NoTypeProgrammer struct {
    3. }
    4. // 标识,要看最终这里的实现 !!!
    5. func (p *NoTypeProgrammer) WriteHelloWorld() string {
    6. return "System.out.Println(\"Hello World!\")"
    7. }
    8. // 传入接口的 方法, 这里传 指针or实例都可以的
    9. func writeFirstProgram(p Programmer) {
    10. fmt.Printf("%T %v\n", p, p.WriteHelloWorld())
    11. }
    方法前面传入对象,是为了通过对象.s 调用内部的成员变量or成员函数

    调用:

    1. noTypeProg := new(NoTypeProgrammer)
    2. writeFirstProgram(noTypeProg)
    3. //writeFirstProgram(NoTypeProgrammer{}) // error

    第二种:子类实现 func (p oTypeProgrammer) WriteHelloWorld() , 则指针和实例都可以:

    1. // 标识,要看最终这里的实现 !!!
    2. func (p NoTypeProgrammer) WriteHelloWorld() string {
    3. return "System.out.Println(\"Hello World!\")"
    4. }

    调用:

    1. noTypeProg := new(NoTypeProgrammer)
    2. writeFirstProgram(noTypeProg)
    3. writeFirstProgram(NoTypeProgrammer{}) // right

    空接口

    1: 空接口,代表 object 类型。需要通过断言判断类型

    1. func DoSomething(p interface{}) {
    2. switch v := p.(type) {
    3. case int:
    4. fmt.Println("Integer", v)
    5. case string:
    6. fmt.Println("String", v)
    7. default:
    8. fmt.Println("Unknow Type")
    9. }
    10. }
    11. // 使用
    12. DoSomething("10")

    2:空接口可以接受任何类型的值:

    var yzj interface{} // 空接口的使用,空接口类型的变量可以保存任何类型的值,空格口类型的变量非常类似于弱类型语言中的变量,未被初始化的 interface 默认初始值为 nil。

    错误处理

    1:使用多返回值,错误码来判断程序的处理;类似于 C ,通过引用或者指针传出来错误码;【最佳的是返回码 + 引用】;

    模块

    • 同一个目录下只能有一个包名;
    • 包名推荐与目录名保持一致;
    • 每一个包内,只能有一个同名函数;
    • 同一个 package 内的 函数, 变量可以随意调用,不用导入

    CSP

    1:Communication sequential process: 进程间通过管道进行通信。

    2:channel 的两种通信方式

    • buffer- channel: 发送者和接受者有更松的耦合关系;
    • 容量没满, 放消息的人可以一直往里面放; 若容量已满, 直到接受者收走一个消息,能继续放入消息了,才能继续往下执行;
    • 接收者也类似: 只要是非空,能一直拿消息,向下执行;如果没有消息,需要一直等待,然后再向下继续执行;

    单通道: chan string, chan int, 一次只能放入一个值, 在 值 被取走前, 通道是阻塞的。

    3: 创建一个协程,除了 go func(){} 还有更简洁的方式:

    go agt.EventProcessGroutine()   // 直接go 后面接一个 实名函数 也可以
    

    协程是异步的, 主线程只会因为 通道阻塞

    1. sequenceDiagram
    2. main ->> main:
    3. main ->> + AsyncService : 异步执行
    4. main ->> +otherTask: 和 AsyncService 同步执行
    5. otherTask-->>- main: return
    6. main ->> main: 等待 <-channel
    7. AsyncService ->> AsyncService: sleep
    8. AsyncService ->> AsyncService: sleep
    9. AsyncService ->>main: retCh <- ret, 通道在左边,是给通道赋值
    10. main ->> main: 唤起执行
    11. AsyncService ->>AsyncService: 继续执行
    12. main ->> main: End
    13. AsyncService ->>AsyncService: 继续执行
    14. 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 自身取的阻塞,有多少个协程,就循环多少次:

    Select

    资料领取直通车:Golang云原生最新资料+视频学习路线

    Go语言学习地址:Golang DevOps项目实战

     

    Context

    1: 可通过父协程取消所有子协程(非 Channel 实现)。

    Sync.Once.Do()

    1: sync.Once 的 Do() 方法,可保证匿名函数只被执行一次

    对象池

    1: 对象池中如果需要放入任意的数据类型,就放入 interface{}

    sync.Pool

    1: 生命周期不可控,随时会被 GC

    bench

    1:

    2: bench 只能做参考,在不同的方式下,运行的次数不同。

    3: bench 的使用

    1. func BenchSyncmap(b *testing.B) {
    2. // bench 需要传入一个函数
    3. b.Run("map with RWLock", func(b *testing.B) {
    4. hm := CreateRWLockMap()
    5. benchMap(b, hm)
    6. })
    7. }

    反射

    1: 可反射成员方法,编写更灵活的代码;

    2: 特别是在解析 json 的时候;

    3: 名字可以不一样,但是类型一样即可。 【万能程序】

    不安全的编程

    1: 类型转换

    2: 可通过 atomic.StorePointer() 和 actomic.LoadPointer() 对多个协程并发读写:

    实际上读和写是分开的两个 Buffer:

    pipe 和 Filter

    1: 定义接口

    2: 定义每个接口Filter的 工厂方法 + 数据处理方法 【工厂方法 在很多 库里都用到了,这种设计方式可成为定式 】

    3: 串起来调用:

    一个更常用的测试用例:

    需要测试的接口,可以将各种实例放进去:

    实现一个 接口的经典例子【golang 的类组织方式,采用的是更扁平化的方式】:

    创建对应的接口,并最终调用它:

    microKernel

    1: 微服务模式, 总类管理子类的过程;

    JSON

    1:通过 struct tag 来解析,相对比较简单,有点类似那个万能程序;

    2:easyjson 需要手动生成 marsh 和 unmarsh 文件;

    HTTP 服务

    1: ROA:面向资源的架构

    性能分析

    1: 常见性能调优过程:

    2: 性能分析的常见指标:

    • 1:Wall Time;
    • 2: CPU Time;
    • 3: Block Time;
    • 4: Memory Allocation;
    • 5: GC Times/Time Spent

    3: prof 常用指令:

    • 1: 查看CPU:
    1. go test -bench=.
    2. go test -bench=. -cpuprofile=cpu.prof
    3. go tool pprof cpu.prof top -cum list function
    • 2: 查看 Mem:
    1. go test -bench=.
    2. go test -bench=. -memprofile=mem.prof
    3. go tool pprof mem.prof
    4. top
    5. list function

    也可调用 对应的API,对某个函数 精准的输出 prof 文件

    4: 网络查看: http://localhost:8081/debug/pprof/

    5: 影响性能的几个要点:

    文本处理和字符串连接

    • json 解析;
    • 字符串连接 用 bytes.Buffer 或 strings.Builder

    协程锁:

    • 少用读锁;
    • 多用 concurrent-map, 实现协程安全的 数据共享;

    GC 友好代码【尽量复用内存,减少内存分配】:

    • 大数组和结构体,在函数中传递时,使用指针;
    • 切片 最好初始化到合适的大小; 或者 用数组;

    面向错误的设计

    1: 隔离错误:

    • 从设计上隔离: Micro-kernal 设计;
    • 物理隔离,部署;

    2: 重用和冗余的适当取舍;

    3: 限流;

    4: 快速拒绝比慢响应更优;(从web 的角度)

    5: 不要无休止的等待;

    6: 断路器: 利用好缓存,适当容错;

    面向恢复的设计

    1: 注意僵尸进程: 池化资源耗尽 和 死锁;

    2: Let it crash 大多数 比 recover 程序更优(尽早发现错误尽早解决);

    3: 构建可恢复的系统:

    • 拒绝单实例;
    • 减少服务之间依赖;
    • 快速启动;
    • 尽量 无状态;

    4:与客户端协商访问频次, 类似限流;

  • 相关阅读:
    【Prometheus】Prometheus+Grafana部署
    用1100天做一款通用的管理后台框架
    pm2 命令手册
    面渣逆袭:MySQL六十六问,两万字+五十图详解!
    centos7篇---安装nvidia-docker
    【前端知识】前端加密算法(base64、md5、sha1、escape/unescape、AES/DES)
    java--object类
    Fourier分析入门——第10章——直接数据分析
    【uniapp/uview】u-datetime-picker 选择器的过滤器用法
    记一次 .NET某MES自动化桌面程序 卡死分析
  • 原文地址:https://blog.csdn.net/weixin_52183917/article/details/127907780