Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
197 lines (183 sloc) 7.75 KB
\item{``Parallelism is about performance;}
\item{Concurrency is about program design.''}
}{\textit{Google IO 2010}\\\textsc{ROB PIKE}}
\noindent{}In this chapter we will show off Go's ability for
concurrent programming using channels and goroutines. Goroutines
are the central entity in Go's ability for concurrency. But what
\emph{is} a goroutine? From \cite{effective_go}:
They're called goroutines because the existing terms --- threads, coroutines,
processes, and so on --- convey inaccurate connotations. A goroutine has a simple
model: \emph{it is a function executing in parallel with other goroutines in the same
address space}. It is lightweight, costing little more than the allocation of
stack space. And the stacks start small, so they are cheap, and grow by
allocating (and freeing) heap storage as required.
A \first{goroutine}{goroutine} is a normal function, except that you start
it with the keyword \first{\key{go}}{keyword!go}.
ready("Tea", 2) |\coderemark{Normal function call}|
go ready("Tea", 2) |\coderemark{\func{ready()} started as goroutine}|
The following idea for a program was taken from \cite{go_course_day3}.
We run a function as two goroutines, the goroutines wait for an amount of
time and then print something to the screen.
On the lines 14 and 15 we start the goroutines.
The \func{main} function
waits long enough, so that both goroutines will have printed their text. Right
now we wait for 5 seconds on line 17, but in fact we have no idea how
long we should wait until all goroutines have exited.
\lstinputlisting[numbers=right,label=src:sleeping,firstnumber=8,caption=Go routines in action,linerange={8,18}]{src/sleep.go}
Listing \ref{src:sleeping} outputs:
I'm waiting \coderemark{Right away}
Coffee is ready! \coderemark{After 1 second}
Tea is ready! \coderemark{After 2 seconds}
If we did not wait for the goroutines (i.e. remove line 17) the program
would be terminated immediately and any running goroutines would
\emph{die with it}.
To fix this we need some kind of mechanism which allows us to
communicate with the goroutines. This mechanism is available
to us in the form of \first{channels}{channels}. A
\first{channel}{channel} can be
compared to a two-way pipe in Unix shells: you can send to and receive
values from it. Those values can only be of a specific type: the
type of the channel. If we define a channel, we must also define the
type of the values we can send on the channel. Note that we must use
\key{make} to create a channel:
ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})
Makes \var{ci} a channel on which we can send and receive integers,
makes \var{cs} a channel for strings and \var{cf} a channel for types
that satisfy the empty interface.
Sending on a channel and receiving from it, is done with the same operator:
\lstinline{<-}. \index{operator!channel}
Depending on the operands it figures out what to do:
ci <- 1 |\coderemark{\emph{Send} the integer 1 to the channel \var{ci}}|
<-ci |\coderemark{\emph{Receive} an integer from the channel \var{ci}}|
i := <-ci |\coderemark{\emph{Receive} from the channel \var{ci} and store it in \var{i}}|
Let's put this to use.
\begin{lstlisting}[numbers=none,caption=Go routines and a channel,label=src:sleeping with channels]
var c chan int |\longremark{Declare \var{c} to be a variable that is a %
channel of ints. That is: this channel can move integers. Note %
that this variable is global so that the goroutines have access to it;}|
func ready(w string, sec int) {
time.Sleep(time.Duration(sec) * time.Second)
fmt.Println(w, "is ready!")
c <- 1 |\longremark{Send the integer 1 on the channel \var{c};}|
func main() {
c = make(chan int) |\longremark{Initialize \var{c};}|
go ready("Tea", 2) |\longremark{Start the goroutines with the keyword \key{go};}|
go ready("Coffee", 1)
fmt.Println("I'm waiting, but not too long")
<-c |\longremark{Wait until we receive a value from the channel. Note that the value we receive is discarded;}|
<-c |\longremark{Two goroutines, two values to receive.}|
There is still some remaining ugliness; we have to read twice from
the channel (lines 14 and 15). This is OK in this case, but what if
we don't know how many goroutines we started? This is where another
Go built-in comes in: \first{\key{select}}{keyword!select}. With \key{select} you
can (among other things) listen for incoming data on a channel.
Using \key{select} in our program does not really make it shorter,
because we run too few go\-routines. We remove the lines 14 and 15 and
replace them with the following:
\begin{lstlisting}[caption=Using select,numbers=right,firstnumber=14]
L: for {
select {
case <-c:
if i > 1 {
break L
We will now wait as long as it takes. Only when we have received more than
one reply on the channel \var{c} will we exit the loop \var{L}.
\subsection{Make it run in parallel}
While our goroutines were running concurrently, they were not running in
parallel. When you do not tell Go anything there can only be one
goroutine running at a time. With \func{runtime.GOMAXPROCS(n)} you
can set the number of goroutines that can run in parallel. From
the documentation:
GOMAXPROCS sets the maximum number of CPUs that can be executing
simultaneously and returns the previous setting. If n < 1, it does not
change the current setting. \emph{This call will go away when the scheduler
If you do not want to change any source code you can also set an
environment variable \verb|GOMAXPROCS| to the desired value.
%% test
%%hallo Yppp hallo Yppp hallo Yppp
%%hallo Yppp hallo Yppp hallo Yppp
%%hallo Yppp
%%\section{So many channels and still \ldots}
\section{More on channels}
\label{sec:more on channels}
When you create a channel in Go with \lstinline{ch := make(chan bool)},
an \first{unbuffered channel}{channel!unbuffered} for
bools is created. What does this mean for your program? For one, if you
read (\lstinline{value := <-ch}) it will block until there is data to
receive. Secondly anything sending (\lstinline{ch<-5}) will block until there
is somebody to read it.
Unbuffered channels make a perfect tool for synchronizing multiple
\index{channel!blocking read}
\index{channel!blocking write}
But Go allows you to specify the buffer size of
a channel, which is quite simply how many elements a channel can hold.
\lstinline{ch := make(chan bool, 4)}, creates a buffered channel of
bools that can hold 4 elements. The first 4 elements in this channel
are written without any blocking.
When you write the 5$^{th}$ element, your
code \emph{will} block, until another goroutine reads some elements from the
channel to make room.
\index{channel!non-blocking read}
\index{channel!non-blocking write}
In conclusion, the following is true in Go:
\textrm{\lstinline{ch := make(chan type, value)}}
value == 0 & \rightarrow \textrm{unbuffered)} \\
value > 0 & \rightarrow \textrm{buffer \emph{value} elements}
\subsection{Closing channels}
When a channel is closed the reading side needs to know this.
The following code will check if a channel is closed.
x, ok = <-ch
Where \lstinline{ok} is set to \lstinline{true} the channel is not closed
\emph{and} we've read something.
Otherwise \var{ok} is set to \lstinline{false}. In that case the channel
was closed.