Errata

Concurrency in Go

Errata for Concurrency in Go

Submit your own errata for this product.

The errata list is a list of errors and their corrections that were found after the product was released.

The following errata were submitted by our customers and have not yet been approved or disproved by the author or editor. They solely represent the opinion of the customer.

Color Key: Serious technical mistake Minor technical mistake Language or formatting error Typo Question Note Update

Version Location Description Submitted by Date submitted

In chapter 6 there is an illustration of a FIFO queue going from P1 through a centralized queue to P2. Centralized is not spelt correctly in the illustration, it's missing the 'n'

Anonymous  Jul 09, 2020 
Chapter 3
sync.Once section

There is this code describing sync.Once -

var numCalcsCreated int
calcPool := &sync.Pool {
New: func() interface{} {
numCalcsCreated += 1
mem := make([]byte, 1024)
return &mem 1
},
}


New function may potentially be called concurrently by multiple goroutines. This can lead to a data race for accessing numCalcsCreated. To ensure atomic increments, appropriate atomic functions must be used.

Nitish Chandra  Sep 12, 2020 
Printed, PDF Page page 127 code
line 13

log.Fatal("error: %v", err) should be log.Fatalf("error: %v", err)

fanghao  Jun 20, 2022 
Cond section of Chapter 3
Item #2 explaining the expanded example

Item #2 reads: "Next, we create a slice with a length of zero. Since we know we’ll eventually add 10 items, we instantiate it with a capacity of 10."

However, the code is written as to never add more than 2 items to the 'queue' slice, so it's a bit confusing to initialize the queue slice with capacity of 10 and then more confusing to state that the it will eventually hold 10 items when in fact it only ever holds 2.

Sean Kennedy  Feb 12, 2023 
Printed Page Chapter 4 - Confinement - Page 88
Confinement Code Example

On page 88 you show an example with a `printData := func(wg *sync.WaitGroup, data []byte)` that supposedly offers confinement to protect access to the `data` variable.

You state:
> In this example, you can see that because `printData` doesn't close around the data sice it cannot acces it, and needs to take in the slice of a `byte` to operate on.

And further down
> Because of the lexical scope we've made it impossible[^1] to do the wrong thing
[^1]: I'm ignoring the possibility of manually manipulating memory via the `unsafe` package. It's called `unsafe` for a reason!

This method of confinement by passing a slice into a function like this isn't entirely safe. Due to fact that slices are pointers to arrays and `append` on a slice will reuse more elements at the end of the array if available it is possible that manipulate memory in a different part of the array accidentally, thus braking down confinement.

Here is a quick example showing my point, it closely mirrors your example, it doesn't use goroutines so as to make it easier to see what is happening but I think you will see my point.

```
func printData(data []byte) {
fmt.Println(string(data))
data = append(data, []byte("xxx")...)
}

func main() {
data := []byte("golang")
printData(data[:3])
printData(data[3:])
}
```

To truly ensure confinement you would need to make a copy of the array for each call to `printData`
```
func printData(data []byte) {
fmt.Println(string(data))
data = append(data, []byte("xxx")...)
}

func main() {
data := []byte("golang")
d1 := make([]byte, 3)
copy(d1, data[:3])
d2 := make([]byte, 3)
copy(d2, data[3:])
printData(d1)
printData(d2)
}
```

Andy Nitschke  Jun 09, 2023 
1
sync.Cond.Broadcast code sample

The sync.Cond Broadcast code includes a "subscribe" function like this:

```
subscribe := func(c *sync.Cond, fn func()) {
var goroutineRunning sync.WaitGroup
goroutineRunning.Add(1)
go func() {
goroutineRunning.Done()
c.L.Lock()
defer c.L.Unlock()
c.Wait()
fn()
}()
goroutineRunning.Wait()
}
// ... three subscribes ...
button.Clicked.Broadcast()
```

Unfortunately there is no guarantee that all subscribed functions will be at `c.Wait()` before the `button.Clicked.Broadcast()` . This can be pretty easily demonstrated with a test that re-runs the example many times:

