Golang map len () reports> 0 when the map is empty

Story:

I have a problem when a card that previously had data but should now be empty reports len()of> 0, although it seems empty, and I have no idea why.

Longer story:

I need to process several devices at a time. Each device can have several messages. concurrency Go looked like an obvious place to start, so I wrote code to handle it, and it seems to work mostly very well. But...

I launched one channel for each device. In the function main()I have map, which contains each of the devices. When a message arrives, I check if the device exists, and if I do not create it, save it on the card, and then transfer the message to the device receiving the buffer channel.

This works great and every device handles beautifully. However, I need the device (and its goroutine) to stop when it does not receive any messages for a given period of time. I did this by checking goroutine myself, how much time has passed since the last message was received, and if goroutine is considered obsolete, then the receiving channel is closed. But how to remove from the card?

So, I passed the pointer to the card, and I have goroutine to remove the device from the card and close the receiving channel before returning. The problem is that in the end I found that the function len()returns a value> 0, but when I output the card, I see that it is empty.

I wrote a toy example to try to reproduce the error, and indeed, I see that it len()reports> 0 when the card is apparently empty. The last time I tried this, I saw 10. The time before this 14. The time before this, 53.

So, I can replicate the error, but I'm not sure if this error is to blame with me or with Go. As len()reported> 0, when there are no elements in it?

Here is an example of how I was able to replicate. I am using Go v1.5.1 windows / amd64

There are two things here:

  • I manage goroutines correctly (maybe not) and
  • len(m) report > 0, ?

:

package main

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

const (
    chBuffSize        = 100             // How large the thing 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 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. , 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

, , , . ?

package main

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

const (
    chBuffSize        = 100             // How large the thing 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 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

, . , , - , , ( , , !)

package main

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

const (
    chBuffSize        = 100             // How large the thing 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 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 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
        }
    }
}
+3
1

map . map goroutines. , .

, goroutine , goroutine . , , .

, Go concurrency. , Fan-out/Fan-in. . Go concurrency.

, goroutine , . . , " (1 )" ) . time.Timer, chan, , reset.


, :

        id:   string(i),

, i (int32). string(65) - A. . . . http://play.golang.org/p/__KpnfQc1V

:

        id:   strconv.Itoa(i),
+6

Source: https://habr.com/ru/post/1617073/


All Articles