Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

golang中的最佳实践 #76

Open
BruceChen7 opened this issue Aug 16, 2023 · 0 comments
Open

golang中的最佳实践 #76

BruceChen7 opened this issue Aug 16, 2023 · 0 comments

Comments

@BruceChen7
Copy link
Owner

BruceChen7 commented Aug 16, 2023

参考资料

init 使用

  • 在 golang 中的每个模块能,定义 init 函数,用来初始化该包内的全局变量,
    package main
    import "fmt"
    // 另外一个文件
    func init() {
        fmt.Println("init 1")
    }
    func init() {
        fmt.Println("init 2")
    }
    func main() {
        fmt.Println("main")  // 输出 init 1, init2  main
    }

错误类型

  • 声明错误的选择很少
  • 调用者是否需要匹配错误以便能处理它?
    • 如果是,必须通过声明顶级错误变量自定义类型来支持 errors.Is 或 errors.As 函数
  • 错误消息是否为静态字符串,还是需要上下文信息的动态字符串?
    • 如果是静态字符串,能使用 errors.New
    • 但对于后者,必须使用 fmt.Errorf 或自定义错误类型
  • 是否正在传递由下游函数返回的新错误
错误匹配? 错误消息 指导
No static errors.New
No dynamic fmt.Errorf
Yes static top-level var with e rrors.New
Yes dynamic custom error type

几种使用场景

  • 无错误匹配,静态错误
    // package foo
    
    func Open(file string) error {
      return fmt.Errorf("file %q not found", file)
    }
    
    // package bar
    
    if err := foo.Open("testfile.txt"); err != nil {
      // Can't handle the error.
      panic("unknown error")
    }
  • 有错误匹配,动态错误
    // package foo
    func Open(file string) error {
      return fmt.Errorf("file %q not found", file)
    }
    
    // package bar
    
    if err := foo.Open("testfile.txt"); err != nil {
      // Can't handle the error.
      panic("unknown error")
    }
  • 如果从包中导出错误变量或类型,它们将成为包的公共 API 的一部分

错误包装

  • 如果调用其他方法时出现错误,通常有三种处理方式能选择
    • 将原始错误原样返回
    • 使用 fmt.Errorf 搭配 %w 将错误添加进上下文后返回
    • 使用 fmt.Errorf 搭配 %v 将错误添加进上下文后返回
  • 如果没有要添加的其他上下文,则按原样返回原始错误
    • 这将保留原始错误类型和消息。
    • 非常适合底层错误消息有足够的信息来追踪它来自哪里的错误
  • 否则,尽可能在错误消息中添加上下文
    • 这样就不会出现诸如连接被拒绝之类的模糊错误,会收到更多有用的错误,例如调用服务 foo:连接被拒绝
    • 使用 fmt.Errorf 为你的错误添加上下文,根据调用者是否应该能够匹配和提取根本原因
    • 在 %w 或 %v 动词之间进行选择。
    • 如果调用者应该能访问底层错误,请使用 %w
      • 对于大多数包装错误,这是一个很好的默认值,但请注意,调用者可能会开始依赖此行为。
      • 因此,对于包装错误是已知 var 或类型的情况请将其作为函数契约的一部分进行记录和测试
    • 使用 %v 来混淆底层错误。
      • 调用者将无法匹配它,但如果需要,能在将来切换到 %w
      • 在为返回的错误添加上下文时,通过避免使用"failed to"之类的短语来保持上下文简洁
      • 当错误通过堆栈向上渗透时,它会一层一层被堆积起来:
        // good
        s, err := store.New()
        if err != nil {
            return fmt.Errorf(
                "new store: %w", err)
        }
        // bad
        s, err := store.New()
        if err != nil {
            return fmt.Errorf(
                "failed to create new store: %w", err)
        }

