SlideShare a Scribd company logo
Concurrency with Go
Natural Design in a Technical Language
Frank Müller
•Oldenburg, Germany


•Born 1965


•Working at Kubermatic


•Team Lead Development


•@themue
Introduction Frank Müller
Our world is concurrent
• Individuals populate this world


• They act sometimes independent of each other, sometimes
dependent, sometimes together


• Communication and signals enable their coexistence
It's a natural principle Frank Müller
World of plants
World of animals
People at work
People doing sports
People at conferences
People of everyday life
It's our daily life
Modern systems are more powerful
• Computer architectures are changing


• Growth via CPUs, cores and hyper-threads


• Manual usage via threads is complex and error-prone


• Concurrent runtime environments enable fine-grained usage
One motivation is the hardware Frank Müller
Distribution of computing power Frank Müller
Process Process Process Process
Process Process Process Process
Runtime Envirunment
Core Core Core Core
Process
Process
Process
Process
Processes can work alone ...
... or as a team together
• Encapsulation of the state in the process


• Communication via messages


• Sequential processing


• Atomic state changes


• OOP in the real sense
Structure is more important motivation Frank Müller
❞ –Rob Pike
Parallelis
m

Programming as the simultaneous execution
of (possibly related) computations
.

Concurrenc
y

Programming as the composition of
independently executing processes.
• Actor Model


‣ 1973


‣ Carl Hewitt, Peter Bishop und Richard Steiger


• Communicating Sequential Processes


‣ 1978


‣ Tony Hoare
Ideas long known Frank Müller
• Concurrent processes communicate via one or more channels


• Processes, unlike channels, are anonymous


• Data is not sent until the receiver is ready to receive it


• Incoming data is processed sequentially
Communicating Sequential Processes Frank Müller
Communicating Sequential Processes Frank Müller
Process
Process
Process
Process Process
Process Process
Process
Process
Process
Technologies need their time
Examples of use
Processes provide services Frank Müller
Service
Provider
Client
Client Client
Client
Active
Waiting
Waiting
Waiting
Processes manage ressources Frank Müller
Client
Client
Client
Manager Resource B
Resource A
Resource C
Active
Waiting
Waiting
Read / Write
Processes manage parallel requests Frank Müller
Worker
Worker
Worker
Client
Client
Client
Master
Request A
Request B
Request C
Request A
Request B
Request C
Reply
Processes handle events Frank Müller
Event
Manager
Emitter
Emitter
Emitter
Subscriber
Subscriber
Subscriber
Events
Events
Events
Events
Processes monitor each other Frank Müller
Supervisor
Process A Process B
Process B'
Starts A Starts B
Monitors A Monitors B
Monitors B'
Restarts B'
Processes support powerful ETL Frank Müller
Sender
Sender
Sender
Transformator(s) Loader
Extractor Receiver
Receiver
Receiver
Transformator(s)
Transformator(s)
Transformed
Data
Transformed
Data
Transformed
Data
Raw
Data
Raw
Data
Raw
Data
Examples in Go
• Development started 2007 by Google


• Designed by Rob Pike, Ken Thompson, and Robert Griesemer


• Initial release 2012


• Looks imperative, but is multi-paradigm


• Types with methods, interfaces, function types, concurrency


• Concurrency realized by goroutines and channels
Google Go Frank Müller
• Goroutines run lightweight in a thread pool


• Functions spawned with the keyword go


• Large simultaneous number possible


• Channels are typed and run synchronous or buffered


• They are used for communication and synchronization


• select statements allow parallel processing of multiple channels


• Use in for loops enables continuous processing
Concurrency in Go Frank Müller
Example "Background Job"
// processMyData processes the data in the background.


func processMyData(data Data) { ... }


// startProcessing pre-processes the data and starts the goroutine.


func startProcessing() {


var data Data


data = ...


// Spawn goroutine.


go processMyData(data)


// Do something else.


...


}
Simple in own function Frank Müller
// startProcessing pre-processes the data and starts the goroutine to process


// it in the background.


func startProcessing() {


var data Data


data = ...


// Spawn goroutine, function uses outer data.


go func() {


...


}()


// Do something else.


...


}
Simple in embedded function Frank Müller
// startProcessing pre-processes the data and starts the goroutine to process


