1

I have a struct called Hub with a Run() method which is executed in its own goroutine. This method sequentially handles incoming messages. Messages arrive concurrently from multiple producers (separate goroutines). Of course I use a channel to accomplish this task. But now I want to hide the Hub behind an interface to be able to choose from its implementations. So, using a channel as a simple Hub's field isn't appropriate.

package main
import "fmt"
import "time"

type Hub struct {
    msgs chan string
}
func (h *Hub) Run() {
    for {
        msg, hasMore := <- h.msgs
        if !hasMore {
            return
        }
        fmt.Println("hub: msg received", msg)
    }
}
func (h *Hub) SendMsg(msg string) {
    h.msgs <- msg
}

func send(h *Hub, prefix string) {
    for i := 0; i < 5; i++ {
        fmt.Println("main: sending msg")
        h.SendMsg(fmt.Sprintf("%s %d", prefix, i))
    }
}

func main() {
    h := &Hub{make(chan string)}
    go h.Run()
    for i := 0; i < 10; i++ {
        go send(h, fmt.Sprintf("msg sender #%d", i))
    }
    time.Sleep(time.Second)
}

So I've introduced Hub.SendMsg(msg string) function that just calls h.msgs <- msg and which I can add to the HubInterface. And as a Go-newbie I wonder, is it safe from the concurrency perspective? And if so - is it a common approach in Go?

Playground here.

Ivan Velichko
  • 6,348
  • 6
  • 44
  • 90
  • 3
    Yes. I'm not sure how else to answer, because I'm unsure what would lead you to believe it could be unsafe. – JimB Nov 12 '16 at 18:59
  • @JimB, actually, after thinking awhile how it works under the hood I realized that the answer is pretty obvious as far as writing to the channel is thread-safe. – Ivan Velichko Nov 12 '16 at 19:04
  • 1
    _A single channel may be used in send statements, receive operations, and calls to the built-in functions cap and len by any number of goroutines without further synchronization._ Fore more, see [If I am using channels properly should I need to use mutexes?](http://stackoverflow.com/questions/34039229/if-i-am-using-channels-properly-should-i-need-to-use-mutexes) – icza Nov 12 '16 at 21:48

2 Answers2

2

Channel send semantics do not change when you move the send into a method. Andrew's answer points out that the channel needs to be created with make to send successfully, but that was always true, whether or not the send is inside a method.

If you are concerned about making sure callers can't accidentally wind up with invalid Hub instances with a nil channel, one approach is to make the struct type private (hub) and have a NewHub() function that returns a fully initialized hub wrapped in your interface type. Since the struct is private, code in other packages can't try to initialize it with an incomplete struct literal (or any struct literal).

That said, it's often possible to create invalid or nonsense values in Go and that's accepted: net.IP("HELLO THERE BOB") is valid syntax, or net.IP{}. So if you think it's better to expose your Hub type go ahead.

twotwotwo
  • 28,310
  • 8
  • 69
  • 56
-1

Easy answer

Yes

Better answer

No

Channels are great for emitting data from unknown go-routines. They do so safely, however I would recommend being careful with a few parts. In the listed example the channel is created with the construction of the struct by the consumer (and not not by a consumer).

Say the consumer creates the Hub like the following: &Hub{}. Perfectly valid... Apart from the fact that all the invokes of SendMsg() will block for forever. Luckily you placed those in their own go-routines. So you're still fine right? Wrong. You are now leaking go-routines. Seems fine... until you run this for a period of time. Go encourages you to have valid zero values. In this case &Hub{} is not valid.

Ensuring SendMsg() won't block could be achieved via a select{} however you then have to decide what to do when you encounter the default case (e.g. throw data away). The channel could block for more reasons than bad setup too. Say later you do more than simply print the data after reading from the channel. What if the read gets very slow, or blocks on IO. You then will start pushing back on the producers.

Ultimately, channels allow you to not think much about concurrency... However if this is something of high-throughput, then you have quite a bit to consider. If it is production code, then you need to understand that your API here involves SendMsg() blocking.

poy
  • 10,063
  • 9
  • 49
  • 74
  • 2
    This is actively confusing; the question asked was whether channel send semantics are changed by moving the send into a function, and they aren't. `&Hub{}` is an invalid value of the type full-stop whether the channel send is inside a function call or not. – twotwotwo Nov 13 '16 at 04:59