错误命名

  • 对于存储为全局变量的错误值,根据是否导出,使用前缀 Err 或 err
        var (
      // 导出以下两个错误,以便此包的用户能将它们与 errors.Is 进行匹配。
    
      ErrBrokenLink = errors.New("link is broken")
      ErrCouldNotOpen = errors.New("could not open")
    
      // 这个错误没有被导出,因为不想让它成为公共 API 的一部分。可能仍然在带有错误的包内使用它。
    
      errNotFound = errors.New("not found")
    )
  • 对于自定义错误类型,能使用后缀 Error
    // 同样,这个错误被导出,以便这个包的用户能将它与 errors.As 匹配。
    type NotFoundError struct {
      File string
    }
    
    func (e *NotFoundError) Error() string {
      return fmt.Sprintf("file %q not found", e.File)
    }
    
    // 并且这个错误没有被导出,因为不想让它成为公共 API 的一部分。
    // 仍然能在带有 errors.As 的包中使用它。
    type resolveError struct {
      Path string
    }
    
    func (e *resolveError) Error() string {
      return fmt.Sprintf("resolve %q", e.Path)
    }
  • %q 占位符,它将引用一个值并将其格式化为带有引号的字符串

一次处理错误

  • 调用方从被调用方接收到错误时,能根据对错误的了解,以各种不同的方式进行处理。
  • 其中包括但不限于:
    • 如果被调用者约定定义了特定的错误,则将错误与 errors.Is 或 errors.As 匹配,并以不同的方式处理分支
      • 如果错误是可恢复的,则记录错误并正常降级
      • 如果该错误表示特定于域的故障条件,则返回定义明确的错误
  • 无论调用方如何处理错误,它通常都应该只处理每个错误一次
  • 调用方不应该记录错误然后返回,因为调用方也可能处理错误
    // 记录错误并将其返回
    // 堆栈中的调用程序可能会对该错误采取类似的操作。
    // 这样做会在应用程序日志中造成大量噪音,但收效甚微
    u, err := getUser(id)
    if err != nil {
      // BAD: See description
      log.Printf("Could not get user %q: %v", id, err)
      return err
    }
    // good
    // 将错误换行并返回
    // 堆栈中更靠上的调用程序将处理该错误。
    // 使用%w可确保它们能将错误与 errors.Is 或 errors.As 相匹配
    u, err := getUser(id)
    if err != nil {
      return fmt.Errorf("get user %q: %w", id, err)
    }
    // good
    // 记录错误并正常降级
    // 如果操作不是绝对必要的,能通过从中恢复来提供降级但不间断的体验
    if err := emitMetrics(); err != nil {
      // Failure to write metrics should not
      // break the application.
      log.Printf("Could not emit metrics: %v", err)
    }
    // good
    // 匹配错误并适当降级
    // 如果被调用者在其约定中定义了一个特定的错误,并且失败是可恢复的,则匹配该错误案例并正常降级。
    // 对于所有其他案例,请包装错误并返回。
    // 堆栈中更靠上的调用程序将处理其他错误
    tz, err := getUserTimeZone(id)
    if err != nil {
      if errors.Is(err, ErrUserNotFound) {
        // User doesn't exist. Use UTC.
        tz = time.UTC
      } else {
        return fmt.Errorf("get user %q: %w", id, err)
      }
    }
  • 使用了 %w 占位符来指定包装错误。这个占位符告诉 fmt.Errorf 函数将后面的错误作为包装错误
  • 能在创建错误时保留原始错误的信息

优先使用 strconv 而不是 fmt

  • 将原语转换为字符串或从字符串转换时,strconv速度比fmt快。

避免字符串到字节的转换

// bad
for i := 0; i < b.N; i++ {
  w.Write([]byte("Hello world"))
}

// good
data := []byte("Hello world")
for i := 0; i < b.N; i++ {
  w.Write(data)
}