```
import (
"sync"
"testing"
)

type Button struct {
Clicked *sync.Cond
}

func TestExampleInBook(t *testing.T) {
// typically fails far earlier
rounds := 1000000

for i:=0; i<rounds; i++ {
button := Button{Clicked: sync.NewCond(&sync.Mutex{})}

subscribe := func(c *sync.Cond, fn func()) {
var goroutineRunning sync.WaitGroup
goroutineRunning.Add(1)
go func() {
goroutineRunning.Done()
c.L.Lock()
defer c.L.Unlock()
c.Wait()
fn()
}()
goroutineRunning.Wait()
}

var clickRegistered sync.WaitGroup
clickRegistered.Add(3)
subscribe(button.Clicked, func() {
clickRegistered.Done()
})
subscribe(button.Clicked, func() {
clickRegistered.Done()
})
subscribe(button.Clicked, func() {
clickRegistered.Done()
})

button.Clicked.Broadcast()

clickRegistered.Wait()
}
}
```

Unfortunately there's no quick fix for this. The root of the problem is that there's no way to make sure all subscribed function's goroutines are at `c.Wait()`, because 1) you can't send any signal after that point to signal readiness, and 2) any signal sent before that point then needs to race to the `c.Wait()` with whatever is listening to that signal.

Unfortunately, sync.Cond suffers from this in *any* construct where your logic requires something to be waiting. There's simply no guarantee that you have any waiters, and the broadcast is not "buffered" so if they're not ready nothing happens. Periodic broadcasts / a system where a single failure doesn't matter fit just fine, but that's not what this code sample shows, nor is it called out in the text.

Steven Littiebrant  Apr 04, 2018 
Printed Page 1
124

the simple pipeline example uses a function
sleep(done, 1*time.Second, zeros)

Maybe I missed it, but I couldn't find a definition of sleep in that chapter.
Can you please clarify ?

Thanks

Edward Ishaq  Apr 21, 2019 
Printed Page 4
3rd

Extract: "Indeed for this very book, I've gotten as many eyes as *possbile* on the code to try and mitigate this."

'possbile' must be 'possible'.

Baumes  Jul 18, 2020 
PDF Page 8
first code sample, at the top of the page

The body of the anonymous function on the second line is surrounded by unbalanced space.

Where it reads:

go func() { data++}()

it should perhaps read¹:

go func() { data++ }()

¹: gofmt concurrs.

pancho horrillo  Apr 01, 2021 
Printed, PDF, Other Digital Version Page 37-39
Concept of demo

The author has used an example of two people walking TOWARDS each other down a hallway and playing a game of "hallway-shuffle". However, the code used to demonstrate it appears to neglect the fact that the two people in the hallway are facing opposite directions. One person's left is the other person's right. The output of the demo code is as follows:

Alice is trying to scoot: left right left right left right left right left right
Alice tosses her hands up in exasperation!
Barbara is trying to scoot: left right left right left right left right left right
Barbara tosses her hands up in exasperation!

If this were the case in real life and Alice and Barbara each move to their 'left' then they will pass each other without issue.

Am I missing something?

Alex Matthews  Nov 26, 2020 
Printed, PDF Page 52
2nd paragraph

> 2. Here we make the producer sleep for one second to make it less active than the observer goroutines.

This should actually read “nanosecond”, not “second”. Or the code sample on the page before should be extended with a time.Second multiplier - but that would substantially change the outcome shown in the table.

Anonymous  Nov 24, 2018 
Printed Page 56
snippet of code

The section shows a possible usage of sync.Cond.
If, for some reasons, the goroutines run a bit slower at line 19 (I have simulated it with a time.Sleep), the program goes deadlock as the sync.Cond.Broadcast method is invoked before all the goroutines invoke sync.Cond.Wait.

Change to the snippet:
go func() {
goroutineRunning.Done()
// Simulating a delay during the execution of the goroutine
time.Sleep(1 * time.Second)
c.L.Lock()
defer c.L.Unlock()
c.Wait()
fn()
}()

Mirko Bonasorte  Aug 01, 2023 
Printed Page 57
3rd paragraph

The statement given in sentence is incorrect:

"Were it not for the clickRegistered waitgroup, we could colud call button.Clicked.Broadcast() multiple times...".

Goroutine started by "subscribe" will be executed only once and terminate. Subsequent calls to button.Clicked.Broadcast() will have no effect.

Marin Prcela  Oct 15, 2017 
PDF Page 57
After the output

On page 57:

You can see that with one call to Broadcast on the Clicked Cond, all three
handlers are run. Were it not for the clickRegistered WaitGroup, we could call
button.Clicked.Broadcast() multiple times, and each time all three handlers
would be invoked. This is something channels can’t do easily and thus is one of the main reasons to utilize the Cond type.

