gpt4 book ai didi

Golang map len() 在 map 为空时报告 > 0

转载 作者:IT王子 更新时间:2023-10-29 00:55:59 25 4
gpt4 key购买 nike

短篇小说:

我遇到一个问题,以前有数据但现在应该为空的 map 报告了 > 0 的 len(),即使它看起来是空的,我不知道为什么。

长话短说:

我需要一次处理多个设备。每个设备 都可以有许多消息。 Go 的并发性似乎是一个显而易见的起点,所以我编写了一些代码来处理它,它似乎大部分进展顺利。然而……

我为每个设备 启动了一个 goroutine。在 main() 函数中,我有一个包含每个设备map。当收到消息时,我会检查设备 是否已经存在,如果不存在,我会创建它,将其存储在映射中,然后将消息传递到设备的接收缓冲 channel 。

效果很好,每个设备都得到了很好的处理。但是,我需要设备(及其 goroutine)在预设时间内未收到任何消息时终止。我通过检查 goroutine 本身自收到最后一条消息以来经过了多长时间来完成此操作,如果 goroutine 被认为陈旧,则接收 channel 将关闭。但是如何从 map 上移除呢?

所以我传入了一个指向 map 的指针,我让 goroutine 从 map 中删除 device 并在返回之前关闭接收 channel 。但问题是,最后我发现 len() 函数返回一个 > 0 的值,但是当我输出 map 本身时,我发现它是空的。

我已经编写了一个玩具示例来尝试复制错误,实际上我看到 len() 在 map 明显为空时报告 > 0。我上次尝试时看到了 10。之前的时间是 14。之前的时间是 53。

所以我可以复制错误,但我不确定错误是在我身上还是在 Go 上。 len() 中显然没有项目时如何报告 > 0?

这是我如何复制的示例。我正在使用 Go v1.5.1 windows/amd64

就我而言,这里有两件事:

  1. 我是否正确地管理了 goroutines(可能不是)并且
  2. 为什么 len(m) 中没有项目时报告 > 0?

谢谢大家

示例代码:

package main

import (
"log"
"os"
"time"
)

const (
chBuffSize = 100 // How large the thing's channel buffer should be
thingIdleLifetime = time.Second * 5 // How long things can live for when idle
thingsToMake = 1000 // How many things and associated goroutines to make
thingMessageCount = 10 // How many messages to send to the thing
)

// The thing that we'll be passing into a goroutine to process -----------------
type thing struct {
id string
ch chan bool
}

// Go go gadget map test -------------------------------------------------------
func main() {
// Make all of the things!
things := make(map[string]thing)
for i := 0; i < thingsToMake; i++ {
t := thing{
id: string(i),
ch: make(chan bool, chBuffSize),
}
things[t.id] = t

// Pass the thing into it's own goroutine
go doSomething(t, &things)

// Send (thingMessageCount) messages to the thing
go func(t thing) {
for x := 0; x < thingMessageCount; x++ {
t.ch <- true
}
}(t)
}

// Check the map of things to see whether we're empty or not
size := 0
for {
if size == len(things) && size != thingsToMake {
log.Println("Same number of items in map as last time")
log.Println(things)
os.Exit(1)
}
size = len(things)
log.Printf("Map size: %d\n", size)
time.Sleep(time.Second)
}
}

// Func for each goroutine to run ----------------------------------------------
//
// Takes two arguments:
// 1) the thing that it is working with
// 2) a pointer to the map of things
//
// When this goroutine is ready to terminate, it should remove the associated
// thing from the map of things to clean up after itself
func doSomething(t thing, things *map[string]thing) {
lastAccessed := time.Now()
for {
select {
case <-t.ch:
// We received a message, so extend the lastAccessed time
lastAccessed = time.Now()
default:
// We haven't received a message, so check if we're allowed to continue
n := time.Now()
d := n.Sub(lastAccessed)
if d > thingIdleLifetime {
// We've run for >thingIdleLifetime, so close the channel, delete the
// associated thing from the map and return, terminating the goroutine
close(t.ch)
delete(*things, string(t.id))
return
}
}

// Just sleep for a second in each loop to prevent the CPU being eaten up
time.Sleep(time.Second)
}
}

补充一下;在我的原始代码中,这是永远循环的。该程序旨在监听 TCP 连接并接收和处理数据,因此检查映射计数的函数在它自己的 goroutine 中运行。然而,即使 map len() 检查在 main() 函数中并且它被设计为处理初始数据突发和然后跳出循环。

更新 2015/11/23 15:56 UTC

我在下面重构了我的示例。我不确定我是否误解了@RobNapier,但这效果更好。但是,如果我将 thingsToMake 更改为更大的数字,比如 100000,那么我会收到很多这样的错误:

goroutine 199734 [select]:
main.doSomething(0xc0d62e7680, 0x4, 0xc0d64efba0, 0xc082016240)
C:/Users/anttheknee/go/src/maptest/maptest.go:83 +0x144
created by main.main
C:/Users/anttheknee/go/src/maptest/maptest.go:46 +0x463

我不确定问题是我要求 Go 做的事情太多,还是我对解决方案的理解有点困惑。有什么想法吗?

package main

import (
"log"
"os"
"time"
)

const (
chBuffSize = 100 // How large the thing's channel buffer should be
thingIdleLifetime = time.Second * 5 // How long things can live for when idle
thingsToMake = 10000 // How many things and associated goroutines to make
thingMessageCount = 10 // How many messages to send to the thing
)