不要一劳永逸使用 goroutine

  • 每个 goroutine:
  • 必须有一个可预测的停止运行时间;或者
  • 必须有一种方法能向 goroutine 发出信号它应该停止
    // bad
    go func() {
      for {
        flush()
        time.Sleep(delay)
      }
    }()
    
    // good
    var (
      stop = make(chan struct{}) // 告诉 goroutine 停止
      done = make(chan struct{}) // 告诉 goroutine 退出了
    )
    go func() {
      defer close(done)
      ticker := time.NewTicker(delay)
      defer ticker.Stop()
      for {
        select {
        case <-tick.C:
          flush()
        case <-stop:
          return
        }
      }
    }()
    // 其它...
    close(stop)  // 指示 goroutine 停止
    <-done       // and wait for it to exit

等待 goroutine 退出

  • 一个由系统生成的 goroutine,必须有一种方案能等待 goroutine 的退出。
  • 有两种常用的方法能做到这一点
    var wg sync.WaitGroup
    for i := 0; i < N; i++ {
      wg.Add(1)
      go func() {
        defer wg.Done()
        // ...
      }()
    }
    
    // To wait for all to finish:
    wg.Wait()
  • 另外一种是下面的:
    done := make(chan struct{})
    go func() {
      defer close(done)
      // ...
    
    }()
    
    // To wait for the goroutine to finish:
    <-done

包名

  • 全部小写。没有大写或下划线。
  • 大多数使用命名导入的情况下,不需要重命名。
  • 简短而简洁。记住,在每个使用的地方都完整标识了该名称。
  • 不用复数。例如 net/url,而不是 net/urls。
  • 不要用 common,util,shared 或 lib。这些是不好的,信息量不足的名称。

局部变量的声明

  • 如果将变量明确设置为某个值,则应使用短变量声明形式 (:=)。

    // bad
    var s = "foo"
    // good
    s := "foo"

nil 是一个有效的 slice

  • 不应明确返回长度为零的切片。
  • 应该返回 nil 来代替
  • 要检查切片是否为空,请始终使用 len(s) == 0。而非 nil
  • 零值切片(用 var 声明的切片)可立即使用,无需调用make() 创建

Channel 的 size 要么是 1,要么是无缓冲的

  • channel 通常 size 应为 1 或是无缓冲的。
  • 默认情况下,channel 是无缓冲的,其 size 为零。
  • 任何其他 size 都必须经过严格的审查。
  • 需要考虑如何确定大小,考虑是什么阻止了 channel 在高负载下和阻塞写时的写入,以及当这种情况发生时系统逻辑有哪些变化。
    // 大小:1
    c := make(chan int, 1) // 或者
    // 无缓冲 channel,大小为 0
    c := make(chan int)

在边界处拷贝 Slices 和 Maps

  • slices 和 maps 包含了指向底层数据的指针,因此在需要复制它们时要特别注意。
  • 当 map 或 slice 作为函数参数传入时,如果存储了对它们的引用,则用户能对其进行修改
    // bad
    func (d *Driver) SetTrips(trips []Trip) {
      d.trips = trips
    }
    
    trips := ...
    d1.SetTrips(trips)
    
    // 你是要修改 d1.trips 吗?
    trips[0] = ...
    
    // good
    func (d *Driver) SetTrips(trips []Trip) {
      d.trips = make([]Trip, len(trips))
      copy(d.trips, trips)
    }
    
    trips := ...
    d1.SetTrips(trips)
    
    // 这里修改 trips[0],但不会影响到 d1.trips
    trips[0] = ...

使用 defer 来释放资源

  • 使用 defer 释放资源,诸如文件和锁。
    // bad
    p.Lock()
    if p.count < 10 {
      p.Unlock()
      return p.count
    }
    
    p.count++
    newCount := p.count
    p.Unlock()
    
    return newCount
    // 当有多个 return 分支时,很容易遗忘 unlock
    
    
    // good
    p.Lock()
    defer p.Unlock()
    
    if p.count < 10 {
      return p.count
    }
    
    p.count++
    return p.count