// a copy in the background.


func startProcessing() {


var data Data


data = ...


// Spawn goroutine with a data copy, same name is no problem.


go func(data Data) {


...


}(data)


// Do something else using the original data.


...


}
Embedded function with data copy Frank Müller
// process pre-processes the data, starts the goroutine and waits until it's


// done.


func process() {


data := ...


var wg sync.WaitGroup // sync.WaitGroup allows counting of activities.


wg.Add(1) // In example here we wait for only one processing.


go processData(&wg, data)


...


wg.Wait() // Wait until it's done.


}
Waiting for processing of data Frank Müller
// processData processes the passed data and signals its ending via


// the sync.WaitGroup.


func processData(wg *sync.WaitGroup, data Data) {


// Deferred function call tells wait group that one processing is done.


defer wg.Done()


// Process data data.


...


}
Tell waiter that work is done Frank Müller
// processDatas starts the goroutines to process the individual datas and waits


// until all are done.


func processDatas(datas []Data) {


var wg sync.WaitGroup


for _, data := range datas {


// Add one per each data to process.


wg.Add(1)


// Spawn processing like in last example.


go processData(&wg, data)


}


wg.Wait() // Wait until they are done.


}
Start a number of background jobs Frank Müller
Example "Streaming"
// processDatas processes all datas it receives via the data channel.


// Loop ends when the channel is closed.


func processDatas(dataChan <-chan Data) {


for data := range dataChan {


// Process the individual data sequentially.


...


}


}


Process all datas received from channel Frank Müller
// Create data channel.


dataChan := make(chan Data)


// Spawn processor with data channel.


go processDatas(dataChan)


// Send datas.


dataChan <- dataA


dataChan <- dataB


dataChan <- dataC


// Close channel.


close(dataChan)
Use the data processor Frank Müller
// processAtoB processes all A datas it receives via the in channel. Results of


// type B are written to the out channel. That will be closed if the function


// ends working a.k.a. the in channel has been closed.


func processAtoB(inChan <-chan A, outChan chan<- B) {


defer close(outChan)


for a := range inChan {


b := ...


outChan <- b


}


}


// processBtoC works similar to processAtoB, only with B and C datas.


func processBtoC(inChan <-chan B, outChan chan<- C) {


...


}
Piping Frank Müller
// Create buffered channels.


aChan := make(chan A, 5)


bChan := make(chan B, 5)


cChan := make(chan C, 5)


// Spawn processors.


go processAtoB(aChan, bChan)


go processBtoC(bChan, cChan)


go processCs(cChan)


// Write A data into A channel, then close.


...


close(aChan)
Use the piping Frank Müller
Example "Service"
• Structure with data and channels


• Function New() as constructor


• Method loop() or backend() for loop with select statement


• Public methods to access the instance


• Requests with return values need explicit channel


• Multiple return values need helper types
Often found pattern Frank Müller
// MyService simply provides a string and an integer field and allows to add


// them if possible.


type MyService struct {


a string


b int


// Many channels needed this way.


setAChan chan string


getAChan chan chan string


setBChan chan int


getBChan chan chan int


addChan chan addResp


}
Structure Frank Müller
// New create a new instance of MyService. The backend goroutine is controlled


// by the given context.


func New(ctx context.Context) *MyService {


ms := &MyService{


setAChan: make(chan string),


getAChan: make(chan chan string),


setBChan: make(chan int),


getBChan: make(chan chan int),


addChan: make(chan addReq),


}


go ms.backend(ctx)


return ms


}
Constructor Frank Müller
// SetA simply sends the data via the channel.


func (ms *MyService) SetA(a string) {


ms.setAChan <- a


}


// GetA sends a buffered channel and receives the result via it.


func (ms *MyService) GetA() string {


// Buffered to allow backend continue working.


respChan := make(chan string, 1)


ms.getAChan <- respChan


return <-respChan


}
Setter and getter Frank Müller
// addResp is a private transporter for the sum and a possible error.


type addResp struct {


sum int


err error


}


// Add sends a buffered channel for a transporter.