I don't understand the second and third sentences, because that one call of button.Clicked.Broadcast() makes all handers return, so multiple-times calls doesn't make sense.

Yoshiki Shibata  Dec 01, 2018 
Printed Page 57
top half

The callout numbering is incorrect.

4 should be 3,
5 should be 4,
6 should be 5,
7 should be 6,
3 partly corresponds to 7.

Sonia Hamilton  Aug 14, 2019 
Printed Page 61
middle of page

"8 calculators were created" should read "4 calculators were created".

I tested this by running the code.

Sonia Hamilton  Aug 14, 2019 
Printed Page 63
2

We let the producer wait for 1s
This should not be 1s, it should be 1 Nanosecond

zhaoyanhui  Oct 10, 2020 
Printed Page 75
Table 3-2

Line 2 of table should read:

"Open and Not Empty Value, true"

Sonia Hamilton  Aug 14, 2019 
Printed Page 76
List 1, Item 4

"Ecapsulate ..." should be "Encapsulate ..."

Samvel  Jan 13, 2019 
Other Digital Version 82
4th Paragraph

"The second return value is a way for a read operation to indicate whther the read off..."
should be
""The second return value is a way for a read operation to indicate whther the read of...""

Chengtao Wen  Apr 11, 2018 
Printed Page 86
4th paragraph "Ad hoc confinement..."

"...whether it be set by the languages community..."

should be -

"...whether it be set by the language's community..."

Anonymous  Dec 04, 2018 
Printed Page 91
2nd paragraph

Currently printed: "Therefore, the strings channel will never actually gets any strings written onto it, ..."
Should be: "Therefore, the strings channel will never actually get any strings written onto it, ...)

Taavi Kivisik  Aug 15, 2019 
Printed Page 106
1st paragraph

the first line saying "...constructs a buffered channel of integers with a length equal to..." while in all the related previous sample codes, it's using a unbufferred channel.

Fanghao  Jun 17, 2022 
Printed Page 108
Table

In this example the done channel, that is shared amongst all stages of the pipeline, is closed. The behavior seen in the table looks like only the generator receives a done and the rest of the pipeline completing normally.
Wouldn't the outcome of this be nondeterministic, with the more likely outcome of all or at least some stages of the pipeline closing simultaneously.

Tobias Mühlberger  Aug 28, 2017 
Printed Page 110
Code example at top of page

There is a line in the example:

case takeStream <- <- valueStream:

That should be:

case takeStream <- valueStream:

Dave Rolsky  Apr 30, 2018 
Printed Page 112
first block of code

In the function "toString", the following line will of course panic if anything but a string is passed in:

"case stringStream <- v.(string):"

Sonia Hamilton  Aug 15, 2019 
Printed Page 113
halfway down the page

The following line has an extra <-

case takeStream <- <- valueStream:

Sonia Hamilton  Aug 15, 2019 
Printed Page 113
halfway down the page

The two arrows are correct, my previous errata is incorrect...

Sonia Hamilton  Aug 15, 2019 
Printed Page 117
1st paragraph

The text says "fanning means multplexing or joining together multiple streams of data ..."

Multiplexing is the fan _out_ part. The fan in is demultiplexing (or demuxing).

Dave Rolsky  Apr 30, 2018 
Printed Page 125
sample code

`buffer := buffer(done, 2, short)` should be error on compile time as both are not in the same type. (channel and function)

I assume the following is the intended code.

```
done := make(chan interface{})
defer close(done)

zeros := take(done, 3, repeat(done, 0))
short := sleep(done, 1*time.Second, zeros)
buf := buffer(done, 2, short) // Buffers sends from short by 2
long := sleep(done, 4*time.Second, buf)
pipeline := long
```

Yoshi Yamaguchi  Aug 24, 2018 
Printed Page 127
code sample

Instead of:

func BenchmarkBufferedWrite(b *testing.B) {
bufferredFile := bufio.NewWriter(tmpFileOrFatal())
performWrite(b, bufio.NewWriter(bufferredFile))
}


I guess should be:

func BenchmarkBufferedWrite(b *testing.B) {
bufferredFile := bufio.NewWriter(tmpFileOrFatal())
performWrite(b, bufferredFile)
}

So we don't need to wrap twice in a buffer.

José Luis Martínez de la Riva Manzano  Sep 02, 2018 
Printed Page 127
code snippet, 2nd function