避免字符串到字节的转换

  • 不要反复从固定字符串创建字节 slice
    // bad
    for i := 0; i < b.N; i++ {
      w.Write([]byte("Hello world"))
    }
    // good
    data := []byte("Hello world")
    for i := 0; i < b.N; i++ {
      w.Write(data)
    }
    

尽量初始化时指定 Map 容量

make(map[T1]T2, hint)
// bad
m := make(map[string]os.FileInfo)

files, _ := ioutil.ReadDir("./files")
for _, f := range files {
    m[f.Name()] = f
}

// good
files, _ := ioutil.ReadDir("./files")
m := make(map[string]os.FileInfo, len(files))
for _, f := range files {
    m[f.Name()] = f
}

避免使用全局变量

  • 使用选择依赖注入方式避免改变全局变量
  • 既适用于函数指针又适用于其他值类型
    // bad
    // sign.go
    var _timeNow = time.Now
    func sign(msg string) string {
      now := _timeNow()
      return signWithTime(msg, now)
    }
    // sign_test.go
    func TestSign(t *testing.T) {
      oldTimeNow := _timeNow
      _timeNow = func() time.Time {
        return someFixedTime
      }
      defer func() { _timeNow = oldTimeNow }()
      assert.Equal(t, want, sign(give))
    }
    
    // bad
    // sign.go
    type signer struct {
      now func() time.Time
    }
    func newSigner() *signer {
      return &signer{
        now: time.Now,
      }
    }
    func (s *signer) Sign(msg string) string {
      now := s.now()
      return signWithTime(msg, now)
    
    }
    // sign_test.go
    func TestSigner(t *testing.T) {
      s := newSigner()
      s.now = func() time.Time {
        return someFixedTime
      }
      assert.Equal(t, want, s.Sign(give))
    }

不要 panic

  • 在生产环境中运行的代码必须避免出现 panic
  • panic 是cascading failures级联失败的主要根源。
  • 如果发生错误,该函数必须返回错误,并允许调用方决定如何处理它
    // bad
    func foo(bar string) {
      if len(bar) == 0 {
        panic("bar must not be empty")
      }
      // ...
    }
    
    func main() {
      if len(os.Args) != 2 {
        fmt.Println("USAGE: foo <bar>")
        os.Exit(1)
      }
      foo(os.Args[1])
    }
    
    // good
    func foo(bar string) error {
      if len(bar) == 0 {
        return errors.New("bar must not be empty")
      }
      // ...
      return nil
    }
    
    func main() {
      if len(os.Args) != 2 {
        fmt.Println("USAGE: foo <bar>")
        os.Exit(1)
      }
      if err := foo(os.Args[1]); err != nil {
        panic(err)
      }
    }

