I want to know in which situation Go chan makes code much simpler than using list or queue or array that is usually available in all languages.
As it was stated by Rob Pike in one of his speeches about Go lexer, Go channels help to organize data flow between structures that are not homomorphic.
I am interested in a simple Go code sample with chan that becomes MUCH more complicated in another language (for example C#) where chan is not available.
I am not interested in samples that use chan just to increase performance by avoiding waiting of data between generating list and consuming the list (which can be solved by chunking) or as a way to organize thread safe queue or thread-safe communication (which can be easily solved by locking primitives).
I am interested in a sample that makes code simpler structurally disregarding size of data.
If such sample does not exist then sample where size of data matters.
I guess desired sample would contain bi-directional communication between generator and consumer.
Also if someone could add tag [channel] to the list of available tags, that would be great.
A generic queue or array is generally not, by itself, thread safe, just like many other data types. Thread safety is usually accomplished in two ways:
- Using mutex locks – each thread that wants to modify a value has to wait.
- Delegation – only the owning thread can modify the value.
Mutex locks are fairly straight forward – nobody owns the value, but only one thread can modify it at a time. When one thread is modifying a value, other threads have to wait. The problem with this approach is contention. More than one thread wanting to modify a value at the same time, and the number of threads waiting stacks up.
How delegation is handled is really dependent on the language in question. In Objective C you have GCD to dispatch asynchronous (or even synchronous) requests on the owning thread. You have an opportunity to (asynchronously) have the value modified without holding up the requesting thread. GCD internally schedules these tasks into a queue and executes them when the target thread isn’t busy.
Go channels are somewhat a form of delegation. You can use it as a non-blocking queue by using buffered channels. But the biggest difference between Go channels and other approaches can probably be summed up with the following sentence:
Don’t communicate by sharing, share by communicating.
A Go channel is much more than just a queue, it’s a communication pipeline. The best way I can illustrate this is by showing an excerpt from one of my own projects:
for {
select {
case client := <-server.accept:
server.addClient(client)
go server.serveClient(client)
case client := <-server.remove:
server.removeClient(client)
case envelope := <-server.dropbox:
switch envelope.cmd {
case "pub":
server.publish(envelope)
case "sub":
server.subscribe(envelope)
case "unsub":
server.unsubscribe(envelope)
}
case err := <-server.errors:
return err
}
}
The above code is running on the main Go routine (the channels are buffered).
<-server.accept
– A second Go routine (not shown here) handles new connections and passes them to the accept channel. The main Go routine accepts the new client and adds them to a map of connected clients. Another Go routine is spawned to handle requests from the new client.
<-server.remove
– When a client disconnects the Go routine handling its connection passes it back to the main Go routine to be removed.
<-server.dropbox
– When a client has something it wants to send it puts it into the “dropbox” to be delivered to other clients, which are registered in the map.
Each client has an inbox channel which the main thread can deliver “mail” to. The Go routine handling the client connection reads the inbox.
As you can see by the example, the code becomes somewhat relatable: A post-office with a drop box to deliver mail, and an inbox for each client to receive it. This is just one example of how channels can be used.
Should channels always be used in every situation? No – I have seen situations where a mutex lock made more sense than a channel. Atomic data structures is an example where mutex locks make more sense.
I think the overall goal of a channel is to make code easier to understand. It conveys intention more precisely, in certain (but many) situations. Internally a channel probably uses mutex locks. I think an important point to take home is – channels are useful; just don’t get carried away.
I want to know in which situation Go chan makes code much simpler than using list or queue or array that is usually available in all languages.
Let’s say you have a routine that generates Fibonacci numbers (as a stand-in for something more complex). The main routine needs to “get the next number” when it needs it. The main routine should not be coupled to the Fibonacci routine, so it must not store the Fibonacci state.
So where do you store the Fibonacci state in between calls? In a language without threads/coroutines, you would have to write a lot of extra code to store this state, and that storage code would have to be changed every time you need to store slightly different state. That “store the state” code is brittle and has nothing to do with the actual calculation.
So it’s much simpler to spin that routine off into it’s own thread of execution. (real thread of language thread). Then we can ask it to generate the next value when needed and neither side worries about state. Yay.
You can use Arrays for this communication, but you will need to write a bunch of code: Locking the array (to prevent races), handling the “array full” case, handling the array wrap-around case, handling the array underflow case, etc. A queue is more similar, but doesn’t have all the same semantics (not always mutithread safe, doesn’t always wake up the receiver, etc)
Having a channel makes it 100 times more obvious what you are doing. Once you get used to channels, you will use them all the time. Not having channels is like not having a Map/Dictonary/Hash data structure. People survived for many years without them, but once you have them, you see all kinds of places they are useful. You’ll start organizing your code around the CSP concept.
Gone are the days where you can buy a single-core computer (or even a single-core phone.) Therefore, communication between threads/processes is only going to get more important over time. If you (or your language) can only think in terms of single-CPU processing, you will be a dinosaur.
Concurrency is not parallelism, it’s better.
Channels and lists used a queues are very similar. In go, channels are designed to work with goroutines. Goroutines allows your program to utilize all the core of your computer. Channels allow the goroutines to share data safely across cores. Channels also allows goroutines to synchronize with the data.