func (ms *MyService) Add() (int, error) {


respChan := make(chan addResp, 1)


ms.addChan <- retChan


resp := <-retChan


if resp.err != nil { return 0, resp.err }


return resp.sum, nil


}
Sometimes even more effort needed Frank Müller
// backend runs as goroutine and serializes the operations.


func (ms *MyService) backend(ctx context.Context) {


// Endless loop with for.


for {


// Select to switch between the channels.


select {


case <-ctx.Done():


// Terminate backend.


return


case ...:


...


}


}


}
Backend structure Frank Müller
select {


...


case respChan := <-ms.getBChan:


respChan <- ms.b


case respChan := <-ms.addChan:


var resp addResp


i, err := strconv.Atoi(ms.a)


if err != nil {


resp.err = err


} else {


resp.sum = i + ms.b


}


respChan <- resp


}
Other cases Frank Müller
• Much extra effort with typed channels and helper types


• Example here does not care if context is cancelled


• Public methods and business logic are separated


• Without private helpers the select statement may grow too much


• But thankfully there's a more simple way to do it
Summary Frank Müller
Example "Actor"
// Actor only needs two extra fields.


type Actor struct {


ctx context.Context


actChan chan func()


// Here own data.


...


}


func New(ctx context.Context) *Actor {


act := &Actor{ ... }


go backend()


return act


}
Structure and constructor Frank Müller
// backend is only executing the received functions.


func (act *Actor) backend() {


for {


select {


case <-act.ctx.Done():


return


case action := <-act.actChan:


// Execute received function.


action()


}


}


}
Backend Frank Müller
// do always keeps an eye on the context when sending.


func (act *Actor) do(action func()) error {


select {


case <-act.ctx.Done():


if ctx.Err() != nil {


return ctx.Err()


}


return errors.New("actor has been stopped")


case act.actChan <- action:


return nil


}


}
Safe sending of a function Frank Müller
// Add works like the Add() from example before.


func (act *Actor) Add() (i int, err error) {


// Send logic to the backend.


if aerr := act.do(func() {


fi, ferr := strconv.Atoi(act.a)


if ferr != nil {


err = ferr


return


}


i = fi + act.b


}); aerr != nil {


// Looks like the actor already has been stopped.


return 0, aerr


}


}
Business logic Frank Müller
• Less code


• Actor can be implemented in one package and always reused


• Additional buffered channel and doAsync() support pure setters
and callers without return values


• do() and doAsync() also could get optional timeout for fault
tolerant behavior
Summary Frank Müller
Example "Supervisor"
// Supervisor helps monitoring and restarting concurrent functions.


type Supervisor struct {


mu sync.Mutex


workers map[string]func() error


spawnChan chan string


}


// New starts the supervisor in the background.


func New(ctx context.Context) *Supervisor {


s := &Structure{ ... }


go s.backend(ctx)


return s


}
Structure and constructor Frank Müller
// Spawn tells backend to spawn the given worker.


func (s *Supervisor) spawn(id string, worker func() error) error {


s.mu.Lock()


defer s.mu.Unlock()


_, ok := s.workers[id]


if ok {


return errors.New("double worker ID")


}


s.workers[id] = worker


s.spawnChan <- id


return nil


}
Start a worker Frank Müller
// wrap takes care for errors of the worker. In case of an error it notifies


// the backend to re-spawn.


func (s *Supervisor) wrap(id string) {


worker := s.workers[id]


if err := worker(); err != nil {


// Log the error and re-spawn the worker.


log.Printf("worker %q terminated with error: %v", id, err)


s.spawnChan <- id


return


}


// Delete successful terminated worker.


s.mu.Lock()


delete(s.workers, id)


s.mu.Unlock()


}
Wrapper to check if worker error Frank Müller
// backend wraps workers and spawns them.


func (s *Supervisor) backend(ctx context.Context) {


for {


select {


case <-ctx.Done():


return


case id := <-s.spawnChan:


go s.wrap(id)


}


}


}
Backend Frank Müller
Pitfalls
• Channels work synchron or buffered


• Working reader could lead to blocking writes, buffers may be
filled


• Most times just waiting, but overload resource may lead to
instability


• If needed check for parallelized pre-processing steps, e.g. by
caller, and keep the need for serialization small


• Alternatively assistent workers for the backend may help
Blocked channels Frank Müller
• Avoid overlapping read and write access with external
modification