避免在公共结构中嵌入类型

  • 这些嵌入的类型泄漏实现细节、禁止类型演化和模糊的文档。

  • 假设共享的AbstractList实现了多种列表类型,请避免在具体的列表实现中嵌入 AbstractList。

  • 相反,只需手动将方法写入具体的列表,该列表将委托给抽象列表。

    // bad
    type AbstractList struct {}
    // 添加将实体添加到列表中。
    func (l *AbstractList) Add(e Entity) {
      // ...
    }
    // 移除从列表中移除实体。
    func (l *AbstractList) Remove(e Entity) {
      // ...
    }
    
    // ConcreteList 是一个实体列表。
    type ConcreteList struct {
      *AbstractList
    }
    
    
    // good
    // ConcreteList 是一个实体列表。
    type ConcreteList struct {
      list *AbstractList
    }
    // 添加将实体添加到列表中。
    func (l *ConcreteList) Add(e Entity) {
      return l.list.Add(e)
    }
    // 移除从列表中移除实体。
    func (l *ConcreteList) Remove(e Entity) {
      return l.list.Remove(e)
    }
  • Go 允许类型嵌入作为继承和组合之间的折衷。

  • 外部类型获取嵌入类型的方法的隐式副本

  • 默认情况下,这些方法委托给嵌入实例的同一方法

  • 结构还获得与类型同名的字段

    • 所以,如果嵌入的类型是 public,那么字段是 public。
    • 为了保持向后兼容性,外部类型的每个未来版本都必须保留嵌入类型
  • 很少需要嵌入类型

    • 这是一种方便,能帮助避免编写冗长的委托方法
    • 即使嵌入兼容的抽象列表 interface,而不是结构体,这将为开发人员提供更大的灵活性来改变未来,但仍然泄露了具体列表使用抽象实现的细节
      // bad
      // AbstractList 是各种实体列表的通用实现。
      type AbstractList interface {
        Add(Entity)
        Remove(Entity)
      }
      // ConcreteList 是一个实体列表。
      type ConcreteList struct {
        AbstractList
      }
      // good
      // ConcreteList 是一个实体列表。
      type ConcreteList struct {
        list *AbstractList
      }
      // 添加将实体添加到列表中。
      func (l *ConcreteList) Add(e Entity) {
        return l.list.Add(e)
      }
      // 移除从列表中移除实体。
      func (l *ConcreteList) Remove(e Entity) {
        return l.list.Remove(e)
      }
  • 无论是使用嵌入式结构还是使用嵌入式接口,嵌入式类型都会限制类型的演化.

    • 向嵌入式接口添加方法是一个破坏性的改变
    • 删除嵌入类型是一个破坏性的改变。
    • 即使使用满足相同接口的替代方法替换嵌入类型,也是一个破坏性的改变
  • 尽管编写这些委托方法是乏味的,但是额外的工作隐藏了实现细节,留下了更多的更改机会,

  • 消除了在文档中发现完整列表接口的间接性操作

判断接口类型的变量是否相等

func main() {
    printNonEmptyInterface1()
}

type T struct {
    name string
}
func (t T) Error() string {
    return "bad error"
}
func printNonEmptyInterface1() {
    var err1 error    // 非空接口类型
    var err1ptr error // 非空接口类型
    var err2 error    // 非空接口类型
    var err2ptr error // 非空接口类型

    err1 = T{"eden"}
    err1ptr = &T{"eden"}

    err2 = T{"eden"}
    err2ptr = &T{"eden"}

    println("err1:", err1)
    println("err2:", err2)
    println("err1 = err2:", err1 == err2)             // true
    println("err1ptr:", err1ptr)
    println("err2ptr:", err2ptr)
    println("err1ptr = err2ptr:", err1ptr == err2ptr) // false
}
  • 具体 interface 的接口表示
    // $GOROOT/src/runtime/runtime2.go
    type iface struct { // 非空接口类型的运行时表示
        tab  *itab
        data unsafe.Pointer
    }
    
    type eface struct { // 空接口类型的运行时表示
        _type *_type
        data  unsafe.Pointer
    }
    
    // $GOROOT/src/runtime/print.go
    func printeface(e eface) {
        print("(", e._type, ",", e.data, ")")
    }
    
    func printiface(i iface) {
        print("(", i.tab, ",", i.data, ")")
    }
  • 第一个字段功能相似,都是表示类型信息的,而第二个指针字段的功能也相同,都是指向当前赋值给该接口类型变量的动态类型变量的值,
  • 从 printeface 和 printiface 的实现能看出 println 会将接口类型变量的类型信息与 data 信息输出,代码如下:
    err1: (0x10c6cc0,0xc000092f20)
    err2: (0x10c6cc0,0xc000092f40)
    err1 = err2: true
    err1ptr: (0x10c6c40,0xc000092f50)
    err2ptr: (0x10c6c40,0xc000092f30)
    err1ptr = err2ptr: false
  • 为了找到真正原因,用 lensm 工具以图形化方式展示出汇编与源 Go 代码的对应关系:
    • img
    • img
  • Go 都会调用 runtime.ifaceeq 来进行比较
    // $GOROOT/src/runtime/alg.go
    func efaceeq(t *_type, x, y unsafe.Pointer) bool {
          if t == nil {
              return true
          }
          eq := t.equal
          if eq == nil {
              panic(errorString("comparing uncomparable type " + t.string()))
          }
          if isDirectIface(t) {
              // Direct interface types are ptr, chan, map, func, and single-element structs/arrays thereof.
              // Maps and funcs are not comparable, so they can't reach here.
              // Ptrs, chans, and single-element items can be compared directly using ==.
              return x == y
          }
          return eq(x, y)
    }
    
    func ifaceeq(tab *itab, x, y unsafe.Pointer) bool {
        if tab == nil {
            return true
        }
        t := tab._type
        eq := t.equal
        if eq == nil {
            panic(errorString("comparing uncomparable type " + t.string()))
        }
        if isDirectIface(t) {
            // See comment in efaceeq.
            return x == y
        }
        return eq(x, y)
    }

