关于struct:Go:如何从切片中删除元素并在内存中对其进行修改

 2020-01-14 

Go: How do I remove an element from a slice and modify it in memory

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

示例:输入,输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
",r.Visitors)
            r.Visitors = append(r.Visitors[:i], r.Visitors[i+1:]...)
            fmt.Printf("Before %v
",r.Visitors)                
            return
        }
    }
}


删除逻辑是正确的(您的输出也确认了这一点)。问题是多个goroutine之间缺乏同步(您的问题对此一无所知)。

您说(在您的评论中)您希望它首先使用1个goroutine,然后再处理同步。但是您的代码已经使用了多个goroutine,因此您将无法获得如此奢侈的体验:

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

多个goroutine正在读写Room.Visitors slice,因此您别无选择,只能同步对其的访问。

一个示例是使用sync.RWLock

utils.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type Room struct {
    Name     string
    Visitors []net.Conn
    mux      sync.RWLock
}


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

同样,每当其他任何代码触摸Room.Visitors时,也请锁定该代码(如果您只是在阅读RWMutex.RLock(),则可以使用它)。

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

还应考虑将移除后释放的元素清零,否则基础数组仍将保留该值,从而阻止gargabe收集器正确释放其使用的内存。有关此的更多信息,请参阅去垃圾收集部分切片吗?


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

您应该只使用声明Visitors类型为接口即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

另外,请参考以下问题:为什么不能将* Struct分配给* Interface?