• E.g. use IncrBy() instead of Get(), local addition, and Set()


• Alternatively read value together with timed handle for update


• Other modifiers get an error during this time


• An unused handle must be released again
Race conditions Frank Müller
• Concurrent updates of coherent data may lead to invalid states


• Avoid too fine granular access


• Assure changes of all related data en bloc
Non-atomic updates Frank Müller
Final summary
• Power of concurrency seems to be complex


• Possibilities are manifold


• As is often the case, only a few patterns make up almost all use
cases


• Own or 3rd party packages reduce work here


• Design of elastic software requires a more natural rethink
Final summary Frank Müller
Thanks a lot and


have a nice


evening


Image Sources


123RF


Pexels


iStockphoto


Own photos

More Related Content

PDF
Go concurrency
PPT
Concurrency in go
PDF
Golang concurrency design
PDF
Concurrency in Golang
PDF
Golang design4concurrency
PDF
Demystifying the Go Scheduler
PDF
The low level awesomeness of Go
PPTX
Go Concurrency Basics
Go concurrency
Concurrency in go
Golang concurrency design
Concurrency in Golang
Golang design4concurrency
Demystifying the Go Scheduler
The low level awesomeness of Go
Go Concurrency Basics

What's hot (20)

PDF
Go on!
PDF
Coding in GO - GDG SL - NSBM
PDF
Mpi in-python
PDF
Go Containers
PDF
Global Interpreter Lock: Episode III - cat &lt; /dev/zero > GIL;
PDF
Protocol handler in Gecko
PPTX
Go Concurrency Patterns
PPTX
.NET Core Summer event 2019 in Brno, CZ - Async demystified -- Karel Zikmund
PDF
Concurrency in Go by Denys Goldiner.pdf
PPTX
Fundamental concurrent programming
PDF
Concurrency in Python4k
PDF
Deep Dive async/await in Unity with UniTask(EN)
PDF
Ownership System in Rust
PDF
Python Async IO Horizon
PDF
PDF
3 rd animation
PDF
PDF
The async/await concurrency pattern in Golang
PDF
Router Queue Simulation in C++ in MMNN and MM1 conditions
Go on!
Coding in GO - GDG SL - NSBM
Mpi in-python
Go Containers
Global Interpreter Lock: Episode III - cat &lt; /dev/zero > GIL;
Protocol handler in Gecko
Go Concurrency Patterns
.NET Core Summer event 2019 in Brno, CZ - Async demystified -- Karel Zikmund
Concurrency in Go by Denys Goldiner.pdf
Fundamental concurrent programming
Concurrency in Python4k
Deep Dive async/await in Unity with UniTask(EN)
Ownership System in Rust
Python Async IO Horizon
3 rd animation
The async/await concurrency pattern in Golang
Router Queue Simulation in C++ in MMNN and MM1 conditions
Ad

Similar to Concurrency with Go (20)

KEY
Beauty and Power of Go
PDF
2011 july-nyc-gtug-go
PDF
Fun with functions
PDF
Mastering Concurrency in GO: From Patterns to Production
KEY
Google Go Overview
PDF
Goroutines and Channels in practice
PDF
WebSummit 2015 - Gopher it
PDF
The 1990s Called. They Want Their Code Back.
PDF
Go Concurrency
PDF
LCA2014 - Introduction to Go
PDF
An Introduction to Go
PDF
Go courseday3
PDF
Go for Rubyists
PDF
Inroduction to golang
PPTX
Go. Why it goes
PPTX
Go from a PHP Perspective
PPTX
Golang 101 (Concurrency vs Parallelism)
PPT
Implementation of 'go-like' language constructions in scala [english version]
PDF
How To Think In Go
PDF
Flow based programming in golang
Beauty and Power of Go
2011 july-nyc-gtug-go
Fun with functions
Mastering Concurrency in GO: From Patterns to Production
Google Go Overview
Goroutines and Channels in practice
WebSummit 2015 - Gopher it
The 1990s Called. They Want Their Code Back.
Go Concurrency
LCA2014 - Introduction to Go
An Introduction to Go
Go courseday3
Go for Rubyists
Inroduction to golang
Go. Why it goes
Go from a PHP Perspective
Golang 101 (Concurrency vs Parallelism)
Implementation of 'go-like' language constructions in scala [english version]
How To Think In Go
Flow based programming in golang
Ad