这回对于接口类型变量的相等性判断一目了然了 (由 efaceeq 中 isDirectIface 函数上面的注释可见)!

  • err1 和 err2 两个接口变量的动态类型都是 T,因此比较的是 data 指向的内存块的值,虽然 err1 和 err2 的 data 字段指向的是两个内存块,但这两个内存块中的 T 对象值相同 (实质就是一个 string),因此 err1 == err2 为 true;
  • err1ptr 和 err2ptr 两个接口变量的动态类型都是*T,因此比较的直接就是 data 的值,显然data 值不同,因此 err1ptr == err2ptr 为 false(指针值的比较)。

常见的数据竞争

循环索引变量捕获导致的 data race

for _, job := range jobs {
    go func() {
        ProcessJob(job)
    }()
}

由于错误变量捕获导致的 data race

x, err := Foo()
if err != nil {
}
go func() {
    var y int
    y, err = Bar()
    if err != nil {
    }
}()
var z int
z, err = Bar()
if err != nil {
}

由于命名的返回变量被捕获,导致数据 data race。

func NamedReturnCallee(result int) {
    result = 0
    if .. {
        return // 等价于 return 10
    }
    go func() {
        ... = result  // data race
    }
    return 20 // 等价于 result = 20
}

slice 是令人困惑的类型,会产生难以察觉的的 data race

func ProcessAll(uuids []string) {
    var results []string
    var mutex sync.Mutex
    safeAppend = func (res string) {
        mutex.Lock()
        results = append(result, res)
        mutex.Unlock()
    }
    for _, uuid := range uuids {
        go func(id string, results string) {
            res := Foo(id)
            safeAppend(res)
        }
    }(uuid, results) // 这里有 data race
}

对 map 并发读写导致了频繁的 data race

func processOrders(uuids []string) error {
    var errMap = make(map[string]error)
    go func(uuid string) {
        orderHandle, err := GetOrder(uuid)
        if err != nil {
            errMap[uuid] = err // data race
            return
        }
    }(uuid)
    return combineError(errMap) // data race
}

混合使用 channel 和共享内存使代码变得复杂,容易发生 data race

func (f *Future) Start() {
    go func() {
        resp, err := f.f()
        f.response = resp
        f.err = err // data race
        f.ch <- 1
    }()
}
func (f *Future) Wait(ctx context.Context) error {
    select {
    case <- f.ch
            return nil
    case <- ctx.Done()
        f.err = ErrCancelled // data race

    }
}

sync.WaitGroup 不正确的 Add 或者 Done 产生 data race

func WaitExample(itemIds []int) int {
    wg sync.WaitGroup
    results := make([]int, len(itemIds))
    for i := 0; i < len(itemIds); i++ {
        go (idx int) {
            wg.Add(1) // data race
            results[idx] = ..
            wg.Done()
        }(i)
    }
    wg.Wait() // data race
}

