mirror of
https://github.com/teivah/100-go-mistakes.git
synced 2026-06-20 16:45:56 +08:00
New section and content
This commit is contained in:
parent
884eda6411
commit
c394febf0a
27 changed files with 1886 additions and 22 deletions
BIN
.cache/plugin/social/822b97f16d5919e56bc94d3567cd9a8d.png
Normal file
BIN
.cache/plugin/social/822b97f16d5919e56bc94d3567cd9a8d.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
207
docs/56-concurrency-faster.md
Normal file
207
docs/56-concurrency-faster.md
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
---
|
||||
title: Thinking concurrency is always faster (#56)
|
||||
comments: true
|
||||
---
|
||||
|
||||
# Thinking concurrency is always faster
|
||||
|
||||
A misconception among many developers is believing that a concurrent solution is always faster than a sequential one. This couldn’t be more wrong. The overall performance of a solution depends on many factors, such as the efficiency of our code structure (concurrency), which parts can be tackled in parallel, and the level of contention among the computation units. This post reminds us about some fundamental knowledge of concurrency in Go; then we will see a concrete example where a concurrent solution isn’t necessarily faster.
|
||||
|
||||
## Go Scheduling
|
||||
|
||||
A thread is the smallest unit of processing that an OS can perform. If a process wants to execute multiple actions simultaneously, it spins up multiple threads. These threads can be:
|
||||
|
||||
* _Concurrent_ — Two or more threads can start, run, and complete in overlapping time periods.
|
||||
* _Parallel_ — The same task can be executed multiple times at once.
|
||||
|
||||
The OS is responsible for scheduling the thread’s processes optimally so that:
|
||||
|
||||
* All the threads can consume CPU cycles without being starved for too much time.
|
||||
* The workload is distributed as evenly as possible among the different CPU cores.
|
||||
|
||||
???+ note
|
||||
|
||||
The word thread can also have a different meaning at a CPU level. Each physical core can be composed of multiple logical cores (the concept of [hyper-threading](https://en.wikipedia.org/wiki/Hyper-threading)), and a logical core is also called a thread. In this post, when we use the word thread, we mean the unit of processing, not a logical core.
|
||||
|
||||
A CPU core executes different threads. When it switches from one thread to another, it executes an operation called _context switching_. The active thread consuming CPU cycles was in an _executing_ state and moves to a _runnable_ state, meaning it’s ready to be executed pending an available core. Context switching is considered an expensive operation because the OS needs to save the current execution state of a thread before the switch (such as the current register values).
|
||||
|
||||
As Go developers, we can’t create threads directly, but we can create goroutines, which can be thought of as application-level threads. However, whereas an OS thread is context-switched on and off a CPU core by the OS, a goroutine is context-switched on and off an OS thread by the Go runtime. Also, compared to an OS thread, a goroutine has a smaller memory footprint: 2 KB for goroutines from Go 1.4. An OS thread depends on the OS, but, for example, on Linux/x86–32, the default size is 2 MB (see https://man7.org/linux/man-pages/man3/pthread_create.3.html). Having a smaller size makes context switching faster.
|
||||
|
||||
???+ note
|
||||
|
||||
Context switching a goroutine versus a thread is about 80% to 90% faster, depending on the architecture.
|
||||
|
||||
Let’s now discuss how the Go scheduler works to overview how goroutines are handled. Internally, the Go scheduler uses the following terminology (see [proc.go](https://github.com/golang/go/blob/go1.17.6/src/runtime/proc.go#L22)):
|
||||
|
||||
* _G_ — Goroutine
|
||||
* _M_ — OS thread (stands for machine)
|
||||
* _P_ — CPU core (stands for processor)
|
||||
|
||||
Each OS thread (M) is assigned to a CPU core (P) by the OS scheduler. Then, each goroutine (G) runs on an M. The GOMAXPROCS variable defines the limit of Ms in charge of executing user-level code simultaneously. But if a thread is blocked in a system call (for example, I/O), the scheduler can spin up more Ms. As of Go 1.5, GOMAXPROCS is by default equal to the number of available CPU cores.
|
||||
|
||||
A goroutine has a simpler lifecycle than an OS thread. It can be doing one of the following:
|
||||
|
||||
* _Executing_ — The goroutine is scheduled on an M and executing its instructions.
|
||||
* _Runnable_ — The goroutine is waiting to be in an executing state.
|
||||
* _Waiting_ — The goroutine is stopped and pending something completing, such as a system call or a synchronization operation (such as acquiring a mutex).
|
||||
|
||||
There’s one last stage to understand about the implementation of Go scheduling: when a goroutine is created but cannot be executed yet; for example, all the other Ms are already executing a G. In this scenario, what will the Go runtime do about it? The answer is queuing. The Go runtime handles two kinds of queues: one local queue per P and a global queue shared among all the Ps.
|
||||
|
||||
Figure 1 shows a given scheduling situation on a four-core machine with GOMAXPROCS equal to 4. The parts are the logical cores (Ps), goroutines (Gs), OS threads (Ms), local queues, and global queue:
|
||||
|
||||
<figure markdown>
|
||||

|
||||
<figcaption>Figure 1: An example of the current state of a Go application executed on a four-core machine. Goroutines that aren’t in an executing state are either runnable (pending being executed) or waiting (pending a blocking operation)</figcaption>
|
||||
</figure>
|
||||
|
||||
First, we can see five Ms, whereas GOMAXPROCS is set to 4. But as we mentioned, if needed, the Go runtime can create more OS threads than the GOMAXPROCS value.
|
||||
|
||||
P0, P1, and P3 are currently busy executing Go runtime threads. But P2 is presently idle as M3 is switched off P2, and there’s no goroutine to be executed. This isn’t a good situation because six runnable goroutines are pending being executed, some in the global queue and some in other local queues. How will the Go runtime handle this situation? Here’s the scheduling implementation in pseudocode (see [proc.go](https://github.com/golang/go/blob/go1.17.6/src/runtime/proc.go#L3291)):
|
||||
|
||||
```
|
||||
runtime.schedule() {
|
||||
// Only 1/61 of the time, check the global runnable queue for a G.
|
||||
// If not found, check the local queue.
|
||||
// If not found,
|
||||
// Try to steal from other Ps.
|
||||
// If not, check the global runnable queue.
|
||||
// If not found, poll network.
|
||||
}
|
||||
```
|
||||
|
||||
Every sixty-first execution, the Go scheduler will check whether goroutines from the global queue are available. If not, it will check its local queue. Meanwhile, if both the global and local queues are empty, the Go scheduler can pick up goroutines from other local queues. This principle in scheduling is called _work stealing_, and it allows an underutilized processor to actively look for another processor’s goroutines and _steal_ some.
|
||||
|
||||
One last important thing to mention: prior to Go 1.14, the scheduler was cooperative, which meant a goroutine could be context-switched off a thread only in specific blocking cases (for example, channel send or receive, I/O, waiting to acquire a mutex). Since Go 1.14, the Go scheduler is now preemptive: when a goroutine is running for a specific amount of time (10 ms), it will be marked preemptible and can be context-switched off to be replaced by another goroutine. This allows a long-running job to be forced to share CPU time.
|
||||
|
||||
Now that we understand the fundamentals of scheduling in Go, let’s look at a concrete example: implementing a merge sort in a parallel manner.
|
||||
|
||||
## Parallel Merge Sort
|
||||
|
||||
First, let’s briefly review how the merge sort algorithm works. Then we will implement a parallel version. Note that the objective isn’t to implement the most efficient version but to support a concrete example showing why concurrency isn’t always faster.
|
||||
|
||||
The merge sort algorithm works by breaking a list repeatedly into two sublists until each sublist consists of a single element and then merging these sublists so that the result is a sorted list (see figure 2). Each split operation splits the list into two sublists, whereas the merge operation merges two sublists into a sorted list.
|
||||
|
||||
<figure markdown>
|
||||

|
||||
<figcaption>Figure 2: Applying the merge sort algorithm repeatedly breaks each list into two sublists. Then the algorithm uses a merge operation such that the resulting list is sorted</figcaption>
|
||||
</figure>
|
||||
|
||||
Here is the sequential implementation of this algorithm. We don’t include all of the code as it’s not the main point of this section:
|
||||
|
||||
```go
|
||||
func sequentialMergesort(s []int) {
|
||||
if len(s) <= 1 {
|
||||
return
|
||||
}
|
||||
|
||||
middle := len(s) / 2
|
||||
sequentialMergesort(s[:middle]) // First half
|
||||
sequentialMergesort(s[middle:]) // Second half
|
||||
merge(s, middle) // Merges the two halves
|
||||
}
|
||||
|
||||
func merge(s []int, middle int) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
This algorithm has a structure that makes it open to concurrency. Indeed, as each _sequentialMergesort_ operation works on an independent set of data that doesn’t need to be fully copied (here, an independent view of the underlying array using slicing), we could distribute this workload among the CPU cores by spinning up each _sequentialMergesort_ operation in a different goroutine. Let’s write a first parallel implementation:
|
||||
|
||||
```go
|
||||
func parallelMergesortV1(s []int) {
|
||||
if len(s) <= 1 {
|
||||
return
|
||||
}
|
||||
|
||||
middle := len(s) / 2
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() { // Spins up the first half of the work in a goroutine
|
||||
defer wg.Done()
|
||||
parallelMergesortV1(s[:middle])
|
||||
}()
|
||||
|
||||
go func() { // Spins up the second half of the work in a goroutine
|
||||
defer wg.Done()
|
||||
parallelMergesortV1(s[middle:])
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
merge(s, middle) // Merges the halves
|
||||
}
|
||||
```
|
||||
|
||||
In this version, each half of the workload is handled in a separate goroutine. The parent goroutine waits for both parts by using _sync.WaitGroup_. Hence, we call the Wait method before the merge operation.
|
||||
|
||||
We now have a parallel version of the merge sort algorithm. Therefore, if we run a benchmark to compare this version against the sequential one, the parallel version should be faster, correct? Let’s run it on a four-core machine with 10,000 elements:
|
||||
|
||||
```
|
||||
Benchmark_sequentialMergesort-4 2278993555 ns/op
|
||||
Benchmark_parallelMergesortV1-4 17525998709 ns/op
|
||||
```
|
||||
|
||||
Surprisingly, the parallel version is almost an order of magnitude slower. How can we explain this result? How is it possible that a parallel version that distributes a workload across four cores is slower than a sequential version running on a single machine? Let’s analyze the problem.
|
||||
|
||||
If we have a slice of, say, 1,024 elements, the parent goroutine will spin up two goroutines, each in charge of handling a half consisting of 512 elements. Each of these goroutines will spin up two new goroutines in charge of handling 256 elements, then 128, and so on, until we spin up a goroutine to compute a single element.
|
||||
|
||||
If the workload that we want to parallelize is too small, meaning we’re going to compute it too fast, the benefit of distributing a job across cores is destroyed: the time it takes to create a goroutine and have the scheduler execute it is much too high compared to directly merging a tiny number of items in the current goroutine. Although goroutines are lightweight and faster to start than threads, we can still face cases where a workload is too small.
|
||||
|
||||
So what can we conclude from this result? Does it mean the merge sort algorithm cannot be parallelized? Wait, not so fast.
|
||||
|
||||
Let’s try another approach. Because merging a tiny number of elements within a new goroutine isn’t efficient, let’s define a threshold. This threshold will represent how many elements a half should contain in order to be handled in a parallel manner. If the number of elements in the half is fewer than this value, we will handle it sequentially. Here’s a new version:
|
||||
|
||||
```go
|
||||
const max = 2048 // Defines the threshold
|
||||
|
||||
func parallelMergesortV2(s []int) {
|
||||
if len(s) <= 1 {
|
||||
return
|
||||
}
|
||||
|
||||
if len(s) <= max {
|
||||
sequentialMergesort(s) // Calls our initial sequential version
|
||||
} else { // If bigger than the threshold, keeps the parallel version
|
||||
middle := len(s) / 2
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
parallelMergesortV2(s[:middle])
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
parallelMergesortV2(s[middle:])
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
merge(s, middle)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the number of elements in the s slice is smaller than max, we call the sequential version. Otherwise, we keep calling our parallel implementation. Does this approach impact the result? Yes, it does:
|
||||
|
||||
```
|
||||
Benchmark_sequentialMergesort-4 2278993555 ns/op
|
||||
Benchmark_parallelMergesortV1-4 17525998709 ns/op
|
||||
Benchmark_parallelMergesortV2-4 1313010260 ns/op
|
||||
```
|
||||
|
||||
Our v2 parallel implementation is more than 40% faster than the sequential one, thanks to this idea of defining a threshold to indicate when parallel should be more efficient than sequential.
|
||||
|
||||
???+ note
|
||||
|
||||
Why did I set the threshold to 2,048? Because it was the optimal value for this specific workload on my machine. In general, such magic values should be defined carefully with benchmarks (running on an execution environment similar to production). It’s also pretty interesting to note that running the same algorithm in a programming language that doesn’t implement the concept of goroutines has an impact on the value. For example, running the same example in Java using threads means an optimal value closer to 8,192. This tends to illustrate how goroutines are more efficient than threads.
|
||||
|
||||
## Conclusion
|
||||
|
||||
We have seen throughout this post the fundamental concepts of scheduling in Go: the differences between a thread and a goroutine and how the Go runtime schedules goroutines. Meanwhile, using the parallel merge sort example, we illustrated that concurrency isn’t always necessarily faster. As we have seen, spinning up goroutines to handle minimal workloads (merging only a small set of elements) demolishes the benefit we could get from parallelism.
|
||||
|
||||
So, where should we go from here? We must keep in mind that concurrency isn’t always faster and shouldn’t be considered the default way to go for all problems. First, it makes things more complex. Also, modern CPUs have become incredibly efficient at executing sequential code and predictable code. For example, a superscalar processor can parallelize instruction execution over a single core with high efficiency.
|
||||
|
||||
Does this mean we shouldn’t use concurrency? Of course not. However, it’s essential to keep these conclusions in mind. If we’re not sure that a parallel version will be faster, the right approach may be to start with a simple sequential version and build from there using profiling (mistake #98, “[Not using Go diagnostics tooling](https://100go.co/98-profiling-execution-tracing/)”) and benchmarks (mistake #89, “[Writing inaccurate benchmarks](https://100go.co/89-benchmarks/)”), for example. It can be the only way to ensure that a concurrent implementation is worth it.
|
||||
BIN
docs/img/go-scheduler.png
Normal file
BIN
docs/img/go-scheduler.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
BIN
docs/img/mergesort.png
Normal file
BIN
docs/img/mergesort.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
125
docs/index.md
125
docs/index.md
|
|
@ -6,8 +6,6 @@ comments: true
|
|||
|
||||
This page is a summary of all the mistakes in the 100 Go Mistakes book. Meanwhile, it's also a page open to the community. If you believe that a mistake should be added, please create a [community mistake issue](https://github.com/teivah/100-go-mistakes/issues/new?assignees=&labels=community+mistake&template=community_mistake.md&title=).
|
||||
|
||||
If you want to engage with the community (asking questions, discussing options, etc.), feel free to use the [Discussions](https://github.com/teivah/100-go-mistakes/discussions) space on the GitHub repo.
|
||||
|
||||
???+ warning
|
||||
|
||||
You're currently viewing a new version that I'm enriching with significantly more content. Yet, this version is still under development; please be gentle if you find an issue, and feel free to create a PR.
|
||||
|
|
@ -270,10 +268,6 @@ The functional options pattern provides a handy and API-friendly way to handle o
|
|||
|
||||
### Project misorganization (project structure and package organization) (#12)
|
||||
|
||||
???+ info "TL;DR"
|
||||
|
||||
Following a layout such as [project-layout](https://github.com/golang-standards/project-layout) can be a good way to start structuring Go projects, especially if you are looking for existing conventions to standardize a new project.
|
||||
|
||||
Regarding the overall organization, there are different schools of thought. For example, should we organize our application by context or by layer? It depends on our preferences. We may favor grouping code per context (such as the customer context, the contract context, etc.), or we may favor following hexagonal architecture principles and group per technical layer. If the decision we make fits our use case, it cannot be a wrong decision, as long as we remain consistent with it.
|
||||
|
||||
Regarding packages, there are multiple best practices that we should follow. First, we should avoid premature packaging because it might cause us to overcomplicate a project. Sometimes, it’s better to use a simple organization and have our project evolve when we understand what it contains rather than forcing ourselves to make the perfect structure up front.
|
||||
|
|
@ -285,6 +279,10 @@ Regarding what to export, the rule is pretty straightforward. We should minimize
|
|||
|
||||
Organizing a project isn’t straightforward, but following these rules should help make it easier to maintain. However, remember that consistency is also vital to ease maintainability. Therefore, let’s make sure that we keep things as consistent as possible within a codebase.
|
||||
|
||||
???+ note
|
||||
|
||||
In 2023, the Go team has published an official guideline for organizing / structuring a Go project: [go.dev/doc/modules/layout](https://go.dev/doc/modules/layout)
|
||||
|
||||
### Creating utility packages (#13)
|
||||
|
||||
???+ info "TL;DR"
|
||||
|
|
@ -1527,11 +1525,9 @@ In summary, let’s be mindful that a goroutine is a resource like any other tha
|
|||
|
||||
### Not being careful with goroutines and loop variables (#63)
|
||||
|
||||
???+ info "TL;DR"
|
||||
???+ warning
|
||||
|
||||
To avoid bugs with goroutines and loop variables, create local variables or call functions instead of closures.
|
||||
|
||||
[Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/63-goroutines-loop-variables/main.go)
|
||||
This mistake isn't relevant anymore from Go 1.22 ([source](https://go.dev/blog/loopvar-preview)).
|
||||
|
||||
### Expecting a deterministic behavior using select and channels (#64)
|
||||
|
||||
|
|
@ -1539,6 +1535,53 @@ In summary, let’s be mindful that a goroutine is a resource like any other tha
|
|||
|
||||
Understanding that `select` with multiple channels chooses the case randomly if multiple options are possible prevents making wrong assumptions that can lead to subtle concurrency bugs.
|
||||
|
||||
One common mistake made by Go developers while working with channels is to make wrong assumptions about how select behaves with multiple channels.
|
||||
|
||||
For example, let's consider the following case (`disconnectCh` is a buffered channel):
|
||||
|
||||
```go
|
||||
go func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
messageCh <- i
|
||||
}
|
||||
disconnectCh <- struct{}{}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case v := <-messageCh:
|
||||
fmt.Println(v)
|
||||
case <-disconnectCh:
|
||||
fmt.Println("disconnection, return")
|
||||
return
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If we run this example multiple times, the result will be random:
|
||||
|
||||
```
|
||||
0
|
||||
1
|
||||
2
|
||||
disconnection, return
|
||||
|
||||
0
|
||||
disconnection, return
|
||||
```
|
||||
|
||||
Instead of consuming the 10 messages, we only received a few of them. What’s the reason? It lies in the specification of the select statement with multiple channels (https:// go.dev/ref/spec):
|
||||
|
||||
!!! quote
|
||||
|
||||
If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection.
|
||||
|
||||
Unlike a switch statement, where the first case with a match wins, the select state- ment selects randomly if multiple options are possible.
|
||||
|
||||
This behavior might look odd at first, but there’s a good reason for it: to prevent possible starvation. Suppose the first possible communication chosen is based on the source order. In that case, we may fall into a situation where, for example, we only receive from one channel because of a fast sender. To prevent this, the language designers decided to use a random selection.
|
||||
|
||||
When using `select` with multiple channels, we must remember that if multiple options are possible, the first case in the source order does not automatically win. Instead, Go selects randomly, so there’s no guarantee about which option will be chosen. To overcome this behavior, in the case of a single producer goroutine, we can use either unbuffered channels or a single channel.
|
||||
|
||||
[Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/64-select-behavior/main.go)
|
||||
|
||||
### Not using notification channels (#65)
|
||||
|
|
@ -1547,12 +1590,74 @@ In summary, let’s be mindful that a goroutine is a resource like any other tha
|
|||
|
||||
Send notifications using a `chan struct{}` type.
|
||||
|
||||
Channels are a mechanism for communicating across goroutines via signaling. A signal can be either with or without data.
|
||||
|
||||
Let’s look at a concrete example. We will create a channel that will notify us whenever a certain disconnection occurs. One idea is to handle it as a `chan bool`:
|
||||
|
||||
```go
|
||||
disconnectCh := make(chan bool)
|
||||
```
|
||||
|
||||
Now, let’s say we interact with an API that provides us with such a channel. Because it’s a channel of Booleans, we can receive either `true` or `false` messages. It’s probably clear what `true` conveys. But what does `false` mean? Does it mean we haven’t been disconnected? And in this case, how frequently will we receive such a signal? Does it mean we have reconnected? Should we even expect to receive `false`? Perhaps we should only expect to receive `true` messages.
|
||||
|
||||
If that’s the case, meaning we don’t need a specific value to convey some information, we need a channel _without_ data. The idiomatic way to handle it is a channel of empty structs: `chan struct{}`.
|
||||
|
||||
### Not using nil channels (#66)
|
||||
|
||||
???+ info "TL;DR"
|
||||
|
||||
Using nil channels should be part of your concurrency toolset because it allows you to _remove_ cases from `select` statements, for example.
|
||||
|
||||
What should this code do?
|
||||
|
||||
```go
|
||||
var ch chan int
|
||||
<-ch
|
||||
```
|
||||
|
||||
`ch` is a `chan int` type. The zero value of a channel being nil, `ch` is `nil`. The goroutine won’t panic; however, it will block forever.
|
||||
|
||||
The principle is the same if we send a message to a nil channel. This goroutine blocks forever:
|
||||
|
||||
```go
|
||||
var ch chan int
|
||||
ch <- 0
|
||||
```
|
||||
|
||||
Then what’s the purpose of Go allowing messages to be received from or sent to a nil channel? For example, we can use nil channels to implement an idiomatic way to merge two channels:
|
||||
|
||||
```go
|
||||
func merge(ch1, ch2 <-chan int) <-chan int {
|
||||
ch := make(chan int, 1)
|
||||
|
||||
go func() {
|
||||
for ch1 != nil || ch2 != nil { // Continue if at least one channel isn’t nil
|
||||
select {
|
||||
case v, open := <-ch1:
|
||||
if !open {
|
||||
ch1 = nil // Assign ch1 to a nil channel once closed
|
||||
break
|
||||
}
|
||||
ch <- v
|
||||
case v, open := <-ch2:
|
||||
if !open {
|
||||
ch2 = nil // Assigns ch2 to a nil channel once closed
|
||||
break
|
||||
}
|
||||
ch <- v
|
||||
}
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
```
|
||||
|
||||
This elegant solution relies on nil channels to somehow _remove_ one case from the `select` statement.
|
||||
|
||||
Let’s keep this idea in mind: nil channels are useful in some conditions and should be part of the Go developer’s toolset when dealing with concurrent code.
|
||||
|
||||
[Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/66-nil-channels/main.go)
|
||||
|
||||
### Being puzzled about channel size (#67)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
.md-typeset figure img {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.md-typeset .admonition,
|
||||
.md-typeset details {
|
||||
font-size: 15px
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ nav:
|
|||
- 9-generics.md
|
||||
- 20-slice.md
|
||||
- 28-maps-memory-leaks.md
|
||||
- 56-concurrency-faster.md
|
||||
- 89-benchmarks.md
|
||||
- 98-profiling-execution-tracing.md
|
||||
- zh.md
|
||||
|
|
|
|||
|
|
@ -659,6 +659,26 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../56-concurrency-faster/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Thinking concurrency is always faster (#56)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<link rel="prev" href="../20-slice/">
|
||||
|
||||
|
||||
<link rel="next" href="../89-benchmarks/">
|
||||
<link rel="next" href="../56-concurrency-faster/">
|
||||
|
||||
|
||||
<link rel="icon" href="../img/Go-Logo_LightBlue.svg">
|
||||
|
|
@ -659,6 +659,26 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../56-concurrency-faster/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Thinking concurrency is always faster (#56)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
|
|
|||
|
|
@ -595,6 +595,26 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/56-concurrency-faster/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Thinking concurrency is always faster (#56)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
|
|
|||
1218
site/56-concurrency-faster/index.html
Normal file
1218
site/56-concurrency-faster/index.html
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -11,7 +11,7 @@
|
|||
<link rel="canonical" href="https://100go.co/89-benchmarks/">
|
||||
|
||||
|
||||
<link rel="prev" href="../28-maps-memory-leaks/">
|
||||
<link rel="prev" href="../56-concurrency-faster/">
|
||||
|
||||
|
||||
<link rel="next" href="../98-profiling-execution-tracing/">
|
||||
|
|
@ -648,6 +648,26 @@
|
|||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../56-concurrency-faster/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Thinking concurrency is always faster (#56)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -710,6 +710,26 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../56-concurrency-faster/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Thinking concurrency is always faster (#56)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
|
|
|||
|
|
@ -649,6 +649,26 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../56-concurrency-faster/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Thinking concurrency is always faster (#56)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
|
|
|||
BIN
site/assets/images/social/56-concurrency-faster.png
Normal file
BIN
site/assets/images/social/56-concurrency-faster.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
|
|
@ -713,6 +713,26 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../56-concurrency-faster/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Thinking concurrency is always faster (#56)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
|
|
|||
|
|
@ -770,6 +770,26 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../56-concurrency-faster/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Thinking concurrency is always faster (#56)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
|
|
|||
20
site/external/index.html
vendored
20
site/external/index.html
vendored
|
|
@ -715,6 +715,26 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../56-concurrency-faster/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Thinking concurrency is always faster (#56)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
|
|
|||
BIN
site/img/go-scheduler.png
Normal file
BIN
site/img/go-scheduler.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
BIN
site/img/mergesort.png
Normal file
BIN
site/img/mergesort.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
116
site/index.html
116
site/index.html
|
|
@ -1611,6 +1611,26 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="56-concurrency-faster/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Thinking concurrency is always faster (#56)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
|
@ -2712,7 +2732,6 @@
|
|||
|
||||
<h1 id="common-go-mistakes">Common Go Mistakes</h1>
|
||||
<p>This page is a summary of all the mistakes in the 100 Go Mistakes book. Meanwhile, it's also a page open to the community. If you believe that a mistake should be added, please create a <a href="https://github.com/teivah/100-go-mistakes/issues/new?assignees=&labels=community+mistake&template=community_mistake.md&title=">community mistake issue</a>.</p>
|
||||
<p>If you want to engage with the community (asking questions, discussing options, etc.), feel free to use the <a href="https://github.com/teivah/100-go-mistakes/discussions">Discussions</a> space on the GitHub repo.</p>
|
||||
<details class="warning" open="open">
|
||||
<summary>Warning</summary>
|
||||
<p>You're currently viewing a new version that I'm enriching with significantly more content. Yet, this version is still under development; please be gentle if you find an issue, and feel free to create a PR.</p>
|
||||
|
|
@ -2914,16 +2933,16 @@ promoted to <code>Foo</code>. Therefore, Baz becomes available from Foo.</p>
|
|||
<p>The functional options pattern provides a handy and API-friendly way to handle options. Although the builder pattern can be a valid option, it has some minor downsides (having to pass a config struct that can be empty or a less handy way to handle error management) that tend to make the functional options pattern the idiomatic way to deal with these kind of problems in Go.</p>
|
||||
<p><a href="https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/11-functional-options/">Source code <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></span></a></p>
|
||||
<h3 id="project-misorganization-project-structure-and-package-organization-12">Project misorganization (project structure and package organization) (#12)</h3>
|
||||
<details class="info" open="open">
|
||||
<summary>TL;DR</summary>
|
||||
<p>Following a layout such as <a href="https://github.com/golang-standards/project-layout">project-layout</a> can be a good way to start structuring Go projects, especially if you are looking for existing conventions to standardize a new project.</p>
|
||||
</details>
|
||||
<p>Regarding the overall organization, there are different schools of thought. For example, should we organize our application by context or by layer? It depends on our preferences. We may favor grouping code per context (such as the customer context, the contract context, etc.), or we may favor following hexagonal architecture principles and group per technical layer. If the decision we make fits our use case, it cannot be a wrong decision, as long as we remain consistent with it.</p>
|
||||
<p>Regarding packages, there are multiple best practices that we should follow. First, we should avoid premature packaging because it might cause us to overcomplicate a project. Sometimes, it’s better to use a simple organization and have our project evolve when we understand what it contains rather than forcing ourselves to make the perfect structure up front.
|
||||
Granularity is another essential thing to consider. We should avoid having dozens of nano packages containing only one or two files. If we do, it’s because we have probably missed some logical connections across these packages, making our project harder for readers to understand. Conversely, we should also avoid huge packages that dilute the meaning of a package name.</p>
|
||||
<p>Package naming should also be considered with care. As we all know (as developers), naming is hard. To help clients understand a Go project, we should name our packages after what they provide, not what they contain. Also, naming should be meaningful. Therefore, a package name should be short, concise, expressive, and, by convention, a single lowercase word.</p>
|
||||
<p>Regarding what to export, the rule is pretty straightforward. We should minimize what should be exported as much as possible to reduce the coupling between packages and keep unnecessary exported elements hidden. If we are unsure whether to export an element or not, we should default to not exporting it. Later, if we discover that we need to export it, we can adjust our code. Let’s also keep in mind some exceptions, such as making fields exported so that a struct can be unmarshaled with encoding/json.</p>
|
||||
<p>Organizing a project isn’t straightforward, but following these rules should help make it easier to maintain. However, remember that consistency is also vital to ease maintainability. Therefore, let’s make sure that we keep things as consistent as possible within a codebase.</p>
|
||||
<details class="note" open="open">
|
||||
<summary>Note</summary>
|
||||
<p>In 2023, the Go team has published an official guideline for organizing / structuring a Go project: <a href="https://go.dev/doc/modules/layout">go.dev/doc/modules/layout</a></p>
|
||||
</details>
|
||||
<h3 id="creating-utility-packages-13">Creating utility packages (#13)</h3>
|
||||
<details class="info" open="open">
|
||||
<summary>TL;DR</summary>
|
||||
|
|
@ -3851,27 +3870,106 @@ the use case. However, we should see the two options as complementary. </p>
|
|||
<p>In summary, let’s be mindful that a goroutine is a resource like any other that must eventually be closed to free memory or other resources. Starting a goroutine without knowing when to stop it is a design issue. Whenever a goroutine is started, we should have a clear plan about when it will stop. Last but not least, if a goroutine creates resources and its lifetime is bound to the lifetime of the application, it’s probably safer to wait for this goroutine to complete before exiting the application. This way, we can ensure that the resources can be freed.</p>
|
||||
<p><a href="https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/62-starting-goroutine/">Source code <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></span></a></p>
|
||||
<h3 id="not-being-careful-with-goroutines-and-loop-variables-63">Not being careful with goroutines and loop variables (#63)</h3>
|
||||
<details class="info" open="open">
|
||||
<summary>TL;DR</summary>
|
||||
<p>To avoid bugs with goroutines and loop variables, create local variables or call functions instead of closures.</p>
|
||||
<details class="warning" open="open">
|
||||
<summary>Warning</summary>
|
||||
<p>This mistake isn't relevant anymore from Go 1.22 (<a href="https://go.dev/blog/loopvar-preview">source</a>).</p>
|
||||
</details>
|
||||
<p><a href="https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/63-goroutines-loop-variables/main.go">Source code <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></span></a></p>
|
||||
<h3 id="expecting-a-deterministic-behavior-using-select-and-channels-64">Expecting a deterministic behavior using select and channels (#64)</h3>
|
||||
<details class="info" open="open">
|
||||
<summary>TL;DR</summary>
|
||||
<p>Understanding that <code>select</code> with multiple channels chooses the case randomly if multiple options are possible prevents making wrong assumptions that can lead to subtle concurrency bugs.</p>
|
||||
</details>
|
||||
<p>One common mistake made by Go developers while working with channels is to make wrong assumptions about how select behaves with multiple channels.</p>
|
||||
<p>For example, let's consider the following case (<code>disconnectCh</code> is a buffered channel):</p>
|
||||
<div class="language-go highlight"><pre><span></span><code><span id="__span-40-1"><a id="__codelineno-40-1" name="__codelineno-40-1" href="#__codelineno-40-1"></a><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
|
||||
</span><span id="__span-40-2"><a id="__codelineno-40-2" name="__codelineno-40-2" href="#__codelineno-40-2"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p"><</span><span class="w"> </span><span class="mi">10</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span>
|
||||
</span><span id="__span-40-3"><a id="__codelineno-40-3" name="__codelineno-40-3" href="#__codelineno-40-3"></a><span class="w"> </span><span class="nx">messageCh</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="nx">i</span>
|
||||
</span><span id="__span-40-4"><a id="__codelineno-40-4" name="__codelineno-40-4" href="#__codelineno-40-4"></a><span class="w"> </span><span class="p">}</span>
|
||||
</span><span id="__span-40-5"><a id="__codelineno-40-5" name="__codelineno-40-5" href="#__codelineno-40-5"></a><span class="w"> </span><span class="nx">disconnectCh</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="kd">struct</span><span class="p">{}{}</span>
|
||||
</span><span id="__span-40-6"><a id="__codelineno-40-6" name="__codelineno-40-6" href="#__codelineno-40-6"></a><span class="p">}()</span>
|
||||
</span><span id="__span-40-7"><a id="__codelineno-40-7" name="__codelineno-40-7" href="#__codelineno-40-7"></a>
|
||||
</span><span id="__span-40-8"><a id="__codelineno-40-8" name="__codelineno-40-8" href="#__codelineno-40-8"></a><span class="k">for</span><span class="w"> </span><span class="p">{</span>
|
||||
</span><span id="__span-40-9"><a id="__codelineno-40-9" name="__codelineno-40-9" href="#__codelineno-40-9"></a><span class="w"> </span><span class="k">select</span><span class="w"> </span><span class="p">{</span>
|
||||
</span><span id="__span-40-10"><a id="__codelineno-40-10" name="__codelineno-40-10" href="#__codelineno-40-10"></a><span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o"><-</span><span class="nx">messageCh</span><span class="p">:</span>
|
||||
</span><span id="__span-40-11"><a id="__codelineno-40-11" name="__codelineno-40-11" href="#__codelineno-40-11"></a><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">v</span><span class="p">)</span>
|
||||
</span><span id="__span-40-12"><a id="__codelineno-40-12" name="__codelineno-40-12" href="#__codelineno-40-12"></a><span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="o"><-</span><span class="nx">disconnectCh</span><span class="p">:</span>
|
||||
</span><span id="__span-40-13"><a id="__codelineno-40-13" name="__codelineno-40-13" href="#__codelineno-40-13"></a><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">"disconnection, return"</span><span class="p">)</span>
|
||||
</span><span id="__span-40-14"><a id="__codelineno-40-14" name="__codelineno-40-14" href="#__codelineno-40-14"></a><span class="w"> </span><span class="k">return</span>
|
||||
</span><span id="__span-40-15"><a id="__codelineno-40-15" name="__codelineno-40-15" href="#__codelineno-40-15"></a><span class="w"> </span><span class="p">}</span>
|
||||
</span><span id="__span-40-16"><a id="__codelineno-40-16" name="__codelineno-40-16" href="#__codelineno-40-16"></a><span class="p">}</span>
|
||||
</span></code></pre></div>
|
||||
<p>If we run this example multiple times, the result will be random:</p>
|
||||
<div class="language-text highlight"><pre><span></span><code><span id="__span-41-1"><a id="__codelineno-41-1" name="__codelineno-41-1" href="#__codelineno-41-1"></a>0
|
||||
</span><span id="__span-41-2"><a id="__codelineno-41-2" name="__codelineno-41-2" href="#__codelineno-41-2"></a>1
|
||||
</span><span id="__span-41-3"><a id="__codelineno-41-3" name="__codelineno-41-3" href="#__codelineno-41-3"></a>2
|
||||
</span><span id="__span-41-4"><a id="__codelineno-41-4" name="__codelineno-41-4" href="#__codelineno-41-4"></a>disconnection, return
|
||||
</span><span id="__span-41-5"><a id="__codelineno-41-5" name="__codelineno-41-5" href="#__codelineno-41-5"></a>
|
||||
</span><span id="__span-41-6"><a id="__codelineno-41-6" name="__codelineno-41-6" href="#__codelineno-41-6"></a>0
|
||||
</span><span id="__span-41-7"><a id="__codelineno-41-7" name="__codelineno-41-7" href="#__codelineno-41-7"></a>disconnection, return
|
||||
</span></code></pre></div>
|
||||
<p>Instead of consuming the 10 messages, we only received a few of them. What’s the reason? It lies in the specification of the select statement with multiple channels (https:// go.dev/ref/spec):</p>
|
||||
<div class="admonition quote">
|
||||
<p class="admonition-title">Quote</p>
|
||||
<p>If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection.</p>
|
||||
</div>
|
||||
<p>Unlike a switch statement, where the first case with a match wins, the select state- ment selects randomly if multiple options are possible.</p>
|
||||
<p>This behavior might look odd at first, but there’s a good reason for it: to prevent possible starvation. Suppose the first possible communication chosen is based on the source order. In that case, we may fall into a situation where, for example, we only receive from one channel because of a fast sender. To prevent this, the language designers decided to use a random selection.</p>
|
||||
<p>When using <code>select</code> with multiple channels, we must remember that if multiple options are possible, the first case in the source order does not automatically win. Instead, Go selects randomly, so there’s no guarantee about which option will be chosen. To overcome this behavior, in the case of a single producer goroutine, we can use either unbuffered channels or a single channel.</p>
|
||||
<p><a href="https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/64-select-behavior/main.go">Source code <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></span></a></p>
|
||||
<h3 id="not-using-notification-channels-65">Not using notification channels (#65)</h3>
|
||||
<details class="info" open="open">
|
||||
<summary>TL;DR</summary>
|
||||
<p>Send notifications using a <code>chan struct{}</code> type.</p>
|
||||
</details>
|
||||
<p>Channels are a mechanism for communicating across goroutines via signaling. A signal can be either with or without data.</p>
|
||||
<p>Let’s look at a concrete example. We will create a channel that will notify us whenever a certain disconnection occurs. One idea is to handle it as a <code>chan bool</code>:</p>
|
||||
<div class="language-go highlight"><pre><span></span><code><span id="__span-42-1"><a id="__codelineno-42-1" name="__codelineno-42-1" href="#__codelineno-42-1"></a><span class="nx">disconnectCh</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">bool</span><span class="p">)</span>
|
||||
</span></code></pre></div>
|
||||
<p>Now, let’s say we interact with an API that provides us with such a channel. Because it’s a channel of Booleans, we can receive either <code>true</code> or <code>false</code> messages. It’s probably clear what <code>true</code> conveys. But what does <code>false</code> mean? Does it mean we haven’t been disconnected? And in this case, how frequently will we receive such a signal? Does it mean we have reconnected? Should we even expect to receive <code>false</code>? Perhaps we should only expect to receive <code>true</code> messages.</p>
|
||||
<p>If that’s the case, meaning we don’t need a specific value to convey some information, we need a channel <em>without</em> data. The idiomatic way to handle it is a channel of empty structs: <code>chan struct{}</code>.</p>
|
||||
<h3 id="not-using-nil-channels-66">Not using nil channels (#66)</h3>
|
||||
<details class="info" open="open">
|
||||
<summary>TL;DR</summary>
|
||||
<p>Using nil channels should be part of your concurrency toolset because it allows you to <em>remove</em> cases from <code>select</code> statements, for example.</p>
|
||||
</details>
|
||||
<p>What should this code do?</p>
|
||||
<div class="language-go highlight"><pre><span></span><code><span id="__span-43-1"><a id="__codelineno-43-1" name="__codelineno-43-1" href="#__codelineno-43-1"></a><span class="kd">var</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span>
|
||||
</span><span id="__span-43-2"><a id="__codelineno-43-2" name="__codelineno-43-2" href="#__codelineno-43-2"></a><span class="o"><-</span><span class="nx">ch</span>
|
||||
</span></code></pre></div>
|
||||
<p><code>ch</code> is a <code>chan int</code> type. The zero value of a channel being nil, <code>ch</code> is <code>nil</code>. The goroutine won’t panic; however, it will block forever.</p>
|
||||
<p>The principle is the same if we send a message to a nil channel. This goroutine blocks forever:</p>
|
||||
<div class="language-go highlight"><pre><span></span><code><span id="__span-44-1"><a id="__codelineno-44-1" name="__codelineno-44-1" href="#__codelineno-44-1"></a><span class="kd">var</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span>
|
||||
</span><span id="__span-44-2"><a id="__codelineno-44-2" name="__codelineno-44-2" href="#__codelineno-44-2"></a><span class="nx">ch</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="mi">0</span>
|
||||
</span></code></pre></div>
|
||||
<p>Then what’s the purpose of Go allowing messages to be received from or sent to a nil channel? For example, we can use nil channels to implement an idiomatic way to merge two channels:</p>
|
||||
<div class="language-go highlight"><pre><span></span><code><span id="__span-45-1"><a id="__codelineno-45-1" name="__codelineno-45-1" href="#__codelineno-45-1"></a><span class="kd">func</span><span class="w"> </span><span class="nx">merge</span><span class="p">(</span><span class="nx">ch1</span><span class="p">,</span><span class="w"> </span><span class="nx">ch2</span><span class="w"> </span><span class="o"><-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="o"><-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">{</span>
|
||||
</span><span id="__span-45-2"><a id="__codelineno-45-2" name="__codelineno-45-2" href="#__codelineno-45-2"></a><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span>
|
||||
</span><span id="__span-45-3"><a id="__codelineno-45-3" name="__codelineno-45-3" href="#__codelineno-45-3"></a>
|
||||
</span><span id="__span-45-4"><a id="__codelineno-45-4" name="__codelineno-45-4" href="#__codelineno-45-4"></a><span class="w"> </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
|
||||
</span><span id="__span-45-5"><a id="__codelineno-45-5" name="__codelineno-45-5" href="#__codelineno-45-5"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="nx">ch1</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="nx">ch2</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="c1">// Continue if at least one channel isn’t nil</span>
|
||||
</span><span id="__span-45-6"><a id="__codelineno-45-6" name="__codelineno-45-6" href="#__codelineno-45-6"></a><span class="w"> </span><span class="k">select</span><span class="w"> </span><span class="p">{</span>
|
||||
</span><span id="__span-45-7"><a id="__codelineno-45-7" name="__codelineno-45-7" href="#__codelineno-45-7"></a><span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="nx">open</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o"><-</span><span class="nx">ch1</span><span class="p">:</span>
|
||||
</span><span id="__span-45-8"><a id="__codelineno-45-8" name="__codelineno-45-8" href="#__codelineno-45-8"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">open</span><span class="w"> </span><span class="p">{</span>
|
||||
</span><span id="__span-45-9"><a id="__codelineno-45-9" name="__codelineno-45-9" href="#__codelineno-45-9"></a><span class="w"> </span><span class="nx">ch1</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="c1">// Assign ch1 to a nil channel once closed</span>
|
||||
</span><span id="__span-45-10"><a id="__codelineno-45-10" name="__codelineno-45-10" href="#__codelineno-45-10"></a><span class="w"> </span><span class="k">break</span>
|
||||
</span><span id="__span-45-11"><a id="__codelineno-45-11" name="__codelineno-45-11" href="#__codelineno-45-11"></a><span class="w"> </span><span class="p">}</span>
|
||||
</span><span id="__span-45-12"><a id="__codelineno-45-12" name="__codelineno-45-12" href="#__codelineno-45-12"></a><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="nx">v</span>
|
||||
</span><span id="__span-45-13"><a id="__codelineno-45-13" name="__codelineno-45-13" href="#__codelineno-45-13"></a><span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="nx">open</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o"><-</span><span class="nx">ch2</span><span class="p">:</span>
|
||||
</span><span id="__span-45-14"><a id="__codelineno-45-14" name="__codelineno-45-14" href="#__codelineno-45-14"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">open</span><span class="w"> </span><span class="p">{</span>
|
||||
</span><span id="__span-45-15"><a id="__codelineno-45-15" name="__codelineno-45-15" href="#__codelineno-45-15"></a><span class="w"> </span><span class="nx">ch2</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="c1">// Assigns ch2 to a nil channel once closed</span>
|
||||
</span><span id="__span-45-16"><a id="__codelineno-45-16" name="__codelineno-45-16" href="#__codelineno-45-16"></a><span class="w"> </span><span class="k">break</span>
|
||||
</span><span id="__span-45-17"><a id="__codelineno-45-17" name="__codelineno-45-17" href="#__codelineno-45-17"></a><span class="w"> </span><span class="p">}</span>
|
||||
</span><span id="__span-45-18"><a id="__codelineno-45-18" name="__codelineno-45-18" href="#__codelineno-45-18"></a><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="nx">v</span>
|
||||
</span><span id="__span-45-19"><a id="__codelineno-45-19" name="__codelineno-45-19" href="#__codelineno-45-19"></a><span class="w"> </span><span class="p">}</span>
|
||||
</span><span id="__span-45-20"><a id="__codelineno-45-20" name="__codelineno-45-20" href="#__codelineno-45-20"></a><span class="w"> </span><span class="p">}</span>
|
||||
</span><span id="__span-45-21"><a id="__codelineno-45-21" name="__codelineno-45-21" href="#__codelineno-45-21"></a><span class="w"> </span><span class="nb">close</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span>
|
||||
</span><span id="__span-45-22"><a id="__codelineno-45-22" name="__codelineno-45-22" href="#__codelineno-45-22"></a><span class="w"> </span><span class="p">}()</span>
|
||||
</span><span id="__span-45-23"><a id="__codelineno-45-23" name="__codelineno-45-23" href="#__codelineno-45-23"></a>
|
||||
</span><span id="__span-45-24"><a id="__codelineno-45-24" name="__codelineno-45-24" href="#__codelineno-45-24"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">ch</span>
|
||||
</span><span id="__span-45-25"><a id="__codelineno-45-25" name="__codelineno-45-25" href="#__codelineno-45-25"></a><span class="p">}</span>
|
||||
</span></code></pre></div>
|
||||
<p>This elegant solution relies on nil channels to somehow <em>remove</em> one case from the <code>select</code> statement.</p>
|
||||
<p>Let’s keep this idea in mind: nil channels are useful in some conditions and should be part of the Go developer’s toolset when dealing with concurrent code.</p>
|
||||
<p><a href="https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/66-nil-channels/main.go">Source code <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></span></a></p>
|
||||
<h3 id="being-puzzled-about-channel-size-67">Being puzzled about channel size (#67)</h3>
|
||||
<details class="info" open="open">
|
||||
|
|
|
|||
|
|
@ -645,6 +645,26 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../56-concurrency-faster/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Thinking concurrency is always faster (#56)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -15,6 +15,11 @@
|
|||
<lastmod>2023-09-27</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://100go.co/56-concurrency-faster/</loc>
|
||||
<lastmod>2023-09-27</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://100go.co/89-benchmarks/</loc>
|
||||
<lastmod>2023-09-27</lastmod>
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,3 +1,8 @@
|
|||
.md-typeset figure img {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.md-typeset .admonition,
|
||||
.md-typeset details {
|
||||
font-size: 15px
|
||||
}
|
||||
|
|
|
|||
|
|
@ -647,6 +647,26 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../56-concurrency-faster/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Thinking concurrency is always faster (#56)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue