Patterns: concurrent Go · 3 MIN READ

Single owner: serialize state through messages

Use one goroutine to own mutable state and define request completion.

Use one goroutine to own mutable state and define request completion. An actor-like owner serializes access to its state through messages. Trace the worked Go example, then compare the benefit with the cost before applying this design to your own project.

Concurrent Go pattern · A MAP OF THE IDEA

  1. Caller sends an explicit request
  2. One goroutine mutates its private state
  3. Reply and done signals establish completion

Useful when

  • State transitions benefit from serial ownership
  • Shutdown and command order should be explicit

Watch out

  • Synchronous replies can strand the owner
  • Mailbox capacity and overload need budgets

Implementation & operation cost: Medium

Fit depends on the task

Small tool or CLI3/5
Domain-rich application4/5
Independent distributed services4/5

Editorial suitability, not benchmarks or a universal ranking. Styles describe different axes and can be combined.

01

The design decision

An actor-like owner serializes access to its state through messages. This can make mutation and ordering explicit, but a Go channel does not automatically provide actor supervision, durability or remote messaging. A mutex is often simpler for a short shared-memory operation.

02

A complete Go example

Read this program from the entry point and follow the calls shown in the diagram. The output is included at the end so you can check the behavior without a compiler.

go
package main

import (
	"fmt"
)

type Add struct {
	Delta int
	Reply chan int
}

func Counter(requests <-chan Add, done chan<- struct{}) {
	defer close(done)
	total := 0
	for request := range requests {
		total += request.Delta
		request.Reply <- total
	}
}

func main() {
	requests := make(chan Add)
	done := make(chan struct{})
	go Counter(requests, done)
	reply := make(chan int)
	requests <- Add{Delta: 2, Reply: reply}
	fmt.Println(<-reply)
	requests <- Add{Delta: 3, Reply: reply}
	fmt.Println(<-reply)
	close(requests)
	<-done
}

// Output:
// 2
// 5
Read the code

Only Counter reads or writes total. Every request has a reply, so main knows when its change is applied, and done confirms shutdown. The caller must always receive the reply here; abandoned requests would require cancellation or a different response design.

03

Tradeoffs and the simpler alternative

Serialize only what needs one owner; a busy owner can become a bottleneck. Define mailbox capacity and overload behavior, and avoid a cycle of owners synchronously waiting for each other. Channel ownership and a mutex are alternatives chosen by the lifecycle, not by fashion.

TAKE THIS WITH YOU

Serializes mutation through one goroutine’s messages. Serialize only what needs one owner; a busy owner can become a bottleneck. Define mailbox capacity and overload behavior, and avoid a cycle of owners synchronously waiting for each other. Channel ownership and a mutex are alternatives chosen by the lifecycle, not by fashion.

Check your understanding

Which statement best describes Single owner: serialize state through messages?

Go to the source

Progress stays in this browser. No account needed.