func Do() {
    var wg sync.WaitGroup
    go func() {
        defer clean(&locationErr) // locationErr data race
        defer wg.Done()
        // ...
    }
    wg.Wait()
    if locationErr != nil { // data race
    }
}

unsafe.Pointer

使用 go vet 来保证代码的严谨性

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    // An array of contiguous uint32 values stored in memory.
    arr := []uint32{1, 2, 3}

    // The number of bytes each uint32 occupies: 4.
    const size = unsafe.Sizeof(uint32(0))

    // Take the initial memory address of the array and begin iteration.
    p := uintptr(unsafe.Pointer(&arr[0]))
    for i := 0; i < len(arr); i++ {
        // Print the integer that resides at the current address and then
        // increment the pointer to the next value in the array.
        fmt.Printf("%d ", (*(*uint32)(unsafe.Pointer(p))))
        p += size
    }
}

表面是会输出 1 2 3,但是使用 go vet

go vet .
# github.com/mdlayher/example
./main.go:20:33: possible misuse of unsafe.Pointer

规则说明:

Converting a Pointer to a uintptr produces the memory address of the value pointed at, as an integer. The usual use for such a uintptr is to print it.

Conversion of a uintptr back to Pointer is not valid in general.

A uintptr is an integer, not a reference. Converting a Pointer to a uintptr creates an integer value with no pointer semantics. Even if a uintptr holds the address of some object, the garbage collector will not update that uintptr’s value if the object moves, nor will that uintptr keep the object from being reclaimed

分裂出来的代码是

p := uintptr(unsafe.Pointer(&arr[0]))

// What happens if there's a garbage collection here?
fmt.Printf("%d ", (*(*uint32)(unsafe.Pointer(p))))

因为把地址值存到了 p 中,没有立即使用,那么可能其中发生了垃圾回收。使用将 unsafe.Point 类型指针转换成 uintpr 后,是不能狗直接转回到 unsafe.Pointer 的,但是只有一种情况例外

If p points into an allocated object, it can be advanced through the object by conversion to uintptr, addition of an offset, and conversion back to Pointer.

也就是执行指针的算术运算逻辑

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    // An array of contiguous uint32 values stored in memory.
    arr := []uint32{1, 2, 3}

    // The number of bytes each uint32 occupies: 4.
    const size = unsafe.Sizeof(uint32(0))

    for i := 0; i < len(arr); i++ {
        // Print an integer to the screen by:
        //   - taking the address of the first element of the array
        //   - applying an offset of (i * 4) bytes to advance into the array
        //   - converting the uintptr back to *uint32 and dereferencing it to
        //     print the value
        fmt.Printf("%d ", *(*uint32)(unsafe.Pointer(
            uintptr(unsafe.Pointer(&arr[0])) + (uintptr(i) * size),
        )))
    }
}

这样使用后,就正常了

go vet .

golang 中的 socket 编程

socket 无数据

如果对方未发送数据到 socket,接收方 (Server) 会阻塞在 Read 操作上,这和前面提到的“模型”原理是一致的。执行该 Read 操作的 goroutine 也会被挂起。runtime 会监视该 socket,直到其有数据才会重新调度该 socket 对应的 Goroutine 完成 read

有部分数据

如果 socket 中有部分数据,且长度小于一次 Read 操作所期望读出的数据长度,那么 Read 将会成功读出这部分数据并返回,而不是等待所有期望数据全部读取后再返回

Socket 中有足够数据

如果 socket 中有数据,且长度大于等于一次 Read 操作所期望读出的数据长度,那么 Read 将会成功读出这部分数据并返回。这个情景是最符合对 Read 的期待的了:Read 将用 Socket 中的数据将传入的 slice 填满后返回:n = 10, err = nil。

socket 关闭

如果 client 端主动关闭了 socket,那么 Server 的 Read 将会读到什么呢?这里分为“有数据关闭”和“无数据关闭”。

#type/golang #public

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant