├── .gitignore ├── LICENSE ├── README.md ├── book.toml └── src ├── SUMMARY.md ├── built-in-constants.md ├── built-in-defer.md ├── built-in-make-and-new.md ├── built-in-panic.md ├── built-in-recover.md ├── built-in.md ├── bytes-buffers.md ├── bytes-compare.md ├── bytes-index.md ├── bytes-intro.md ├── context-details.md ├── context-intro.md ├── cover.md ├── errors-As-Is.md ├── errors-great-again.md ├── errors-intro.md ├── errors-unwrap.md ├── favicon.png ├── img └── cover.svg ├── io-intro.md ├── io-pipe-in-depth.md ├── io-reader-and-writer.md ├── notes-about-godocs.md ├── preface.md ├── sync-cond.md ├── sync-do.md ├── sync-intro.md ├── sync-mutex.md ├── sync-pool.md └── sync-waitgroup.md /.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spaceship Go 2 | 3 | ![](./controllers_brief.svg) 4 | 5 | 6 | To view the book, click [here](https://blasrodri.github.io/spaceship-go-gh-pages/) 7 | 8 | ## Notes from the author 9 | 10 | This is a set of notes I gathered on my way to understanding how things worked 11 | in Go. The Standard Library is one of the best places to find answers. Especially 12 | for Go, since it comes with _batteries included_. And its source code is 13 | wonderfully documented. 14 | 15 | There is official documentation, which I made extensive usage of. And some 16 | blog posts from the Go team that helped me build my understanding. But no 17 | matter how good these resources are, it can be difficult for a non-expert 18 | developer to try to find some answers there. And this is what this book is 19 | about: it serves as a bridge between these learners, and some amazing 20 | resources that perhaps look a bit intimidating. 21 | 22 | I expect that there will be errors and things that could be better explained. For 23 | that is that I have made this repository public. I intend to use it as an 24 | exchange of ideas and suggestions. PRs and issues are more than welcome. And 25 | they will be included in the book as soon as they're merged to master. 26 | 27 | Writing is challenging. But I found it enormously helpful, as an exercise towards 28 | building a deeper understanding of how things work. If one can explain something, 29 | it means that one understands it. And this is the purpose of these notes. I hope 30 | I've learned, and I also hope you too! 31 | 32 | ## Building 33 | 34 | To build the book: 35 | 36 | 1. [Install `mdbook`](https://rust-lang.github.io/mdBook/guide/installation.html) 37 | 2. Clone the repository (`git clone https://github.com/blasrodri/spaceship-go.git`) 38 | 3. Run `mdbook` 39 | 40 | To optionally build the epub file format, install `mdbook-epub`: 41 | 42 | ``` 43 | cargo install mdbook-epub --version 0.4.14-alpha.0 44 | # Then run mdbook 45 | mdbook 46 | ``` 47 | 48 | Output will be saved as `book/Spaceship Go.epub`. 49 | -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Blas Rodriguez Irizar"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Spaceship Go" 7 | 8 | [output.epub] 9 | cover-image = "img/cover.svg" 10 | additional-resources = ["img/cover.svg"] 11 | optional = true 12 | -------------------------------------------------------------------------------- /src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Cover](./cover.md) 4 | - [Preface](./preface.md) 5 | - [Notes About Go Docs](./notes-about-godocs.md) 6 | - [Built in](./built-in.md) 7 | - [Constants](./built-in-constants.md) 8 | - [make and new](./built-in-make-and-new.md) 9 | - [defer](./built-in-defer.md) 10 | - [panic](./built-in-panic.md) 11 | - [recover](./built-in-recover.md) 12 | - [Bytes](./bytes-intro.md) 13 | - [Compare](./bytes-compare.md) 14 | - [Index](./bytes-index.md) 15 | - [Buffers](./bytes-buffers.md) 16 | - [Context](./context-intro.md) 17 | - [In detail](./context-details.md) 18 | - [Errors](./errors-intro.md) 19 | - [As and Is](./errors-As-Is.md) 20 | - [Unwrap](./errors-unwrap.md) 21 | - [Making error great again](./errors-great-again.md) 22 | - [Sync](./sync-intro.md) 23 | - [Cond](./sync-cond.md) 24 | - [Mutex](./sync-mutex.md) 25 | - [Do](./sync-do.md) 26 | - [Pool](./sync-pool.md) 27 | - [WaitGroup](./sync-waitgroup.md) 28 | - [IO](./io-intro.md) 29 | - [Reader and Writer](./io-reader-and-writer.md) 30 | - [Pipe in depth](./io-pipe-in-depth.md) 31 | -------------------------------------------------------------------------------- /src/built-in-constants.md: -------------------------------------------------------------------------------- 1 | ## Constants 2 | 3 | Booleans true and false are defined here. And in a very smart and concise way: 4 | ```go 5 | const ( 6 | true = 0 == 0 // this is always true! 7 | false = 0 != 0 // this is always false! 8 | ) 9 | ``` 10 | Iota (view https://github.com/golang/go/wiki/Iota and https://golang.org/ref/spec#Iota) 11 | represents successive untyped integer constants. 12 | ```go 13 | const iota = 0 // Untyped int. 14 | ``` 15 | These two examples, describe how iota can be useful: 16 | ```go 17 | const ( 18 | c0 = iota // c0 == 0 19 | c1 = iota // c1 == 1 20 | c2 = iota // c2 == 2 21 | ) 22 | 23 | const ( 24 | a = 1 << iota // a == 1 (iota == 0) 25 | b = 1 << iota // b == 2 (iota == 1) 26 | c = 3 // c == 3 (iota == 2, unused) 27 | d = 1 << iota // d == 8 (iota == 3) 28 | ) 29 | ``` 30 | Note: The `<<` is a left shift operand, which shift lefts the operand by an integer 31 | (in this case `iota` value). So, for instance `1 << 2` equals `1 * 2 * 2`. 32 | 33 | `nil` is a widely used variable. It is defined as: 34 | ```go 35 | var nil Type 36 | ``` 37 | It represents the zero value of a type, within these classes: slices, pointer, 38 | map, func, interface, or channel. 39 | -------------------------------------------------------------------------------- /src/built-in-defer.md: -------------------------------------------------------------------------------- 1 | ## defer 2 | 3 | This function is not defined in the built-in package. But since it is used in 4 | the example for `recover` I wanted to mention it. 5 | 6 | `defer` is used to ensure that a function will be executed right before the 7 | function that calls it terminates. As [Go By example](https://gobyexample.com/defer) 8 | mentions, other languages provide the construct _finally_ in some contexts. 9 | 10 | The syntax is simple: 11 | ```go 12 | defer func() { // ... 13 | }() 14 | ``` 15 | 16 | Or directly 17 | 18 | ```go 19 | defer closeFile(f) 20 | ``` 21 | 22 | `defer` takes a closure as argument. Which means that it is possible to include a wide 23 | range of elements inside it. 24 | 25 | As noted in the [recover](./built-in-recover.md) section, if we call `defer` multiple times, the order in which 26 | these functions will be executed is **last in, first out**. So, for instance: 27 | 28 | ```go 29 | import ( 30 | "fmt" 31 | ) 32 | func main() { 33 | fmt.Println("Let's count backwards:") 34 | defer func() { 35 | fmt.Println(1) 36 | }() 37 | defer func() { 38 | fmt.Println(2) 39 | }() 40 | defer func() { 41 | fmt.Println(3) 42 | }() 43 | } 44 | ``` 45 | This will output 46 | 47 | ``` 48 | Let's count backwards: 49 | 3 50 | 2 51 | 1 52 | ``` 53 | -------------------------------------------------------------------------------- /src/built-in-make-and-new.md: -------------------------------------------------------------------------------- 1 | # make and new 2 | 3 | ## make 4 | 5 | `make` is a very singular function. Its purpose is to allocate and initialize 6 | an object of type slice, map, or chan (only). The output depends on the type 7 | it's operating upon. You can see the differences, depending upon the type in the 8 | excerpt below: 9 | 10 | ```go 11 | // from src/go/cmd/compile/internal/gc/builtin/runtime.go 12 | func makemap64(mapType *byte, hint int64, mapbuf *any) (hmap map[any]any) 13 | func makemap(mapType *byte, hint int, mapbuf *any) (hmap map[any]any) 14 | func makemap_small() (hmap map[any]any) 15 | func makechan64(chanType *byte, size int64) (hchan chan any) 16 | func makechan(chanType *byte, size int) (hchan chan any) 17 | func makeslice(typ *byte, len int, cap int) unsafe.Pointer 18 | func makeslice64(typ *byte, len int64, cap int64) unsafe.Pointer 19 | func makeslicecopy(typ *byte, tolen int, fromlen int, from unsafe.Pointer) unsafe.Pointer 20 | ``` 21 | 22 | ## new 23 | 24 | It can be thought as a `make` alternative, but with a size equal to zero and 25 | always initializing the value to its zero value. 26 | -------------------------------------------------------------------------------- /src/built-in-panic.md: -------------------------------------------------------------------------------- 1 | ## panic 2 | 3 | It's a function that stops the normal execution of the goroutine. All the 4 | deferred functions are still being called, and once they complete this goroutine 5 | returns to the caller. To the caller, the panic on the callee triggers a 6 | termination on itself. This is a recursive mechanism that can eventually terminate 7 | in the program exiting with a non-zero code. However, it's possible to control it 8 | using recover. `panic` has different variants, and some are implemented in assembly, 9 | while some other purely in Go. 10 | 11 | ```go 12 | // Some are implemented in go 13 | 14 | func panicshift() 15 | func panicdivide() 16 | ``` 17 | 18 | ```go 19 | // Some others are implemented in assembly 20 | 21 | func panicIndex(x, y int) 22 | func panicIndexU(x uint, y int) 23 | ``` 24 | -------------------------------------------------------------------------------- /src/built-in-recover.md: -------------------------------------------------------------------------------- 1 | ## recover 2 | 3 | Recover is a built-in function allows a program to manage behavior of a panicking 4 | goroutine. It is useful only **inside a deferred function**. It stops the panicking 5 | sequence by restoring normal execution and retrieves the error value passed to the 6 | call of panic. If recover is called outside the deferred function it will not stop 7 | a panicking sequence. In this case, or when the goroutine is not panicking, or if 8 | the argument supplied to panic was nil, recover returns nil. Thus the return value 9 | from recover reports whether the goroutine is panicking. 10 | 11 | An example (https://blog.golang.org/defer-panic-and-recover) of the mechanisms of 12 | panic and defer: 13 | 14 | ```go 15 | package main 16 | 17 | import "fmt" 18 | 19 | func main() { 20 | f() 21 | fmt.Println("Returned normally from f.") 22 | } 23 | 24 | func f() { 25 | defer func() { 26 | if r := recover(); r != nil { 27 | fmt.Println("Recovered in f", r) 28 | } 29 | }() 30 | fmt.Println("Calling g.") 31 | g(0) 32 | fmt.Println("Returned normally from g.") 33 | } 34 | 35 | func g(i int) { 36 | if i > 3 { 37 | fmt.Println("Panicking!") 38 | panic(fmt.Sprintf("%v", i)) 39 | } 40 | defer fmt.Println("Defer in g", i) 41 | fmt.Println("Printing in g", i) 42 | g(i + 1) 43 | } 44 | 45 | // outputs 46 | // Calling g. 47 | // Printing in g 0 48 | // Printing in g 1 49 | // Printing in g 2 50 | // Printing in g 3 51 | // Panicking! 52 | // Defer in g 3 53 | // Defer in g 2 54 | // Defer in g 1 55 | // Defer in g 0 56 | // Recovered in f 4 57 | // Returned normally from f. 58 | ``` 59 | 60 | Note that the call to referred functions goes in the opposite direction. 61 | This is because the defer statement places the goroutine in the goroutine stack. 62 | And thus, they get popped in LIFO order. 63 | 64 | The same example, but without calling `recover` inside a defer is presented below: 65 | 66 | ```go 67 | package main 68 | 69 | import "fmt" 70 | 71 | func main() { 72 | f() 73 | fmt.Println("Returned normally from f.") 74 | } 75 | 76 | func f() { 77 | // defer func() { 78 | // if r := recover(); r != nil { 79 | // fmt.Println("Recovered in f", r) 80 | // } 81 | // }() 82 | fmt.Println("Calling g.") 83 | g(0) 84 | fmt.Println("Returned normally from g.") 85 | } 86 | 87 | func g(i int) { 88 | if i > 3 { 89 | fmt.Println("Panicking!") 90 | panic(fmt.Sprintf("%v", i)) 91 | } 92 | defer fmt.Println("Defer in g", i) 93 | fmt.Println("Printing in g", i) 94 | g(i + 1) 95 | } 96 | 97 | // outputs 98 | // Calling g. 99 | // Printing in g 0 100 | // Printing in g 1 101 | // Printing in g 2 102 | // Printing in g 3 103 | // Panicking! 104 | // Defer in g 3 105 | // Defer in g 2 106 | // Defer in g 1 107 | // Defer in g 0 108 | 109 | // AND NOW, this is different! 110 | 111 | // panic: 4 112 | // panic PC=0x2a9cd8 113 | // [stack trace omitted] 114 | ``` 115 | -------------------------------------------------------------------------------- /src/built-in.md: -------------------------------------------------------------------------------- 1 | # Built in 2 | 3 | >Package builtin provides documentation for Go's predeclared identifiers. 4 | >The items documented here are not actually in package builtin (...) 5 | > 6 | > *Built in package overview* 7 | 8 | Even though none of the items are defined in the built in package, it serves as a 9 | compilation of highly used items that every Go developer needs to be aware of. 10 | The goal of this chapter is to cover some of these elements, with special focus 11 | on how they are implemented. 12 | -------------------------------------------------------------------------------- /src/bytes-buffers.md: -------------------------------------------------------------------------------- 1 | ## Working with buffers 2 | 3 | Sometimes our program needs to manipulate bytes. And we might or might not know 4 | what's the size of this stream of bytes coming. Or perhaps we need to work with 5 | chunks... who knows! 6 | 7 | Buffers can help you, when you find yourself in these waters. But wait. What's a buffer? 8 | Think of it as a variable sized array of bytes. It's a data structure that can be 9 | resized to accommodate more or less bytes. It lets you write (`WriteByte`) new bytes to it, 10 | read bytes from it (`ReadByte`), remove some bytes (`Truncate`), remove all (`Reset`), etc. 11 | 12 | To create a Buffer, we need to call either `NewBuffer` or `NewBufferString`. 13 | The difference is in the input that they take. The former accepts a byte slice `[]byte` 14 | while the latter expects a string. 15 | 16 | For testing purposes let's say we create a buffer with some bytes: 17 | 18 | ```go 19 | // we call 20 | b := bytes.NewBuffer([]byte("some bytes")) 21 | ``` 22 | 23 | To get the slice back, the `Bytes` method that can be used: 24 | 25 | ```go 26 | b.Bytes() // [115 111 109 101 32 98 121 116 101 115] 27 | ``` 28 | 29 | We can read a fixed amount of bytes by using `Next(n int)`. It's important to note 30 | that calling this method will actually **read the bytes**. And thus, the buffer 31 | will have a new length of the previous one minus `n`. In case the buffer's length 32 | was less than n, then the method will return the entire buffer. 33 | 34 | Alternatively, buffers can grow, be truncated and reset. For that, we have: 35 | 36 | ```go 37 | func (b *Buffer) Grow(n int) // increases the buffer's capacity 38 | func (b *Buffer) Truncate(n int) 39 | func (*Buffer) Reset() // same as Truncate(0) 40 | ``` 41 | 42 | `Truncate(n int)` will discard all the bytes from the buffer, except for the first n ones. 43 | It does not shrink the capacity of the buffer. So this means that the buffer can still 44 | accommodate the same amount of elements without having to perform a memory allocation. 45 | If n < 0 or n > b.Len() then it panics. 46 | 47 | If you want to shrink a buffer, Go does not offer a built in way of doing it. 48 | An alternative of achieving it would be: 49 | 50 | ```go 51 | // https://stackoverflow.com/questions/16748330/does-go-have-no-real-way-to-shrink-a-slice-is-that-an-issue 52 | b = append([]T(nil), b[:newSize]...) 53 | ``` 54 | 55 | It is possible to work with the `Reader` interface. But it won't be covered in this chapter. 56 | -------------------------------------------------------------------------------- /src/bytes-compare.md: -------------------------------------------------------------------------------- 1 | ## Compare 2 | 3 | First, we can compare two bytes. That is, having a function that tells us whether 4 | these two bytes are the same or not. 5 | 6 | ```go 7 | func Compare(a, b []byte) int 8 | ``` 9 | The function Compare does this for us. It checks whether two **byte slices** are 10 | the same, and returns the result as an integer. The function performs a 11 | [lexicographic comparison](https://en.wikipedia.org/wiki/Lexicographical_order) 12 | and returns 0 if they are the same, -1 if a < b and +1 if a > b. 13 | 14 | It's interesting to note how this function is implemented. Diving into Go's source code 15 | we can see the following: 16 | 17 | ```go 18 | func Compare(a, b []byte) int { 19 | 20 | return bytealg.Compare(a, b) 21 | 22 | } 23 | ``` 24 | Something as _simple_ as just comparing bytes, involves going down to assembly language. 25 | The function Compare calls the function Compare in the package `bytealg`, which defines 26 | `Compare` like this: 27 | 28 | ```go 29 | //go:noescape 30 | 31 | func Compare(a, b []byte) int 32 | ``` 33 | 34 | The directive `//go:noespace` tells the compiler that the values passed to the function 35 | must be stored in the stack. And additionally that the function implementation is not written 36 | in Go. This means that the byte comparison function is implemented in a lower language, like 37 | assembly. 38 | 39 | Check [Compiler directives](https://golang.org/cmd/compile/#hdr-Compiler_Directives) if you 40 | want to get more details in this topic. 41 | -------------------------------------------------------------------------------- /src/bytes-index.md: -------------------------------------------------------------------------------- 1 | # Index 2 | 3 | Let's say we have some bytes, represented as a slice of byte, which in Go lingo means 4 | `[]byte`. If we want to verify whether these bytes contain a certain subset of bytes, 5 | and we're particularly interested in knowing where in the slice they are located 6 | we can use the function `Index`, provided in the package **bytes**. 7 | 8 | ```go 9 | func Index(s, sep []byte) int 10 | ``` 11 | 12 | The way it is implemented is as follows: 13 | 14 | - If the length of the separator is 0, then it returns 0. Nothing more to do 15 | - If it is 1, it calls another function called IndexByte to do the work. 16 | This is also an assembly function. One of its variants can be found [here](https://golang.org/src/internal/bytealg/indexbyte_amd64.s) 17 | - If the sizes between sep and s match, then it checks whether they are equal or not. For that, it calls Equal, which we have already mentioned. 18 | - If the separator length is bigger than one, then it's no longer a byte. It then goes to a more complex logic involving `bytealg.Index`. 19 | 20 | Besides the full implementation, sometimes it's useful to follow how things work under the hood. 21 | And in such a low level package, we can find the need of working with assembly and targeting 22 | the different architectures. Check [bytealg package](https://golang.org/src/internal/bytealg/) for an overview. 23 | -------------------------------------------------------------------------------- /src/bytes-intro.md: -------------------------------------------------------------------------------- 1 | # Bytes 2 | 3 | Bytes are program's building blocks. 4 | 5 | In Go, a byte is a type alias of `uint8`, and thus it is defined as: 6 | 7 | ```go 8 | type byte = uint8 9 | ``` 10 | 11 | And thus, when talking about **bytes**, we mean a slice of `byte`, namely: `[]byte`. 12 | 13 | ## Why and when use bytes? 14 | 15 | Bytes are pervasive, especially when working in systems programming. If you want 16 | to work on an application related to networking, operating systems, databases, 17 | you will likely end up working with bytes. And that is why it's very important 18 | to understand what tools are available off the shelf. 19 | 20 | In the following sections, we will address some operations Go allows us to 21 | perform on bytes. 22 | -------------------------------------------------------------------------------- /src/context-details.md: -------------------------------------------------------------------------------- 1 | # Context details 2 | 3 | The `Context` carries a deadline, a cancellation signal, `Done` and other values 4 | across processes and APIs. It is defined as an interface: 5 | 6 | ```go 7 | type Context interface { 8 | Deadline() (deadline time.Time, ok bool) 9 | Done() <-chan struct{} 10 | Err() error 11 | Value(key interface{}) interface{} 12 | } 13 | ``` 14 | 15 | Note: these methods may be called by multiple goroutines simultaneously. 16 | 17 | The package provides different types of contexts, with different _service levels_: 18 | 19 | - emptyCtx: it's one that is never cancelled, has no values and no deadline 20 | - cancelCtx: is one that can be cancelled. When cancelled, it also cancels all 21 | its children that implement cancel. 22 | - valueCtx: carries a key-value pair. Delegates all other calls (such as cancel) to its 23 | embedded context. 24 | - timerCtx: carries a timer and a deadline. It embeds `cancelCtx` to implement `Done` and `Err`. 25 | `cancel` is implementing by stopping its timer and delegating the call to `cancelCtx` 26 | 27 | It's important to note that as a user of the package, we only have access to these types through 28 | 29 | ```go 30 | func WithCancel(parent Context) (ctx Context, cancel CancelFunc) 31 | func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) 32 | func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) 33 | ``` 34 | 35 | ## CancelFunc 36 | 37 | ```go 38 | type CancelFunc func() 39 | ``` 40 | 41 | When creating a context, there are some factory functions. And all of them return a `CancelFunc` type. 42 | This is, as its name indicates, a function. It tells an operation to abandon its work. It's async, and 43 | it can be called by multiple goroutines simultaneously. After the first call, the subsequent calls are 44 | no-ops. 45 | 46 | As an example, for the `cancelCtx`, `cancel` method exists. It closes the `done` channel and cancels 47 | all of its children, plus some other cleaning tasks. 48 | 49 | ## Examples 50 | 51 | This is an extension of the original example provided in the std library. 52 | It depicts the usage of context within several goroutines that form a tree. 53 | 54 | Once the main goroutine triggers the `cancel` function, all the others 55 | are also cancelled. 56 | 57 | ```go 58 | package main 59 | 60 | import ( 61 | "context" 62 | "fmt" 63 | "time" 64 | ) 65 | 66 | func main() { 67 | // gen generates integers in a separate goroutine and 68 | // sends them to the returned channel. 69 | // The callers of gen need to cancel the context once 70 | // they are done consuming generated integers not to leak 71 | // the internal goroutine started by gen. 72 | gen := func(ctx context.Context) <-chan int { 73 | ctx2, cancel := context.WithCancel(ctx) 74 | defer cancel() 75 | // propagating the cancel context to a new goroutine 76 | go doStuff(ctx2) 77 | dst := make(chan int) 78 | n := 1 79 | go func() { 80 | for { 81 | select { 82 | case <-ctx.Done(): 83 | fmt.Println("Closing gen") 84 | fmt.Println(n) 85 | return // returning not to leak the goroutine 86 | case dst <- n: 87 | n++ 88 | } 89 | } 90 | }() 91 | return dst 92 | } 93 | 94 | ctx, cancel := context.WithCancel(context.Background()) 95 | // defer cancel() // cancel when we are finished consuming integers 96 | for n := range gen(ctx) { 97 | fmt.Println(n) 98 | if n == 5 { 99 | break 100 | } 101 | } 102 | cancel() 103 | for { 104 | time.Sleep(3 * time.Second) 105 | break 106 | } 107 | } 108 | func doStuff(ctx context.Context) { 109 | fmt.Println("Starting child") 110 | for { 111 | select { 112 | case <-ctx.Done(): 113 | fmt.Println("Closing child") 114 | return 115 | } 116 | } 117 | } 118 | 119 | ``` 120 | 121 | A plausible output (it's non-deterministic): 122 | 123 | ``` 124 | 1 125 | 2 126 | 3 127 | 4 128 | 5 129 | Closing gen 130 | 6 131 | Starting child 132 | Closing child 133 | ``` 134 | 135 | ### Using WithTimeout 136 | 137 | This example is rather simple. But it shows how to propagate a timeout into a tree. 138 | Here we show how the main goroutine receives a `Done` signal after 50 milliseconds. This 139 | triggers (through `cancel`) subsequent `Done` signals to all the goroutines that 140 | where created with the `WithTimeout` method. 141 | 142 | ```go 143 | package main 144 | 145 | import ( 146 | "context" 147 | "fmt" 148 | "time" 149 | ) 150 | 151 | func main() { 152 | // Pass a context with a timeout to tell a blocking function that it 153 | // should abandon its work after the timeout elapses. 154 | ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) 155 | ctx2, cancel2 := context.WithTimeout(context.Background(), 50*time.Millisecond) 156 | 157 | func(ctx2 context.Context) { 158 | select { 159 | case <-ctx2.Done(): 160 | fmt.Println(ctx2.Err()) // prints "context deadline exceeded" 161 | return 162 | case <-time.After(100 * time.Millisecond): 163 | fmt.Println("overslept 2") 164 | } 165 | 166 | }(ctx2) 167 | 168 | defer cancel() 169 | defer cancel2() 170 | 171 | select { 172 | case <-time.After(100 * time.Millisecond): 173 | fmt.Println("overslept") 174 | case <-ctx.Done(): 175 | fmt.Println(ctx.Err()) // prints "context deadline exceeded" 176 | } 177 | 178 | } 179 | ``` 180 | 181 | ## Context being used in the real world 182 | 183 | These are some libraries making heavy use of this pattern: 184 | 185 | 1. [kubernetes](https://github.com/kubernetes/kubernetes) 186 | 2. [gRPC](https://github.com/grpc/grpc-go) 187 | 3. [dgraph](https://github.com/dgraph-io/dgraph) 188 | -------------------------------------------------------------------------------- /src/context-intro.md: -------------------------------------------------------------------------------- 1 | # Context 2 | 3 | Package context defines the Context type. A context can include deadlines, 4 | cancellation signals, and other request-scoped values across API boundaries 5 | and between processes. A key feature of this pattern is that it allows signals 6 | to be propagated. So for instance, when a context is cancelled then all the 7 | contexts derived from it are cancelled as well. 8 | 9 | Context is a very singular pattern, that I have not seen implemented in the 10 | standard libraries of other languages. And since it's widely adopted in different 11 | projects, I thought it was worth covering it. 12 | 13 | Go proposes an interface to work with `Context`, which is 14 | 15 | > Programs that use Contexts should follow these rules to keep interfaces 16 | > consistent across packages and enable static analysis tools to check context propagation: 17 | > 18 | > Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. 19 | > The Context should be the first parameter, typically named ctx: 20 | 21 | ```go 22 | func DoSomething(ctx context.Context, arg Arg) error { 23 | // ... use ctx ... 24 | } 25 | ``` 26 | 27 | Next, we describe some different functionalities offered by the package 28 | and show alternatives of how it can be used. Much of it has been taken from 29 | [this blog post](https://blog.golang.org/context) by Sameer Ajmani. 30 | -------------------------------------------------------------------------------- /src/cover.md: -------------------------------------------------------------------------------- 1 | ![Cover Image](./img/cover.svg) 2 | -------------------------------------------------------------------------------- /src/errors-As-Is.md: -------------------------------------------------------------------------------- 1 | ## As 2 | 3 | `As` finds the **first error in err's chain that matches target**. If it 4 | succeeds, it sets target to that error value and returns true. Otherwise, 5 | it returns false. 6 | 7 | `As` panics if target is not a non-nil pointer to either a type that implements 8 | error, or to any interface type. 9 | 10 | ```go 11 | var e *QueryError 12 | if errors.As(err, &e) { 13 | // err is a *QueryError, and e is set to the error's value 14 | } 15 | ``` 16 | 17 | ## Is 18 | 19 | `Is` reports whether **any error** (as opposed to finding the first with `As`) 20 | in err's chain matches target. 21 | 22 | The chain consists of err itself followed by the **sequence of errors obtained** 23 | **by repeatedly calling Unwrap**. 24 | 25 | ```go 26 | if errors.Is(err, ErrPermission) { 27 | // ... 28 | } 29 | ``` 30 | 31 | This equivalent to the following: 32 | 33 | ```go 34 | if e, ok := err.(*QueryError); ok && e.Err == ErrPermission { 35 | // ... 36 | } 37 | ``` 38 | 39 | ## Remarks 40 | 41 | One of the biggest advantages of having these two methods, is when working with 42 | highly nested errors. They bring all the necessary machinery to unwrap as much 43 | as needed until a match is found. Or if there is no match, we are sure that all 44 | the inner errors were also compared. 45 | -------------------------------------------------------------------------------- /src/errors-great-again.md: -------------------------------------------------------------------------------- 1 | ## Making error great again 2 | 3 | The following is a canonical example of how horrible error handling 4 | can become in Go: 5 | 6 | ```go 7 | _, err = fd.Write(p0[a:b]) 8 | if err != nil { 9 | return err 10 | } 11 | _, err = fd.Write(p1[c:d]) 12 | if err != nil { 13 | return err 14 | } 15 | _, err = fd.Write(p2[e:f]) 16 | if err != nil { 17 | return err 18 | } 19 | // and so on 20 | ``` 21 | 22 | An improvement, using a helper function: 23 | 24 | ```go 25 | var err error 26 | write := func(buf []byte) { 27 | if err != nil { 28 | return 29 | } 30 | _, err = w.Write(buf) 31 | } 32 | write(p0[a:b]) 33 | write(p1[c:d]) 34 | write(p2[e:f]) 35 | // and so on 36 | if err != nil { 37 | return err 38 | } 39 | ``` 40 | 41 | However, here we need to be sure that `err` is being captured by `write` 42 | every time we call this function. We could actually do better, by creating a 43 | new type, and adding a method for it that does what `write` is doing here. Then, 44 | we will not need to have any closure, nor ensure that the value is being closed 45 | by the function. 46 | 47 | This is even a much better way of working with errors. And in some ways, it 48 | brings to the table some of Haskell's/FP approach to error handling: 49 | 50 | ```go 51 | type errWriter struct { 52 | w io.Writer 53 | err error 54 | } 55 | 56 | func (ew *errWriter) write(buf []byte) { 57 | if ew.err != nil { 58 | return 59 | } 60 | _, ew.err = ew.w.Write(buf) 61 | } 62 | ``` 63 | 64 | ```go 65 | ew := &errWriter{w: fd} 66 | ew.write(p0[a:b]) 67 | ew.write(p1[c:d]) 68 | ew.write(p2[e:f]) 69 | // and so on 70 | if ew.err != nil { 71 | return ew.err 72 | } 73 | ``` 74 | 75 | Here, `write` checks whether `errWriter` have failed before. If it 76 | did, then it won't do anything again; we are an _abnormal state_ already. 77 | Otherwise, it's free to do some work. 78 | 79 | ### References: 80 | 81 | - https://blog.golang.org/errors-are-values 82 | - http://jxck.hatenablog.com/entry/golang-error-handling-lesson-by-rob-pike 83 | -------------------------------------------------------------------------------- /src/errors-intro.md: -------------------------------------------------------------------------------- 1 | ## Errors 2 | 3 | Error handling is a very important topic in software engineering. There are 4 | plenty different schools of thought. Go's approach to it is very simple. 5 | It treats [**errors as values**](https://blog.golang.org/errors-are-values), 6 | and to some people is insufficient. But we won't get into this discussion here. 7 | 8 | The `error` type is used to indicate an _abnormal state_. When this happens, 9 | there are several ways to act. In this chapter, we will only focus on the 10 | availability of tools that Go provides the developer to characterize and respond 11 | to these _anomalies_. 12 | 13 | `error` is defined in the `built-in` package as: 14 | 15 | ```go 16 | type error interface { 17 | Error() string 18 | } 19 | ``` 20 | 21 | However, this package provides nothing else than this interface. Most of the 22 | surrounding infrastructure is provided within the `errors` package instead. 23 | -------------------------------------------------------------------------------- /src/errors-unwrap.md: -------------------------------------------------------------------------------- 1 | ## Unwrap 2 | 3 | _(feature added in Go 1.13)_ 4 | 5 | This is an interesting feature, when building errors that 6 | **contain other errors**. 7 | 8 | `Unwrap` is actually defined as follows: 9 | 10 | ```go 11 | func Unwrap(err error) error { 12 | 13 | u, ok := err.(interface { 14 | Unwrap() error 15 | }) 16 | if !ok { 17 | return nil 18 | } 19 | return u.Unwrap() 20 | 21 | } 22 | ``` 23 | 24 | It first checks whether `err` has itself the `Unwrap` method. If it doesn't, then 25 | nothing else can be done. There is nothing to unwrap. Nada. But if the method 26 | is defined, it calls it and returns its value, which is an `error` itself. 27 | 28 | This is a very useful way of composing errors, and thus adding much more 29 | information. We could think of the following scenario: 30 | 31 | ```go 32 | package main 33 | 34 | import ( 35 | "fmt" 36 | "log" 37 | "errors" 38 | ) 39 | type databaseError struct { 40 | err error 41 | } 42 | 43 | type queryMissingParamsError struct { 44 | params string 45 | } 46 | 47 | func (de *databaseError) Unwrap() error { 48 | return de.err 49 | } 50 | 51 | func (de *databaseError) Error() string { 52 | return fmt.Sprintf("Database error. %s", de.err) 53 | } 54 | 55 | func (qmp *queryMissingParamsError) Error() string { 56 | return fmt.Sprintf("Some parameter were missing: %s", qmp.params) 57 | } 58 | 59 | // bogus query 60 | type Query struct {} 61 | 62 | func (q *Query) Check() error { 63 | return &databaseError { 64 | err: &queryMissingParamsError { 65 | params: "name", 66 | }, 67 | } 68 | } 69 | 70 | func handleDatabaseQuery(query *Query) error { 71 | err := query.Check() 72 | if err == nil { 73 | return nil 74 | } 75 | log.Print(err) 76 | return errors.Unwrap(err) 77 | } 78 | func main() { 79 | q := &Query{} 80 | err := handleDatabaseQuery(q) 81 | fmt.Println(err) 82 | } 83 | 84 | ``` 85 | 86 | In this example, we can see how an error can pack and unpack other errors. And 87 | we can choose to define a strategy for the different scenarios. So, in this 88 | particular example, we could ask the user to make the query again and remind 89 | her not to forget to include _name_ as a parameter. 90 | -------------------------------------------------------------------------------- /src/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blasrodri/spaceship-go/089000aae4f32d5b2aad1d025aef20442cdb894b/src/favicon.png -------------------------------------------------------------------------------- /src/img/cover.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/io-intro.md: -------------------------------------------------------------------------------- 1 | ## IO 2 | 3 | > Package io provides basic interfaces to I/O primitives. 4 | > Its primary job is to wrap existing implementations of such primitives, 5 | > such as those in package os, into shared public interfaces that abstract 6 | > the functionality, plus some other related primitives. 7 | 8 | `io` is a very important package in Go. It provides a set of primitives 9 | and interfaces that every developer needs to understand in order to work 10 | properly with bytes, standard input, output, error and pipes among others. 11 | 12 | An important note is that these primitives are not thread safe by default. 13 | This means that they shouldn't be used by multiple goroutines at the same time. 14 | -------------------------------------------------------------------------------- /src/io-pipe-in-depth.md: -------------------------------------------------------------------------------- 1 | # Pipe 2 | 3 | In Go a pipe is **synchronous in-memory pipe**. `pipe` is a type that 4 | is not publicly exposed. However, `io` allows us to interact with 5 | it through `PipeWriter` and `PipeReader`. In order to create 6 | these two halves of the `pipe`, `io` has a function: 7 | 8 | ```go 9 | func Pipe() (*PipeReader, *PipeWriter) 10 | ``` 11 | 12 | Just for having the full picture, and to see how it works under the hood, this 13 | is how `pipe` is defined: 14 | 15 | ```go 16 | // A pipe is the shared pipe structure underlying PipeReader and PipeWriter. 17 | type pipe struct { 18 | wrMu sync.Mutex // Serializes Write operations 19 | wrCh chan []byte 20 | rdCh chan int 21 | once sync.Once // Protects closing done 22 | done chan struct{} 23 | rerr onceError 24 | werr onceError 25 | } 26 | ``` 27 | 28 | Again, as consumers of `pipe` we only care about `PipeReader` and `PipeWriter`. 29 | 30 | > It can be used to connect code expecting an `io.Reader` with code expecting 31 | > an `io.Writer`. 32 | 33 | Sometimes it can happen that we hold an `io.Reader`, but we need to call a function 34 | that expects an `io.Writer`. `Pipe` is a really good tool for achieving it. 35 | 36 | > The data is copied directly from the `io.Write` to the corresponding `io.Read` 37 | > (or Reads) there is no internal buffering. 38 | 39 | ## Video about pipes 40 | 41 | I really suggest anyone to visit [this video](https://www.youtube.com/watch?v=LHZ2CAZE6Gs) 42 | to get a deeper understanding on 43 | how to use pipes. 44 | -------------------------------------------------------------------------------- /src/io-reader-and-writer.md: -------------------------------------------------------------------------------- 1 | ## Writer 2 | 3 | Writer is the interface that wraps the basic Write method. `io` package makes 4 | extensive use of this interface. `Writer` is implemented by `os.File`. Which 5 | in the `io` package is used to reference to `Stdin`, `StdOut`, and `StdErr`: 6 | 7 | ```go 8 | var ( 9 | Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin") 10 | Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout") 11 | Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr") 12 | ) 13 | ``` 14 | 15 | ### How does writer works? 16 | 17 | > Write writes len(p) bytes from p to the underlying data stream. 18 | > It returns the number of bytes written from p (0 <= n <= len(p)) 19 | > and any error encountered that caused `write` function to stop 20 | > early. Write must return a non-nil error if it returns n < len(p). 21 | > `Write` must not modify the slice data, even temporarily. 22 | 23 | Implementations must not retain p. 24 | 25 | ```go 26 | type Writer interface { 27 | Write(p []byte) (n int, err error) 28 | } 29 | ``` 30 | 31 | To see an implementation, we can check the one for `os.File`. There 32 | are some OS dependent helper functions that help `Write` doing their 33 | job. But essentially it is a transparent copy of its description. 34 | 35 | Check a fragment of the POSIX implementation of `Write` from the source 36 | code in the `os` package: 37 | 38 | ```go 39 | // Write writes len(b) bytes to the File. 40 | // It returns the number of bytes written and an error, if any. 41 | // Write returns a non-nil error when n != len(b). 42 | 43 | func (f *File) Write(b []byte) (n int, err error) { 44 | // for posix f.checkValid returns nil unless f is nil 45 | if err := f.checkValid("write"); err != nil { 46 | return 0, err 47 | } 48 | n, e := f.write(b) 49 | if n < 0 { 50 | n = 0 51 | } 52 | if n != len(b) { 53 | err = io.ErrShortWrite 54 | } 55 | epipecheck(f, e) 56 | 57 | if e != nil { 58 | err = f.wrapErr("write", e) 59 | } 60 | return n, err 61 | } 62 | ``` 63 | 64 | Side note: it's important to see that `write` will block the thread. There's 65 | an ongoing [discussion](https://github.com/golang/go/issues/6817) to provide 66 | an async API for files, but it's still being considered. 67 | 68 | ## Reader 69 | 70 | Similarly, `Reader` is the reading counterpart. It is also an interface that wraps 71 | the `Read` method. 72 | 73 | > `Read` reads up to len(p) bytes into p. It returns the number of bytes 74 | > read (0 <= n <= len(p)) and any error encountered. Even if `Read` returns 75 | > n < len(p), it may use all of p as scratch space during the call. 76 | > If some data is available but not len(p) bytes, `Read` conventionally 77 | > returns what is available instead of waiting for more. 78 | 79 | It is defined as: 80 | 81 | ```go 82 | type Reader interface { 83 | Read(p []byte) (n int, err error) 84 | } 85 | ``` 86 | 87 | As an implementor of `Read`, when there is nothing else to read, the Standard 88 | Library suggest to return 0, `EOF` instead. 0, nil is only acceptable when 89 | `p` length is equal to zero. But that's not happening very often. 90 | 91 | Additionally, the Standard Library suggest first reading the `n` bytes, and then 92 | handling the error if there was any. It adds that: 93 | 94 | > Doing so correctly handles I/O errors that happen after reading some bytes and 95 | > also both of the allowed EOF behaviors. 96 | 97 | `Read` implementation for type `os.File` relies on a helper function `read` that 98 | does all the work (except error handling). `read` itself relies on `poll.FD` (on \*nix), 99 | which is in charge of checking whether there is anything to be read on that 100 | file descriptor. For the very curious reader, go and check its implementation of 101 | `FD`'s `Read` [here](https://golang.org/src/internal/poll/fd_unix.go?s=4169:4210#L135). 102 | 103 | ```go 104 | func (f *File) Read(b []byte) (n int, err error) { 105 | if err := f.checkValid("read"); err != nil { 106 | return 0, err 107 | } 108 | 109 | n, e := f.read(b) 110 | return n, f.wrapErr("read", e) 111 | } 112 | ``` 113 | -------------------------------------------------------------------------------- /src/notes-about-godocs.md: -------------------------------------------------------------------------------- 1 | # Notes about Go docs 2 | 3 | When visiting any of the packages in [Go Std Library](https://golang.org/pkg/), 4 | there is a certain pattern that is being kept through. It helps to dissect it, 5 | so that we know what to expect from these docs, and also where to get extra 6 | information. First, there is an **Overview**, which is a simple paragraph that 7 | explains the scope of the package. It essentially tells the reader what to 8 | expect from the package in question. Additionally, in some cases, it can make 9 | some references to other packages when this helps to understand some design 10 | choices. This can, for example, happen when the API of two packages is very 11 | similar (i.e. bytes and strings). 12 | 13 | The **Index** lists all the contents of the package. It outlines all the constants, 14 | variables, functions, structs, methods, and interfaces exported by the package. 15 | It's important to note that everything that is being listed in the docs is 16 | public elements. That means, that the author of the package has deliberately 17 | chosen to let the user of the API call. But there are other elements, that are 18 | not public. And these help to build functionality, but the author has chosen not 19 | to expose them out of the package. It is sometimes useful to go through the 20 | private items of the package. This can help to get some understanding of the 21 | design choices and trade-offs of some implementation. These private blocks are 22 | found directly in the source code of the package, which is found at the bottom 23 | of the index and examples, with the title of **package files**. 24 | 25 | ## Go Specification 26 | 27 | It's important to note that, while the scope of this book is the Standard Library, 28 | there is a lot of useful information in the [Go Spec](https://golang.org/ref/spec). 29 | This is a resource that needs to be considered and reviewed almost every time, 30 | when in doubt of how internal things work in the language. 31 | -------------------------------------------------------------------------------- /src/preface.md: -------------------------------------------------------------------------------- 1 | # Preface 2 | 3 | Welcome, traveler! This is a set of notes of my journey into part of Go's 4 | standard library, comprised into a book. I hope you enjoy it! 5 | Each chapter provides a dive into some packages. I will provide a brief 6 | introduction of what is the main purpose of the package, and some examples to 7 | help to understand. In some cases, I will add some references to external 8 | content that work either as a complementary view to the topic, or even to extend 9 | the coverage of the subject. 10 | 11 | ## Who is this book for? 12 | 13 | Ideally you are a software developer, or someone with great interest in how to write 14 | code. Having an understanding on the syntax and certain command of the Go programming 15 | language will certainly help. However, I will try to keep the explanations as 16 | simple as possible in order to be able to reach to a larger audience. And 17 | hopefully, this will still be found interesting for those looking for getting 18 | a deeper understanding of what tools the language and its runtime has to offer. 19 | 20 | ## Feedback and comments 21 | 22 | This book is built entirely using markdown. And it's also hosted publicly on GitHub. 23 | For comments, issues, or any kind of feedback, please use the 24 | [Spaceship Go](https://github.com/blasrodri/spaceship-go) repository. PR's are more 25 | than welcome, of any type! 26 | 27 | ## Special thanks 28 | 29 | Most of the book is just my interpretation of the Go's standard library. Besides this, I've 30 | found that the Go Blog is an extremely useful resource. Big thanks especially 31 | to Andrew Gerrand, for all the great contributions he has in that space. 32 | 33 | There is also a YouTube channel called **just for func**, which I also highly 34 | recommend. It was particularly useful to prepare the groundwork for the `Context` 35 | chapter. Thanks to its creator, Francesc Campoy. 36 | 37 | The cover of the book is based on original work from [egonelbre gophers repo](https://github.com/egonelbre/gophers) 38 | -------------------------------------------------------------------------------- /src/sync-cond.md: -------------------------------------------------------------------------------- 1 | ## Cond 2 | > Cond implements a condition variable, a rendezvous point for goroutines 3 | > waiting for or announcing the occurrence of an event. 4 | 5 | 6 | ## Broadcast 7 | A typical use case for `Broacast` is when multiple goroutines are waiting 8 | for something to happen. In order to let all of them that a certain event 9 | happened using channels, then we would need to have one channel for each 10 | of them. And then send each of them a message. This is what `Broadcast` 11 | is about. Broadcast wakes all goroutines waiting on that `Cond`so that 12 | they can continue working. 13 | 14 | Inside a `Cond` type, there is an attribute called `notify` of type `notifyList`. 15 | `notify` is used by a function called `runtime_notifyListNotifyAll` in order to wake 16 | up all goroutines. 17 | 18 | func (c *Cond) Broadcast() { 19 | c.checker.check() 20 | runtime_notifyListNotifyAll(&c.notify) 21 | } 22 | 23 | And even when it's not necessary to understand how `Broadcast`works, if we peek 24 | at runtime_notifyListNotifyAll implementation, we can actually verify that it 25 | _readies_ all the outstanding goroutines by calling `goready`. 26 | 27 | ```go 28 | 29 | // notifyListNotifyAll notifies all entries in the list. 30 | 31 | //go:linkname notifyListNotifyAll sync.runtime_notifyListNotifyAll 32 | 33 | func notifyListNotifyAll(l *notifyList) { 34 | // Fast-path: if there are no new waiters since the last notification 35 | // we don't need to acquire the lock. 36 | if atomic.Load(&l.wait) == atomic.Load(&l.notify) { 37 | return 38 | } 39 | /* 40 | omitted code 41 | */ 42 | s := l.head 43 | /* 44 | omitted code 45 | */ 46 | for s != nil { 47 | next := s.next 48 | s.next = nil 49 | readyWithTime(s, 4) // this function calls goready 50 | s = next 51 | } 52 | } 53 | ``` 54 | 55 | ## Signal 56 | Signal will only wake **one** goroutine, if there was at least one waiting. 57 | 58 | The implementation is similar to the one for `Broadcast`. This time, it relies 59 | on a helper function called `notifyListNotifyOne`. 60 | 61 | ## Wait 62 | 63 | Cond has an associated `Locker` (which normally is a *Mutex or *RWMutex). This 64 | `Locker` must be held when calling the Wait method. 65 | 66 | If there's a goroutine that needs to be awaken to do some work, 67 | it can be modelled as follows: 68 | 69 | ```go 70 | go func(c *sync.Cond) { 71 | // hold the Locker when calling the wait method 72 | c.L.Lock() 73 | for !condition() { 74 | c.Wait() 75 | } 76 | // ... make use of condition ... 77 | c.L.Unlock() 78 | // Unlock the Locker when it's done 79 | } 80 | ``` 81 | 82 | For completeness, a `Locker` is just an interface that defines two methods: `Lock` and `Unlock`. 83 | -------------------------------------------------------------------------------- /src/sync-do.md: -------------------------------------------------------------------------------- 1 | ## Once - Do 2 | 3 | A very simple - but sometimes useful - method is `Do`. Its role is simply to 4 | guarantee that a function run through it won't run more than once. This means 5 | that if we have: 6 | 7 | ```go 8 | var once sync.Once 9 | a := make([]int, 0) 10 | once.Do(func() {a = append(a, 1)}) 11 | once.Do(func() {a = append(a, 2)}) 12 | fmt.Print(a) // [1] and not [1 2] 13 | ``` 14 | `Once` is the right type, when it's important that a certain resource gets 15 | called or initialized only once. In order to be able to call it again, and 16 | that the function is actually run, another instance of `Once` needs to be 17 | defined. And `Do` called from that new instance. -------------------------------------------------------------------------------- /src/sync-intro.md: -------------------------------------------------------------------------------- 1 | # Sync 2 | 3 | When working on a concurrent setup, Go's capabilities shine. It exposes natively 4 | channels and goroutines. It makes it really easy to get off the ground with a 5 | program that can -in many cases- run much faster. 6 | 7 | The hard part of concurrency stems from having moving parts, which need 8 | to communicate with each other and where **sequence** of that exchange really 9 | matters. There are times when channels fall short. And that's why resorting 10 | to the `sync` package can be a really good option. 11 | 12 | Normally, I only pick a selection of functions, types, methods and interfaces to 13 | cover from each package. But `sync`is an exception. I will cover everything from 14 | it, because I really believe it's a critical package. And mastering makes a 15 | difference. 16 | -------------------------------------------------------------------------------- /src/sync-mutex.md: -------------------------------------------------------------------------------- 1 | ## Mutex 2 | 3 | A `Mutex` is a flag that can be held by at most one goroutine at at time. Its 4 | name is derived from _mutually exclusive_, which indicates its purpose. The 5 | _raison d’etre_ of a `Mutex` is synchronization, by preventing that no other 6 | goroutine can perform an operation over the resources that are being mutexed. 7 | 8 | It fulfills the `Locker` interface, thus it defines `Lock` and `Unlock`. 9 | 10 | It's important to note that a lock doesn't belong to a particular goroutine. 11 | Thus, it's allowed for a goroutine to lock the `Mutex` and another to `Unlock` 12 | it. Therefore, it's still important how the different goroutines interact with 13 | the resource in order both to avoid deadlocks but also to avoid accessing to 14 | a resource when it's not locked. 15 | 16 | ## Lock 17 | 18 | This is how `Lock` is implemented. But the real meat lies in `lockSlow`, which 19 | we won't cover here. I'm mostly copying the definition just to see how the pieces 20 | are put together. The complete implementation is quite complicated, and it involves 21 | assembly code in the very end. 22 | 23 | ```go 24 | func (m *Mutex) Lock() { 25 | // Fast path: grab unlocked mutex. 26 | if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) { 27 | if race.Enabled { 28 | race.Acquire(unsafe.Pointer(m)) 29 | } 30 | return 31 | } 32 | m.lockSlow() 33 | } 34 | ``` 35 | 36 | If the `Mutex` is already locked, then the goroutine that calls `Lock` will block 37 | until the `Mutex` gets released, and can be locked by it. 38 | 39 | ## Unlock 40 | This method is responsible for unlocking the `Mutex`. It can be called from any 41 | goroutine, and it's not necessary that is the one that locked it. However, if 42 | it's unlocked, it cannot be unlocked right away. Otherwise, it panics. 43 | The implementation of this behavior is as follows: 44 | 45 | ```go 46 | const ( 47 | mutexLocked = 1 << iota // mutex is locked 48 | ) 49 | 50 | func (m *Mutex) unlockSlow(new int32) { 51 | if (new+mutexLocked)&mutexLocked == 0 { 52 | throw("sync: unlock of unlocked mutex") 53 | } 54 | /* 55 | ... 56 | */ 57 | } 58 | ``` -------------------------------------------------------------------------------- /src/sync-pool.md: -------------------------------------------------------------------------------- 1 | ## Pool 2 | > A Pool is a set of temporary objects that may be individually saved and retrieved. 3 | 4 | `Pool` provides a way to amortize allocation overhead. It serves for when there 5 | are multiple users of a resource, and we would like to avoid having to create 6 | a new time every time we need to. 7 | 8 | `Pool` caches allocated but unused items so that they can be available for later 9 | use. It relieves pressure from the garbage collector. However, 10 | **Any item stored in the Pool may be removed automatically at** 11 | **any time without notification**. 12 | 13 | 14 | ```go 15 | type Pool struct { 16 | 17 | // New optionally specifies a function to generate 18 | // a value when Get would otherwise return nil. 19 | // It may not be changed concurrently with calls to Get. 20 | New func() interface{} 21 | // contains filtered or unexported fields 22 | } 23 | ``` 24 | The important bit from the definition is that, whenever we call 25 | `Get`, and there are no items left in the pool, then it creates 26 | a new one by calling `New`. 27 | 28 | Once an item has been used, it can be returned to the `Pool` by 29 | calling `Put`. `Put` adds an item back into the `Pool`. 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/sync-waitgroup.md: -------------------------------------------------------------------------------- 1 | ## WaitGroup 2 | 3 | > A WaitGroup waits for a collection of goroutines to finish. 4 | > The main goroutine calls `Add` to set the number of goroutines to wait for. 5 | > Then each of the goroutines runs and calls `Done` when finished. 6 | > At the same time, `Wait` can be used to block until all goroutines have finished. 7 | 8 | It's possible to have another goroutine also to wait on this group. The only 9 | real constraint is that `Add` can only be called by the main one. 10 | 11 | ### Add 12 | 13 | ```go 14 | func (wg *WaitGroup) Add(delta int) 15 | ``` 16 | 17 | The **delta** defines how many more - or less - goroutines, the goroutines 18 | waiting need to actually wait for. When the counter gets to zero, then 19 | all the waiting goroutines get released. If the counter is negative, then 20 | `Add` panics. 21 | 22 | ### Done 23 | 24 | It decrements the counter by one. 25 | 26 | ### Wait 27 | 28 | Blocks the goroutine until the counter of the `WaitGroup` gets to zero. --------------------------------------------------------------------------------