The bufferredFile is already a buffered writer in BenchmarkBufferedWrite function.

So, the change should be from:

performaWrite(b, bufio.NewWriter(bufferedFile))

to:

performaWrite(b, bufferedFile)

Samvel  Jan 13, 2019 
Printed Page 130
last 2 equations

The equation should resolve into:

Lr = 13r

because the second line from the bottom is equivalent to:

x - 3 = 10

Which leads to:

x = 13

Samvel  Jan 13, 2019 
Printed Page 131
1st code snippet

indicate that CancelFunc is a function, and Context is an interface, e.g. change from:

type CancelFunc
type Context

to:

type CancelFunc func()
type Context interface { ... }

Samvel  Jan 13, 2019 
Printed Page 136
diagram

One side should be Farewell instead of printGreeting

Samvel  Jan 13, 2019 
Printed Page 139
diagram

one side of the graph should be Farewell

Samvel  Jan 13, 2019 
Printed Page 152
first sample code

Though there's a function call to `isGloballyExec()` in the function `runJob`, this will be error on compile time, as `isGloballyExec` is in "lowlevel" package. The sample code should be:

```
type IntermediateErr struct {
error
}

func runJob(id string) error {
const jobBinPath = "/bad/job/binary"
isExecutable, err := lowlevel.isGloballyExec(jobBinPath)
if err != nil {
return err
} else if isExecutable == false {
return wrapError(nil, "job binary is not executable")
}

return exec.Command(jobBinPath, "--id="+id).Run()
}
```

Yoshi Yamaguchi  Aug 24, 2018 
Printed Page 159
2nd paragraph

The paragraph starts with the lower case.

Samvel  Jan 13, 2019 
Printed Page 159
2nd paragraph

The first letter of the first word in the sentence is not capitalised:

“there’s another problem lurking here as well: ...”

Should be:

“There’s another problem lurking here as well: ...”

James Allured  Apr 19, 2021 
159
2

Book content: there’s another problem lurking here as well
Problem: The initial letter in a sentence (paragraph) is not capitalized.
Expected: There’s another problem lurking here as well

Arun Saha  Aug 09, 2022 
Printed Page 160
diagram

he text talks about Stage A2, while the diagram has "Stage 2", e.g. it should be "Stage A2"

Samvel  Jan 13, 2019 
Printed Page 167
callout #2

"results" should be "workStream" ie

"We don't want to include this in the same select black as the send on workStream because..."

Sonia Hamilton  Aug 19, 2019 
Printed Page 172
2nd to last paragraph

The paragraph talks about "nanoseconds" but the code is using seconds.

Dave Rolsky  May 08, 2018 
Printed Page 189
code snippet

it looks like the wardDone channel is shared between the old and new wards; what happens in the following sequence of events:

the channel closes
the new ward starts and overwrites the wardDone
the old ward accesses the wardDone
which channel will the old ward access: will it be the new channel?

It might be better (but take more space) to encapsulate the ward hearbeats and done channels into a structure.

Samvel  Jan 13, 2019 
Printed Page 191
line -8

zeros := take(done, 3, repeat(done, 0))
=>
zeros := take(done, repeat(done, 0), 3)

Soojin Nam  Feb 02, 2020 
Printed Page 192
code example

it seems there are multiple channel leaks, namely:

- heartbeat
- intChanStream

Samvel  Jan 13, 2019 
Printed Page 202
1st paragraph

the text refers to the fib(2) called from the fib(3), and the notation is <caller>-<callee>, e.g. 3-2.

Thus, is should change from:

fib(3) (i.e. 3-1)

to:

fib(3) (i.e. 3-2)

Samvel  Jan 13, 2019 
Printed Page 207
table, T2 call stack column

the T1 finished calculating fib(2) and is in the state (returns 2), while T2 is at the moment waiting for the goroutines to finish fib(4).

Thus, the table should change from:

(returns 2)

to:

fib(4) (unrealized join point)

Samvel  Jan 13, 2019 
Printed Page 208
single thread tables, T1 work queue column

change from:

main

to:

cont. of main

since the table describes continuation stealing.

Samvel  Jan 13, 2019 
Printed Page 209
tables 3-4, T1 work queue column

should have cont of fib(3) (unrealized join point)

Samvel  Jan 13, 2019 
Printed Page 209
tables 6-7, T1 work queue column

should have cont of fib(4) (unrealized join point)

Samvel  Jan 13, 2019