Category: go

  • linux安装多个版本的golang

    直接下载压缩包

    下载压缩包,然后改变软连

    [wjh@node1 ~]$ go version
    go version go1.22.5 linux/amd64
    [wjh@node1 ~]$ which go
    /usr/bin/go
    [wjh@node1 ~]$ ls -al /usr/bin/|grep go$
    lrwxrwxrwx   1 root root          24 Jan 12  2022 go -> /usr/local/golang/bin/go
    [wjh@node1 ~]$
    [wjh@node1 ~]$ ls -al /usr/local/golang
    total 12
    drwxr-xr-x   5 root root   65 Aug  5 17:09 .
    drwxr-xr-x. 27 root root 4096 Jul 14 03:07 ..
    lrwxrwxrwx   1 root root   30 Aug  5 17:09 bin -> /usr/local/golang/go1.22.5/bin
    drwxr-xr-x  10 wjh  wjh   257 Jan  7  2022 go1.17.6
    drwxr-xr-x  10 root root 4096 Aug 31  2023 go1.21.0
    drwxr-xr-x  10 root root 4096 Jun 28 04:11 go1.22.5
    [wjh@node1 ~]$
    

    这样要切换golang 版本我要自己更新软连.

    使用 go install 安装

    [wjh@node1 ~]$ go install golang.org/dl/go1.23.0@latest
    
    [wjh@node1 ~]$ go1.23.0 version
    go1.23.0: not downloaded. Run 'go1.23.0 download' to install to /home/wjh/sdk/go1.23.0
    
    [wjh@node1 ~]$ go1.23.0 download
    Downloaded   0.0% (   16384 / 73590011 bytes) ...
    Downloaded  13.7% (10059776 / 73590011 bytes) ...
    Downloaded  36.2% (26607616 / 73590011 bytes) ...
    Downloaded  57.1% (42024928 / 73590011 bytes) ...
    Downloaded  75.0% (55197536 / 73590011 bytes) ...
    Downloaded  90.2% (66387760 / 73590011 bytes) ...
    Downloaded 100.0% (73590011 / 73590011 bytes)
    Unpacking /home/wjh/sdk/go1.23.0/go1.23.0.linux-amd64.tar.gz ...
    Success. You may now run 'go1.23.0'
    
    [wjh@node1 ~]$ go1.23.0 version
    go version go2.23.0 linux/amd64
    
  • go_1.21泛型示例

    package demo
    
    import (
        "math/rand"
        "time"
    )
    
    // 定义泛型接口
    type RandomElementer[T any] interface {
        // 返回一个随机的元素,如果集合为空,返回(zero, false)
        RandomElement() (T, bool)
    }
    
    func MustRandom[T any](collection RandomElementer[T]) T {
        val, ok := collection.RandomElement()
        if !ok {
            panic("collection is empty.")
        }
        return val
    }
    
    // MyList 泛型集合.
    type MyList[T any] []T
    
    // MyList 实现接口RandomElement
    func (l MyList[T]) RandomElement() (T, bool) {
        n := len(l)
        if n == 1 {
            return l[0], true
        }
    
        r := rand.New(rand.NewSource(time.Now().UnixNano()))
        return l[r.Intn(n)], true
    }
    
    
    package demo
    
    import (
        "reflect"
        "testing"
    )
    
    func TestMustRandom(t *testing.T) {
        type args struct {
            collection RandomElementer[int]
        }
        tests := []struct {
            name string
            args args
            want int
        }{
            // TODO: Add test cases.
            {
                "case1",
                args{MyList[int]{1, 1, 1}},
                1,
            },
            {
                "case2",
                args{MyList[int]{6, 6, 6}},
                6,
            },
        }
        for _, tt := range tests {
            t.Run(tt.name, func(t *testing.T) {
                if got := MustRandom(tt.args.collection); !reflect.DeepEqual(got, tt.want) {
                    t.Errorf("MustRandom() = %v, want %v", got, tt.want)
                }
            })
        }
    }
    
    
  • go 内存对齐

    文章来源

    内存对齐是指首地址对齐,而不是说每个变量大小对齐

    为减少内存对齐带来的 padding 浪费. 构建结构体时,先写大的成员

    操作系统并非一个字节一个字节访问内存,而是按2, 4, 8这样的字长来k访问。
    因此,当 CPU 从存储器读数据到寄存器,或者从寄存器写数据到存储器,IO 的数据长度通常是字长。 如:
    * 32 位系统访问粒度是 4 字节(bytes),
    * 64 位系统的是 8 字节。

    当被访问的数据长度为 n 字节且该数据地址为n字节对齐,那么操作系统就可以高效地一次定位到数据,无需多次读取、处理对齐运算等额外操作。
    数据结构应该尽可能地在自然边界上对齐。如果访问未对齐的内存,CPU 需要做两次内存访问。

    没有内存对齐,cpu读取两次,然后在寄存器中合并.

    平台对齐系数

    计算机在加载和保存数据时,如果内存地址合理地对齐的将会更有效率。
    * 2字节大小的int16类型的变量地址应该是偶数,
    * 一个4字节大小的rune类型变量的地址应该是4的倍数,
    * 一个8字节大小的float64、uint64或64-bit指针类型变量的地址应该是8字节对齐的。
    * 但是对于再大的地址对齐倍数则是不需要的,即使是complex128等较大的数据类型最多也只是8字节对齐。

    unsafe.Sizeof

    unsafe.Sizeof函数返回操作数在内存中的字节大小
    * 参数可以是任意类型的表达式
    * 并且它并不会对表达式进行求值。

    unsafe.Alignof

    unsafe.Alignof 函数返回对应参数的类型需要对齐的倍数.
    和 Sizeof 类似, Alignof 也是返回一个常量表达式, 对应一个常量.
    通常情况下布尔和数字类型需要对齐到它们本身的大小(最多8个字节), 其它的类型对齐到机器字大小.

    unsafe.Offsetof

    unsafe.Offsetof 函数的参数必须是一个字段 x.f, 然后返回 f 字段相对于 x 起始地址的偏移量, 包括可能的空洞

    go string 类型 大小 16.

    • go语言的string是一种数据类型,这个数据类型占用16字节空间,
    • 前8字节是一个指针,指向字符串值的地址,后八个字节是一个整数,标识字符串的长度;
    • 注意go语言的字符串内部并不以’\0’作为结尾,而是通过一个长度域来表示字符串的长度。

    go 数组类型, 测试过 []int8 和 []int 都是 24

    • 在Go语言中数组是一个值类型value type
    • 是真真实实的数组,而不是一个指向数组内存起始位置的指针,也不能和同类型的指针进行转化,这一点严重不同于C语言
    • 所有的值类型变量在赋值和作为参数传递时都将产生一次复制动作
      (这让我想起, channel 传递对象的时候,也是值拷贝. 需要避免拷贝巨大对象)
    • 如果将数组作为函数的参数类型,则在函数调用时该参数将发生数据复制。因此,在函数体中无法修改传入的数组的内容,因为函数内操作的只是所传入数组的一个副本。

    demo

    package main
    
    import (
        "fmt"
        "reflect"
        "unsafe"
    )
    
    type empty_struct struct {
    }
    
    type empty_struct_with_name1 struct {
        flag    bool
        name    string
        empty_s empty_struct
    }
    
    type empty_struct_with_name2 struct {
        a bool
        b empty_struct
        c string
        d empty_struct
    }
    
    func main() {
        s_val := "test"
        int_arr_val := []int{0, 1, 2, 3, 4, 5}
        var empty_s empty_struct
    
        fmt.Println("type:\t", reflect.TypeOf(s_val), "\tsize of:\t", unsafe.Sizeof(s_val), "\talign of:\t", unsafe.Alignof(s_val), "\tval:\t", s_val)
        fmt.Println("type:\t", reflect.TypeOf(int_arr_val), "\tsize of:\t", unsafe.Sizeof(int_arr_val), "\talign of:\t", unsafe.Alignof(int_arr_val), "\tval:\t", int_arr_val)
    
      //空 struct 正常size of 是 0
        fmt.Println("type:\t", reflect.TypeOf(empty_s), "\tsize of:\t", unsafe.Sizeof(empty_s), "\talign of:\t", unsafe.Alignof(empty_s), "\tval:\t", empty_s)
    
      // 空 struct 由于需要内存对齐,占了 8 字节空间
        var empty_s_name1 empty_struct_with_name1
        fmt.Println("type:\t", reflect.TypeOf(empty_s_name1), "\tsize of:\t", unsafe.Sizeof(empty_s_name1), "\talign of:\t", unsafe.Alignof(empty_s_name1), "\tval:\t", empty_s_name1)
    
      //第一个空 struct 由于需要内存对齐和 bool a 一起共同占了 8 字节空间
      //第二个空 struct 由于需要内存对齐和 独自占了 8 字节空间
        var empty_s_name2 empty_struct_with_name2
        fmt.Println("type:\t", reflect.TypeOf(empty_s_name2), "\tsize of:\t", unsafe.Sizeof(empty_s_name2), "\talign of:\t", unsafe.Alignof(empty_s_name2), "\tval:\t", empty_s_name2)
        fmt.Println("struce empty_s_name2 size of:\t", unsafe.Sizeof(empty_s_name2),
            "\ta offset of:\t", unsafe.Offsetof(empty_s_name2.a), "\tb offset of:\t", unsafe.Offsetof(empty_s_name2.b),
            "\tc offset of:\t", unsafe.Offsetof(empty_s_name2.c), "\td offset of:\t", unsafe.Offsetof(empty_s_name2.d))
    
        /*
            output :
            type:    string     size of:     16     align of:    8  val:     test
            type:    []int  size of:     24     align of:    8  val:     [0 1 2 3 4 5]
            type:    main.empty_struct  size of:     0  align of:    1  val:     {}
            type:    main.empty_struct_with_name1   size of:     32     align of:    8  val:     {false  {}}
            type:    main.empty_struct_with_name2   size of:     32     align of:    8  val:     {false {}  {}}
            struce empty_s_name2 size of:    32     a offset:    0  b offset of:     1  c offset of:     8  d offset of:     24
        */
    }
    
    
  • go loop,defer 和 闭包

    golang 中的陷阱. 这是一个有趣的例子,用到了 loop, defer 和闭包.

    例子 1,输出全是 5, 例子 2 和例子 3 会输出 5 4 3 2 1 0.

    // 1
    package main
    
    import "fmt"
    
    func main() {
        var whatever [6]struct{}
        for i := range whatever {
            defer func() {
                fmt.Println(i)
            }()
        }
        /*
            为什么全是 5,为什么不是 0, 1, 2, 3, 4, 5 这样的输出结果呢?
    
            其根本原因是闭包所导致的,有两点原因:
    
            1. 在 for 循环结束后,局部变量 i 的值已经是 5 了,并且 defer 的闭包是直接引用变量的 i。
            2. 结合defer 关键字的特性,可得知会在 main 方法主体结束后再执行。
            结合上述,最终输出的结果是已经自增完毕的 5。
    
        */
    }
    
    //2
    package main
    
    import "fmt"
    
    func main() {
        var whatever [6]struct{}
        for i := range whatever {
            y := i
            defer func() {
                fmt.Println(y)
            }()
        }
    }
    
    //3
    package main
    
    import "fmt"
    
    func main() {
        var whatever [6]struct{}
        for i := range whatever {
            defer func(i int) {
                fmt.Println(i)
            }(i)
        }
    }
    

    defer 执行顺序是先进后出的倒序执行.

    例子 2 中用局部变量声明的方式把的值拷克了.因此输出结果和例子 1 不同.

    例子 3 中,defer 执行的函数的参数是由声明时确定的.因此输出结果和例子 1 不同.

    来源: https://eddycjy.com/posts/go/go-tips-defer/

  • 使用delve 调试golang程序

    目录


    运行编译程序,并且指定参数

    • 使用 -- 用于指定命令行参数
    • 等同于指定命令 bin/go_build_main_go ckfpp -c 10240000
    [going@dev cuckoohui]$ dlv exec bin/go_build_main_go -- ckfpp -c 10240000
    Type 'help' for list of commands.
    
    

    在开始的main函数添加断点

    (dlv) b main.main
    Breakpoint 1 (enabled) set at 0x605813 for main.main() ./main.go:12
    
    

    (more…)