Why are goroutines easy


Go tutorial, part 3: Concurrency

Go uses lightweight goroutines for concurrent functions. In the example program, you expand the "GitHub Progress Monitor" and automate event analyzes for various repositories.


For concurrency, Go uses goroutines, lightweight functions that a multiplexer distributes over a number of threads.

Since goroutines have a low overhead compared to threads, they scale well with the amount of cores; the simultaneous operation of a large number of goroutines is also possible on small systems.

Goroutines communicate via channels that, like arrays, slices and maps, are generic data types and can transmit functions as well as simple data.

So far, the first two parts of the tutorial have not dealt with a special feature of Google Go: the handling of concurrent functions. Many languages ​​use threads for this. They can be run at the same time, communication between them usually takes place via shared memory, and a mutex procedure is used for synchronization. However, context switches between threads have a certain overhead, and errors using the mutexes can quickly lead to deadlocks. If you do without them, however, you have to reckon with inconsistent data.

As a solution to this dilemma, Go uses goroutines, lightweight functions that a multiplexer distributes over a set of threads. If there is a risk of a goroutine blocking, for example when executing a system call, the goroutines assigned to the same thread would also be blocked. In this case, the runtime environment moves the goroutine to a free thread and thus prevents the blockage. The developer does not have to take any precautions for this. With their low overhead compared to threads, the simultaneous operation of thousands of goroutines is also possible on small systems and also scales seamlessly with the number of cores.

Channels make the difference

Such a goroutine is started with the keyword go (Listing 1). This can be used to send a function or method call to the background. A reference is not returned. This means that a goroutine cannot simply be stopped from the outside using a keyword. Likewise, a return discarded a goroutine. In both cases, channels are used for signals as well as for the input and output of data.

So far this is not uncommon. And for the corresponding use cases, Gos contains a standard library in the packages sync and sync / atomic Helper for traditional synchronization. But what is really special about the language is the channels - generic data types such as arrays, slices and maps. They are specified in the declaration and with make () created. An optional parameter controls whether a channel should receive a buffer. Without a buffer, the writer blocks until its data has been read from the channel. With buffers, execution continues immediately after writing, as long as there is still space in the buffer.

The line creates a channel for strings

myChan: = make (chan string)

The instruction

myChan <- "foo"

writes the string "foo" into the channel. The operator for receiving is also an arrow, but the individual elements of the statement are arranged differently. With

data: = <-myChan

data can be read from the channel and the variables data assign.

A very simple use case for the use of channels is the return of results mentioned above. A generated result channel is transferred to the goroutine and read out again later (Listing 2).

But what do you have to do if the waiting for the answer is to be given a timeout? There is no direct parameter for the receive operator, but Go can use the select- Listen to the statement on several channels at the same time. Along with the function time.After (Duration) <-chan Time from the package time this makes it easy to implement a timeout (Listing 3).

Channels also send channels

This form of receiving data from several channels in parallel is also used in another typical variety of goroutines. It forms the backend for the form of the objects indicated above, which receive their messages via channels and act as desired. A little guy for joining strings is supposed to demonstrate this. It contains three open channels for the content to be added, for querying the generated string and for resetting to a new value (Listing 4).

The one that is noticeable here ContentChan. It shows that channels can also be sent via channels, here a chan string. So the Appender a way in which it can send its content back to the caller. The goroutine is in the process here baking () implemented (Listing5). She runs an infinite loop in which she is in a select queries the channels and acts accordingly. Now is the use of one Appender very easy through different goroutines via its reference (Listing 6).

But be careful: What result does the goroutine give in result back? The elegance of the sequential execution of commands within the instance of the Appender must not hide potential race conditions. "foobarbazyadda" is just as possible as "fooyaddabarbaz" or "foobaryaddabaz". When designing goroutines, one must therefore note that operations must be carried out in a closed manner and that data to be changed must also be transmitted together. A SetA (), SetB () and GetAB () invites inconsistent states. Fortunately, the Go Toolset comes with a Race Detector.