简体   繁体   中英

golang string channel send/receive inconsistency

New to go. I'm using 1.5.1. I'm trying to accumulate a word list based on an incoming channel. However, my input channel (wdCh) is sometimes getting the empty string ("") during testing. I'm perplexed. I'd rather not have a test for the empty string before I add its accumulated count in my map. Feels like a hack to me.

package accumulator

import (
    "fmt"
    "github.com/stretchr/testify/assert"
    "testing"
)

var words map[string]int

func Accumulate(wdCh chan string, closeCh chan bool) {
    words = make(map[string]int)
    for {
        select {
        case word := <-wdCh:
            fmt.Printf("word = %s\n", word)
            words[word]++
        case <-closeCh:
            return
        }
    }
}

func pushWords(w []string, wdCh chan string) {
    for _, value := range w {
        fmt.Printf("sending word = %s\n", value)
        wdCh <- value
    }
    close(wdCh)
}

func TestAccumulate(t *testing.T) {
    sendWords := []string{"one", "two", "three", "two"}
    wMap := make(map[string]int)
    wMap["one"] = 1
    wMap["two"] = 2
    wMap["three"] = 1

    wdCh := make(chan string)
    closeCh := make(chan bool)

    go Accumulate(wdCh, closeCh)
    pushWords(sendWords, wdCh)

    closeCh <- true
    close(closeCh)

    assert.Equal(t, wMap, words)
}

Check out this article about channel-axioms . Looks like there's a race between closing wdCh and sending true on the closeCh channel.

So the outcome depends on what gets scheduled first between pushWords returning and Accumulate .

If TestAccumulate runs first, sending true on closeCh , then when Accumulate runs it picks either of the two channels since they can both be run because pushWords closed wdCh .

A receive from a closed channel returns the zero value immediately.

Until closedCh is signaled, Accumulate will randomly put one or more empty "" words in the map.

If Accumulate runs first then it's likely to put many empty strings in the word map as it loops until TestAccumulate runs and finally it sends a signal on closeCh .

An easy fix would be to move

close(wdCh)

after sending true on the closeCh . That way wdCh can't return the zero value until after you've signaled on the closeCh . Additionally, closeCh <- true blocks because closeCh doesn't have a buffer size, so wdCh won't get closed until after you've guaranteed that Accumulate has finished looping forever.

I think the reason is when you close the channle, "select" will although receive the signal.

So when you close "wdCh" in "func pushWords", the loop in Accumulate will receive signal from "<-wdCh". May be you should add some code to test the action after channel is closed!

for {
    select {
    case word, ok := <-wdCh:
        if !ok {
            fmt.Println("channel wdCh is closed!")
            continue
        }
        fmt.Printf("word = %s\n", word)
        words[word]++
    case <-closeCh:
        return
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM