1
votes

In this ZeroMQ example,

// Multithreaded Hello World server.
// Uses Goroutines.  We could also use channels (a native form of
// inproc), but I stuck to the example.
//
// Author:  Brendan Mc.
// Requires: http://github.com/alecthomas/gozmq

package main

import (
    "fmt"
    zmq "github.com/alecthomas/gozmq"
    "time"
)

func main() {
    // Launch pool of worker threads
    for i := 0; i != 5; i = i + 1 {
        go worker()
    }

    // Prepare our context and sockets
    context, _ := zmq.NewContext()
    defer context.Close()

    // Socket to talk to clients
    clients, _ := context.NewSocket(zmq.ROUTER)
    defer clients.Close()
    clients.Bind("tcp://*:5555")

    // Socket to talk to workers
    workers, _ := context.NewSocket(zmq.DEALER)
    defer workers.Close()
    workers.Bind("ipc://workers.ipc")

    // connect work threads to client threads via a queue
    zmq.Device(zmq.QUEUE, clients, workers)

}

func worker() {
    context, _ := zmq.NewContext()
    defer context.Close()

    // Socket to talk to dispatcher
    receiver, _ := context.NewSocket(zmq.REP)
    defer receiver.Close()
    receiver.Connect("ipc://workers.ipc")

    for true {
        received, _ := receiver.Recv(0)
        fmt.Printf("Received request [%s]\n", received)

        // Do some 'work'
        time.Sleep(time.Second)

        // Send reply back to client
        receiver.Send([]byte("World"), 0)
    }
}

each goroutine has its own ZeroMQ Context. However, in the ZeroMQ guide, it says the following:

Create one ZeroMQ context at the start of your process, and pass that to all threads that you want to connect via inproc sockets.

Don't share ZeroMQ sockets between threads. ZeroMQ sockets are not threadsafe. Technically it's possible to migrate a socket from one thread to another but it demands skill. The only place where it's remotely sane to share sockets between threads are in language bindings that need to do magic like garbage collection on sockets.

I know that goroutines are not threads.

Instead, they live in threads. But I've also read that it's possible to have a shared object between goroutines.

So, why doesn't the context is shared between goroutines?

I think it'd consume a lot less space, because a context with a socket in it might be kinda big.

But even if it's not that consuming, why at all the context is not shared in this example?

2

2 Answers

0
votes

You can absolutely share objects between Goroutines, as long as those objects are threadsafe. The Go ZeroMQ bindings you are using is explicitly non-threadsafe. While a single Goroutine may not correspond to a single OS thread, it is not possible to guarantee that the Goroutines you spawn will live on the same OS thread. Their life cycle is managed dynamically by the Go run-time.

You can "share" your ZeroMQ socket between multiple Goroutines by using channels to synchronize access, or by using mutexes, where you lock access to your socket while a Goroutine is accessing it.

0
votes

Q: why isn't the context shared between goroutines? ( in the example )

Besides the ( rather extremist ) technical option to indeed share a Context instance ( under some well justified circumstances / occassions ), the demonstrated piece of code has focused on a Zero-Sharing, as a general principle, the one of a few Zero-Maxims, the ZeroMQ is so well known for.

There are advantages of a habit to employ a group of disjunct Context instances ( which one may also have several of these, within the same thread, on purpose ). The performance scaling:

  • in a wide meaning, the separation of workloads, among instances

  • in a narrower meaning, a direct IO-thread scaling ( defining different number of IO-threads upon the Context instantiation, some having just 1 IO-thread, whereas other may have 2, 3 or 10 IO-threads for a high-performance Data-pumping engine ) and using a bit-mask mapping via a setsockopt( ZMQ_AFFINITY ... )-based socket-to-IO-threads mapping, which allows to directly separate some transport-tasks onto different, discjunct Context-instances, and also to more specifically handle different transport-traffic priority-modes, right by using the separate, specialised groups of Context-instances' IO-threads ( naturally, where knowingly equipped with more and different amounts of underlying ZeroMQ Context's IO-threads ). This is an invaluable feature for performance scaling and almost-deterministic traffic-policy handling implementations.

This may inspire your further thought on how such fine-grained management ( granularity ) may help your distributed applications better harness the ZeroMQ framework's services and how this Zero-Sharing concept opens you the new worlds of performance scaling and throughput throttling.

If in doubts, just notice the inital remark in the source code:

...
// Uses Goroutines.  We could also use channels (a native form of
// inproc), but I stuck to the example.
...

The inproc:// transport class is the clear example on how to benefit from the non-shared, non-blocking Context-instance and enjoy the almost linear scale-up, up to the peak performance.