// The thing that we'll be passing into a goroutine to process -----------------
type thing struct {
id string
ch chan bool
done chan string
}

// Go go gadget map test -------------------------------------------------------
func main() {
// Make all of the things!
things := make(map[string]thing)

// Make a channel to receive completion notification on
doneCh := make(chan string, chBuffSize)

log.Printf("Making %d things\n", thingsToMake)
for i := 0; i < thingsToMake; i++ {
t := thing{
id: string(i),
ch: make(chan bool, chBuffSize),
done: doneCh,
}
things[t.id] = t

// Pass the thing into it's own goroutine
go doSomething(t)

// Send (thingMessageCount) messages to the thing
go func(t thing) {
for x := 0; x < thingMessageCount; x++ {
t.ch <- true
time.Sleep(time.Millisecond * 10)
}
}(t)
}
log.Printf("All %d things made\n", thingsToMake)

// Receive on doneCh when the goroutine is complete and clean the map up
for {
id := <-doneCh
close(things[id].ch)
delete(things, id)
if len(things) == 0 {
log.Printf("Map: %v", things)
log.Println("All done. Exiting")
os.Exit(0)
}
}
}

// Func for each goroutine to run ----------------------------------------------
//
// Takes two arguments:
// 1) the thing that it is working with
// 2) the channel to report that we're done through
//
// When this goroutine is ready to terminate, it should remove the associated
// thing from the map of things to clean up after itself
func doSomething(t thing) {
timer := time.NewTimer(thingIdleLifetime)
for {
select {
case <-t.ch:
// We received a message, so extend the timer
timer.Reset(thingIdleLifetime)
case <-timer.C:
// Timer returned so we need to exit now
t.done <- t.id
return
}
}
}

更新 2015/11/23 16:41 UTC

完成的代码似乎工作正常。如果有任何可以改进的地方,请随时告诉我,但这行得通( sleep 是为了看到进步,否则太快了!)

package main

import (
"log"
"os"
"strconv"
"time"
)

const (
chBuffSize = 100 // How large the thing's channel buffer should be
thingIdleLifetime = time.Second * 5 // How long things can live for when idle
thingsToMake = 100000 // How many things and associated goroutines to make
thingMessageCount = 10 // How many messages to send to the thing
)

// The thing that we'll be passing into a goroutine to process -----------------
type thing struct {
id string
receiver chan bool
done chan string
}

// Go go gadget map test -------------------------------------------------------
func main() {
// Make all of the things!
things := make(map[string]thing)

// Make a channel to receive completion notification on
doneCh := make(chan string, chBuffSize)

log.Printf("Making %d things\n", thingsToMake)

for i := 0; i < thingsToMake; i++ {
t := thing{
id: strconv.Itoa(i),
receiver: make(chan bool, chBuffSize),
done: doneCh,
}
things[t.id] = t

// Pass the thing into it's own goroutine
go doSomething(t)

// Send (thingMessageCount) messages to the thing
go func(t thing) {
for x := 0; x < thingMessageCount; x++ {
t.receiver <- true
time.Sleep(time.Millisecond * 100)
}
}(t)
}
log.Printf("All %d things made\n", thingsToMake)

// Check the `len()` of things every second and exit when empty
go func() {
for {
time.Sleep(time.Second)
m := things
log.Printf("Map length: %v", len(m))
if len(m) == 0 {
log.Printf("Confirming empty map: %v", things)
log.Println("All done. Exiting")
os.Exit(0)
}
}
}()

// Receive on doneCh when the goroutine is complete and clean the map up
for {
id := <-doneCh
close(things[id].receiver)
delete(things, id)
}
}

// Func for each goroutine to run ----------------------------------------------
//
// When this goroutine is ready to terminate it should respond through t.done to
// notify the caller that it has finished and can be cleaned up. It will wait
// for `thingIdleLifetime` until it times out and terminates on it's own
func doSomething(t thing) {
timer := time.NewTimer(thingIdleLifetime)
for {
select {
case <-t.receiver:
// We received a message, so extend the timer
timer.Reset(thingIdleLifetime)
case <-timer.C:
// Timer expired so we need to exit now
t.done <- t.id
return
}
}
}

最佳答案

map 不是线程安全的。您无法安全地访问多个 goroutine 上的 map。您可以破坏 map ,正如您在本例中看到的那样。

与其允许 goroutine 修改 map ,goroutine 应该在返回之前将其标识符写入 channel 。主循环应该监视该 channel ,并且当标识符返回时,应该从 map 中删除该元素。

您可能想要阅读 Go 并发模式。特别是,您可能想看看 Fan-out/Fan-in .查看底部的链接。 Go 博客有很多关于并发的信息。

请注意,您的 goroutine 正忙于等待检查超时。没有理由这样做。你“ sleep (1 秒)”)这一事实应该是一个错误的线索。相反,请查看 time.Timer,它会给你一个 chan,它会在一段时间后收到一个值,你可以重置它。


您的问题是如何将数字转换为字符串:

        id:   string(i),

这会使用 i 作为 rune (int32) 创建一个字符串。例如 string(65)A。一些不相​​等的 rune 解析为相等的字符串。您遇到碰撞并关闭同一个 channel 两次。参见 http://play.golang.org/p/__KpnfQc1V

你的意思是:

        id:   strconv.Itoa(i),

关于Golang map len() 在 map 为空时报告 > 0,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33872157/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com