More from Frank Müller (20)

PDF
IT-Tage 2024: Philosophie in der Software-Architektur
PDF
JAX 2023 - Cloud Provider APIs
PDF
JAX 2023 - Generics in Go
PDF
Let The Computer Do It
PDF
2021 OOP - Kubernetes Operatoren
PDF
DevOpsCon - Verteilte Entwicklung in Go
PDF
Devs@Home - Einführung in Go
PDF
Ein Gopher im Netz
PDF
Blockchains - Mehr als nur digitale Währungen
PDF
Spaß an der Nebenläufigkeit
PDF
Go - Googles Sprache für skalierbare Systeme
PDF
Cloud Provisioning mit Juju
PDF
Juju - Scalable Software with Google Go
PDF
RESTful Web Applications with Google Go
PDF
Clouds, leicht beherrschbar
PDF
Skalierbare Anwendungen mit Google Go
PDF
WTC 2013 - Juju - Mit etwas Magie zur perfekten Cloud
PDF
Juju - Google Go in a scalable Environment
PDF
OOP 2013 - Weltweite Entwicklung von Open Source Software
PDF
Pecha Kucha: Nebenläufigkeit als natürliches Paradigma
IT-Tage 2024: Philosophie in der Software-Architektur
JAX 2023 - Cloud Provider APIs
JAX 2023 - Generics in Go
Let The Computer Do It
2021 OOP - Kubernetes Operatoren
DevOpsCon - Verteilte Entwicklung in Go
Devs@Home - Einführung in Go
Ein Gopher im Netz
Blockchains - Mehr als nur digitale Währungen
Spaß an der Nebenläufigkeit
Go - Googles Sprache für skalierbare Systeme
Cloud Provisioning mit Juju
Juju - Scalable Software with Google Go
RESTful Web Applications with Google Go
Clouds, leicht beherrschbar
Skalierbare Anwendungen mit Google Go
WTC 2013 - Juju - Mit etwas Magie zur perfekten Cloud
Juju - Google Go in a scalable Environment
OOP 2013 - Weltweite Entwicklung von Open Source Software
Pecha Kucha: Nebenläufigkeit als natürliches Paradigma

Recently uploaded (20)

