【问题标题】:Go: How do I remove an element from a slice and modify it in memoryGo:如何从切片中删除元素并在内存中修改它
【发布时间】:2016-11-11 20:27:34
【问题描述】:

我正在尝试编写一个简单的服务器/客户端聊天程序以用于学习目的,但我被卡住了。我想让Leave 函数删除它传递的指针并更新结构中的切片,以便指针不再存在。但它不起作用。

例如:Input,Output

type Room struct {
    Name     string
    Visitors []*net.Conn
} 


func (r *Room) Leave(pc *net.Conn) {
    for i, pv := range r.Visitors {
        //found the connection we want to remove
        if pc == pv {
            fmt.Printf("Before %v\n",r.Visitors)
            r.Visitors = append(r.Visitors[:i], r.Visitors[i+1:]...)
            fmt.Printf("Before %v\n",r.Visitors)                
            return
        }
    }
}

【问题讨论】:

  • 删除逻辑是正确的(您的输出也证实了这一点)。问题可能是多个 goroutine 之间缺乏或不正确的同步(您的问题没有说明这一点),或者您的代码的其他部分可能存在您尚未发布的其他问题。
  • 确实没有努力确保它适用于多个 goroutine,但我想,让我们确保它适用于单个 goroutine,然后在它适用于一个线程时添加并发检查(也许这是一种不好的心态)。如果您有时间查看,我会将所有代码放在gist

标签: go struct slice side-effects


【解决方案1】:

删除逻辑是正确的(您的输出也证实了这一点)。问题是多个 goroutine 之间缺乏同步(你的问题没有说明这一点)。

您说(在您的评论中)您希望它首先使用 1 个 goroutine,然后再处理同步。但是你的代码已经使用了多个 goroutine,所以你不能这么奢侈:

//Let a goroutine Handle the connection
go handleConnection(conn)

多个 goroutine 正在读取和写入 Room.Visitors 切片,因此您别无选择,只能同步访问它。

一个例子是使用sync.RWLock:

utils.go:

type Room struct {
    mu       sync.RWLock
    Name     string
    Visitors []net.Conn
}


func (r *Room) Leave(c net.Conn) {
    r.mu.Lock()
    defer r.mu.Unlock()
    for i, v := range r.Visitors {
        //found the connection we want to remove
        if c == v {
            fmt.Printf("Before remove %v\n", r.Visitors)
            r.Visitors = append(r.Visitors[:i], r.Visitors[i+1:]...)
            fmt.Printf("After remove %v\n", r.Visitors)
            return
        }
    }
}

此外,当任何其他代码触及Room.Visitors 时,也要锁定代码(如果您只是阅读它,可以使用RWMutex.RLock())。

同样,您需要同步对多个 goroutine 读取/更改的所有变量的访问。

还要考虑将删除后释放的元素归零,否则底层数组仍将保留该值,从而阻止垃圾收集器正确释放它使用的内存。有关这方面的更多信息,请参阅Does go garbage collect parts of slices?

【讨论】:

  • 我为离开函数添加了互斥锁,并在删除它之前将它分配给元素(我认为这是你通过将被释放的元素归零的意思)但问题是在某种意义上仍然存在,我现在只是在我的切片中得到一堆 nil 元素。 example r.Visiting[i]=nil r.Visiting = append(r.Visiting[:i],r.Visiting([i+1:]...)
  • @AxelOlivecrona 否。删除元素后的可用空间将是最后一个元素(删除之前)。所以要将它归零,保存切片值(标题),删除元素,并将保存的切片中的最后一个值归零(在接口的情况下分配nil)。您没有将最后一个元素归零,只是将被删除的那个(并且很快将被覆盖),所以它没有真正的效果(除非可移动的是最后一个元素)。
  • @AxelOlivecrona 请确保同步所有其他共享变量访问,而不仅仅是Room.Visitors,这只是一个示例。
【解决方案2】:

您正在使用指向 Room 类型中的接口 (Visitors []*net.Conn) 的指针。您永远不需要指向接口值的指针。接口是值就像一个通用的内容持有者,它在内存中的表示不同于实现该接口的结构。

您应该简单地将Visitors 类型声明为接口:

type Room struct {
    Name     string
    Visitors []net.Conn // should not be a pointer to interface value
} 


func (r *Room) Leave(pc net.Conn) { // same thing as above
    for i, pv := range r.Visitors {
        // found the connection we want to remove
        // in the comparison below actual concrete types are being compared.
        // both these concrete types must be comparable (they can't be slices for example
        if pc == pv {
            r.Visitors = append(r.Visitors[:i], r.Visitors[i+1:]...)
            return
        }
    }
}

请注意,上面的比较 (pc == pv) 并不是那么微不足道。在这里阅读:https://golang.org/ref/spec#Comparison_operators

另外,参考这个问题:Why can't I assign a *Struct to an *Interface?

【讨论】:

猜你喜欢
  • 2014-06-25
  • 2016-03-10
  • 2016-09-16
  • 1970-01-01
  • 1970-01-01
  • 2019-02-05
  • 2014-11-28
相关资源
最近更新 更多