PDF
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
PDF
Softaken Excel to vCard Converter Software.pdf
PPTX
Reimagine Home Health with the Power of Agentic AI​
PDF
Design an Analysis of Algorithms I-SECS-1021-03
PDF
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
PPTX
Introduction to Artificial Intelligence
PDF
Navsoft: AI-Powered Business Solutions & Custom Software Development
PDF
wealthsignaloriginal-com-DS-text-... (1).pdf
PDF
How Creative Agencies Leverage Project Management Software.pdf
PPTX
Operating system designcfffgfgggggggvggggggggg
PDF
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
PDF
Understanding Forklifts - TECH EHS Solution
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PDF
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
PDF
PTS Company Brochure 2025 (1).pdf.......
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 41
PDF
Which alternative to Crystal Reports is best for small or large businesses.pdf
PDF
2025 Textile ERP Trends: SAP, Odoo & Oracle
PDF
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
PDF
Wondershare Filmora 15 Crack With Activation Key [2025
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
Softaken Excel to vCard Converter Software.pdf
Reimagine Home Health with the Power of Agentic AI​
Design an Analysis of Algorithms I-SECS-1021-03
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
Introduction to Artificial Intelligence
Navsoft: AI-Powered Business Solutions & Custom Software Development
wealthsignaloriginal-com-DS-text-... (1).pdf
How Creative Agencies Leverage Project Management Software.pdf
Operating system designcfffgfgggggggvggggggggg
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
Understanding Forklifts - TECH EHS Solution
Design an Analysis of Algorithms II-SECS-1021-03
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
PTS Company Brochure 2025 (1).pdf.......
Internet Downloader Manager (IDM) Crack 6.42 Build 41
Which alternative to Crystal Reports is best for small or large businesses.pdf
2025 Textile ERP Trends: SAP, Odoo & Oracle
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
Wondershare Filmora 15 Crack With Activation Key [2025

Concurrency with Go

  • 1. Concurrency with Go Natural Design in a Technical Language Frank Müller
  • 2. •Oldenburg, Germany •Born 1965 •Working at Kubermatic •Team Lead Development •@themue Introduction Frank Müller
  • 3. Our world is concurrent
  • 4. • Individuals populate this world • They act sometimes independent of each other, sometimes dependent, sometimes together • Communication and signals enable their coexistence It's a natural principle Frank Müller
  • 12. Modern systems are more powerful
  • 13. • Computer architectures are changing • Growth via CPUs, cores and hyper-threads • Manual usage via threads is complex and error-prone • Concurrent runtime environments enable fine-grained usage One motivation is the hardware Frank Müller
  • 14. Distribution of computing power Frank Müller Process Process Process Process Process Process Process Process Runtime Envirunment Core Core Core Core Process Process Process Process
  • 15. Processes can work alone ...
  • 16. ... or as a team together
  • 17. • Encapsulation of the state in the process • Communication via messages • Sequential processing • Atomic state changes • OOP in the real sense Structure is more important motivation Frank Müller
  • 18. ❞ –Rob Pike Parallelis m Programming as the simultaneous execution of (possibly related) computations . Concurrenc y Programming as the composition of independently executing processes.
  • 19. • Actor Model ‣ 1973 ‣ Carl Hewitt, Peter Bishop und Richard Steiger • Communicating Sequential Processes ‣ 1978 ‣ Tony Hoare Ideas long known Frank Müller
  • 20. • Concurrent processes communicate via one or more channels • Processes, unlike channels, are anonymous • Data is not sent until the receiver is ready to receive it • Incoming data is processed sequentially Communicating Sequential Processes Frank Müller
  • 21. Communicating Sequential Processes Frank Müller Process Process Process Process Process Process Process Process Process Process
  • 24. Processes provide services Frank Müller Service Provider Client Client Client Client Active Waiting Waiting Waiting
  • 25. Processes manage ressources Frank Müller Client Client Client Manager Resource B Resource A Resource C Active Waiting Waiting Read / Write
  • 26. Processes manage parallel requests Frank Müller Worker Worker Worker Client Client Client Master Request A Request B Request C Request A Request B Request C Reply
  • 27. Processes handle events Frank Müller Event Manager Emitter Emitter Emitter Subscriber Subscriber Subscriber Events Events Events Events
  • 28. Processes monitor each other Frank Müller Supervisor Process A Process B Process B' Starts A Starts B Monitors A Monitors B Monitors B' Restarts B'
  • 29. Processes support powerful ETL Frank Müller Sender Sender Sender Transformator(s) Loader Extractor Receiver Receiver Receiver Transformator(s) Transformator(s) Transformed Data Transformed Data Transformed Data Raw Data Raw Data Raw Data
  • 31. • Development started 2007 by Google • Designed by Rob Pike, Ken Thompson, and Robert Griesemer • Initial release 2012 • Looks imperative, but is multi-paradigm • Types with methods, interfaces, function types, concurrency • Concurrency realized by goroutines and channels Google Go Frank Müller
  • 32. • Goroutines run lightweight in a thread pool • Functions spawned with the keyword go • Large simultaneous number possible • Channels are typed and run synchronous or buffered • They are used for communication and synchronization • select statements allow parallel processing of multiple channels • Use in for loops enables continuous processing Concurrency in Go Frank Müller
  • 34. // processMyData processes the data in the background. func processMyData(data Data) { ... } // startProcessing pre-processes the data and starts the goroutine. func startProcessing() { var data Data data = ... // Spawn goroutine. go processMyData(data) // Do something else. ... } Simple in own function Frank Müller
  • 35. // startProcessing pre-processes the data and starts the goroutine to process 
 // it in the background. func startProcessing() { var data Data data = ... // Spawn goroutine, function uses outer data. go func() { ... }() // Do something else. ... } Simple in embedded function Frank Müller
  • 36. // startProcessing pre-processes the data and starts the goroutine to process 
 // a copy in the background. func startProcessing() { var data Data data = ... // Spawn goroutine with a data copy, same name is no problem. go func(data Data) { ... }(data) // Do something else using the original data. ... } Embedded function with data copy Frank Müller
  • 37. // process pre-processes the data, starts the goroutine and waits until it's 
 // done. func process() { data := ... var wg sync.WaitGroup // sync.WaitGroup allows counting of activities. wg.Add(1) // In example here we wait for only one processing. go processData(&wg, data) ... wg.Wait() // Wait until it's done. } Waiting for processing of data Frank Müller
  • 38. // processData processes the passed data and signals its ending via // the sync.WaitGroup. func processData(wg *sync.WaitGroup, data Data) { // Deferred function call tells wait group that one processing is done. defer wg.Done() // Process data data. ... } Tell waiter that work is done Frank Müller
  • 39. // processDatas starts the goroutines to process the individual datas and waits // until all are done. func processDatas(datas []Data) { var wg sync.WaitGroup for _, data := range datas { // Add one per each data to process. wg.Add(1) // Spawn processing like in last example. go processData(&wg, data) } wg.Wait() // Wait until they are done. } Start a number of background jobs Frank Müller
  • 41. // processDatas processes all datas it receives via the data channel. // Loop ends when the channel is closed. func processDatas(dataChan <-chan Data) { for data := range dataChan { // Process the individual data sequentially. ... } } Process all datas received from channel Frank Müller
  • 42. // Create data channel. dataChan := make(chan Data) // Spawn processor with data channel. go processDatas(dataChan) // Send datas. dataChan <- dataA dataChan <- dataB dataChan <- dataC // Close channel. close(dataChan) Use the data processor Frank Müller
  • 43. // processAtoB processes all A datas it receives via the in channel. Results of // type B are written to the out channel. That will be closed if the function // ends working a.k.a. the in channel has been closed. func processAtoB(inChan <-chan A, outChan chan<- B) { defer close(outChan) for a := range inChan { b := ... outChan <- b } } // processBtoC works similar to processAtoB, only with B and C datas. func processBtoC(inChan <-chan B, outChan chan<- C) { ... } Piping Frank Müller
  • 44. // Create buffered channels. aChan := make(chan A, 5) bChan := make(chan B, 5) cChan := make(chan C, 5) // Spawn processors. go processAtoB(aChan, bChan) go processBtoC(bChan, cChan) go processCs(cChan) // Write A data into A channel, then close. ... close(aChan) Use the piping Frank Müller
  • 46. • Structure with data and channels • Function New() as constructor • Method loop() or backend() for loop with select statement • Public methods to access the instance • Requests with return values need explicit channel • Multiple return values need helper types Often found pattern Frank Müller
  • 47. // MyService simply provides a string and an integer field and allows to add // them if possible. type MyService struct { a string b int // Many channels needed this way. setAChan chan string getAChan chan chan string setBChan chan int getBChan chan chan int addChan chan addResp } Structure Frank Müller
  • 48. // New create a new instance of MyService. The backend goroutine is controlled // by the given context. func New(ctx context.Context) *MyService { ms := &MyService{ setAChan: make(chan string), getAChan: make(chan chan string), setBChan: make(chan int), getBChan: make(chan chan int), addChan: make(chan addReq), } go ms.backend(ctx) return ms } Constructor Frank Müller
  • 49. // SetA simply sends the data via the channel. func (ms *MyService) SetA(a string) { ms.setAChan <- a } // GetA sends a buffered channel and receives the result via it. func (ms *MyService) GetA() string { // Buffered to allow backend continue working. respChan := make(chan string, 1) ms.getAChan <- respChan return <-respChan } Setter and getter Frank Müller
  • 50. // addResp is a private transporter for the sum and a possible error. type addResp struct { sum int err error } // Add sends a buffered channel for a transporter. func (ms *MyService) Add() (int, error) { respChan := make(chan addResp, 1) ms.addChan <- retChan resp := <-retChan if resp.err != nil { return 0, resp.err } return resp.sum, nil } Sometimes even more effort needed Frank Müller
  • 51. // backend runs as goroutine and serializes the operations. func (ms *MyService) backend(ctx context.Context) { // Endless loop with for. for { // Select to switch between the channels. select { case <-ctx.Done(): // Terminate backend. return case ...: ... } } } Backend structure Frank Müller
  • 52. select { ... case respChan := <-ms.getBChan: respChan <- ms.b case respChan := <-ms.addChan: var resp addResp i, err := strconv.Atoi(ms.a) if err != nil { resp.err = err } else { resp.sum = i + ms.b } respChan <- resp } Other cases Frank Müller
  • 53. • Much extra effort with typed channels and helper types • Example here does not care if context is cancelled • Public methods and business logic are separated • Without private helpers the select statement may grow too much • But thankfully there's a more simple way to do it Summary Frank Müller
  • 55. // Actor only needs two extra fields. type Actor struct { ctx context.Context actChan chan func() // Here own data. ... } func New(ctx context.Context) *Actor { act := &Actor{ ... } go backend() return act } Structure and constructor Frank Müller
  • 56. // backend is only executing the received functions. func (act *Actor) backend() { for { select { case <-act.ctx.Done(): return case action := <-act.actChan: // Execute received function. action() } } } Backend Frank Müller
  • 57. // do always keeps an eye on the context when sending. func (act *Actor) do(action func()) error { select { case <-act.ctx.Done(): if ctx.Err() != nil { return ctx.Err() } return errors.New("actor has been stopped") case act.actChan <- action: return nil } } Safe sending of a function Frank Müller
  • 58. // Add works like the Add() from example before. func (act *Actor) Add() (i int, err error) { // Send logic to the backend. if aerr := act.do(func() { fi, ferr := strconv.Atoi(act.a) if ferr != nil { err = ferr return } i = fi + act.b }); aerr != nil { // Looks like the actor already has been stopped. return 0, aerr } } Business logic Frank Müller
  • 59. • Less code • Actor can be implemented in one package and always reused • Additional buffered channel and doAsync() support pure setters and callers without return values • do() and doAsync() also could get optional timeout for fault tolerant behavior Summary Frank Müller
  • 61. // Supervisor helps monitoring and restarting concurrent functions. type Supervisor struct { mu sync.Mutex workers map[string]func() error spawnChan chan string } // New starts the supervisor in the background. func New(ctx context.Context) *Supervisor { s := &Structure{ ... } go s.backend(ctx) return s } Structure and constructor Frank Müller
  • 62. // Spawn tells backend to spawn the given worker. func (s *Supervisor) spawn(id string, worker func() error) error { s.mu.Lock() defer s.mu.Unlock() _, ok := s.workers[id] if ok { return errors.New("double worker ID") } s.workers[id] = worker s.spawnChan <- id return nil } Start a worker Frank Müller
  • 63. // wrap takes care for errors of the worker. In case of an error it notifies // the backend to re-spawn. func (s *Supervisor) wrap(id string) { worker := s.workers[id] if err := worker(); err != nil { // Log the error and re-spawn the worker. log.Printf("worker %q terminated with error: %v", id, err) s.spawnChan <- id return } // Delete successful terminated worker. s.mu.Lock() delete(s.workers, id) s.mu.Unlock() } Wrapper to check if worker error Frank Müller
  • 64. // backend wraps workers and spawns them. func (s *Supervisor) backend(ctx context.Context) { for { select { case <-ctx.Done(): return case id := <-s.spawnChan: go s.wrap(id) } } } Backend Frank Müller
  • 66. • Channels work synchron or buffered • Working reader could lead to blocking writes, buffers may be filled • Most times just waiting, but overload resource may lead to instability • If needed check for parallelized pre-processing steps, e.g. by caller, and keep the need for serialization small • Alternatively assistent workers for the backend may help Blocked channels Frank Müller
  • 67. • Avoid overlapping read and write access with external modification • E.g. use IncrBy() instead of Get(), local addition, and Set() • Alternatively read value together with timed handle for update • Other modifiers get an error during this time • An unused handle must be released again Race conditions Frank Müller
  • 68. • Concurrent updates of coherent data may lead to invalid states • Avoid too fine granular access • Assure changes of all related data en bloc Non-atomic updates Frank Müller
  • 70. • Power of concurrency seems to be complex • Possibilities are manifold • As is often the case, only a few patterns make up almost all use cases • Own or 3rd party packages reduce work here • Design of elastic software requires a more natural rethink Final summary Frank Müller
  • 71. Thanks a lot and have a nice evening Image Sources 123RF Pexels iStockphoto Own photos