-
Notifications
You must be signed in to change notification settings - Fork 1
/
concurrencytalks-session1.slide
376 lines (236 loc) · 15.8 KB
/
concurrencytalks-session1.slide
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
Cytora Golang Academy
Part One
Adelina Simion
Senior Software Engineer, Cytora
@classic_addetz
* About me
.image assets/about-me.png
* About Cytora
.image assets/about-cytora.png
* Format of talks
We have three talks/magic shows each 45-60mins.
*Easy* *magic* *tricks:* *concurrency* *basics* *in* *Go*
introduces and covers the basics.
*Apprentice* *to* *Illusionist:* *channels* *deep* *dive*
discusses more complex channels operations, their usage and their limitations.
*Master* *Illusionist:* *concurrency* *patterns*
presents Go concurrency patterns and real world examples.
.image https://media.giphy.com/media/21T9PmWttOb0EFrrwK/giphy.gif _ 450
* About this slide deck
These slides are built with `go` `present`.
.link https://godoc.org/golang.org/x/tools/present
You can find the slide deck repository on GitHub
.link https://github.com/cytora/goroutines-channels-talks
* Our first magic show!
.image assets/session1-title.png
* Agenda - Magic show I
This session will cover:
- Concurrency vs parallelism
- What is a goroutine?
- Common uses of goroutines
- Goroutines basics
- The Mutex & the WaitGroup
- Channels basics
- Channels under the hood
* Concurrency vs parallelism
Everyone knows that `Go` is designed for concurrency, but what is *concurrency*?
*Concurrency*:
- the composition of independently executing processes
- dealing with lots of things at once
- execution can complete in any given order decided by the scheduler
*Parallelism*:
- the simultaneous execution of (possibly related) computations
- doing lots of things at once
- execution is happening simultaneously and independently
* The concurrent & parallel magician
The magician shuffles the card deck and entertains his audience with jokes using *paralellism*. He is doing both at the same time.
The magician shuffles the card deck and steals a card using *concurrency*. At some point, he stopped shuffling and stole a card. But we are not sure the order in which these events happened.
.image https://media.giphy.com/media/UV4liSdOaKdw3V9HGI/giphy.gif _ 350
* What is a Goroutine?
In Go, concurrency is achieved by using Goroutines. Goroutines are functions or methods which can run concurrently with other methods and functions
- Goroutines are sometimes called _lightweight_ _threads_
- Goroutines require *2kb* of memory, while threads require *1Mb* (~500x more)
- Unlike threads, they are managed by the `Go` runtime not the OS, so they are faster
There are other differences between threads and goroutines, but this is outside the scope of our discussion/magic show today.
We will consider goroutines Go runtime managed lightweight threads.
* Cool, but why should I care?
Common uses of goroutines are:
- Background processing of large files
- Handling user requests in web servers
- Pushing tracking events/logs in the background
Most importantly, goroutines allow our programs to run more than one function.
.image https://media.giphy.com/media/Nx85vtTY70T3W/giphy.gif _ 400
* The go keyword
Prefix the function or method call with the keyword `go` and you will have a new Goroutine running concurrently with the main goroutine.
.code code-session1/goroutines/go_keyword.go /START OMIT/,/END OMIT/
.image https://media.giphy.com/media/UDjF1zMreMld6/giphy.gif _ 400
* The Goroutine Mentalist trick
Let's start the show - our magician will read our minds. Fingers crossed it will work!
.play code-session1/goroutines/first_routine.go /START OMIT/,/END OMIT/
* Oh no! The magician failed!
Let's have a look at why our previous example didn't work:
- The `main` goroutine started the magic show
- The `main` goroutine started the magician goroutine
- The `main` goroutine had no more work and closed down the magic show
- The `main` goroutine exited
Our magician goroutine never had the chance to return its results before main shut down the whole program!
When a new Goroutine is started, the goroutine call returns immediately. It is *non-blocking* and allows the goroutine that it is created from to continue its work.
If the main goroutine terminates then the program will be terminated and no other Goroutine will run.
* Let's give the magician another chance
.image https://media.giphy.com/media/XGhGacEaO9PQ1Kgck3/giphy.gif _ 350
* Waiting for our goroutines - the Goroutine Mentalist trick reloaded!
.play code-session1/goroutines/first_routine_sleep.go /START OMIT/,/END OMIT/
* The magician did his trick!
The magician takes a bow, he's done his trick, but there are some problems:
- The magic show ends a long time after the magician ends = *waste* *of* *resources*
- The sleep is still no guarantee that we will wait for the magician if he is slow = *intermittent* *bug*
We need something better!
.image https://media.giphy.com/media/KOBFnpQdaKChW/giphy.gif _ 400
* The sync package
We will discuss two synchronization mechanisms from the `sync` package today:
- the `WaitGroup`
- the `Mutex`
From the `godocs`:
.code code-session1/waitgroups/sync_documentation.go /START OMIT/,/END OMIT/
.link https://golang.org/pkg/sync
A further discussion of synchronization vs channels will take place in the second presentation/magic show, we are only interested in usage today.
* Magic refresher - pointers & argument passing
Let's recount one of the basic magic school lessons about pointers & argument passing:
- A *pointer* is a memory address to a variable. The `iptr:=&i` syntax gives the memory address of `i`, i.e. a pointer to `i` is assigned to variable `iptr`
- The `*iptr` syntax *dereferences* the pointer from its memory address to the current value at that address
- Passing pointers to functions allow us to make permanent changes to variables
- The rules of pointer apply to *method* *receivers* also, as they can be considered the first *implicit* argument of methods.
* The Rabbit Hat-trick
.play code-session1/pointers/copy_argument_passing.go /START OMIT/,/END OMIT/
* Oh noes! The hat trick failed!
We tried to place a rabbit and flowers in the hat, but it remained empty!
This is because of the way we pass arguments to functions in Go.
Arguments to functions are passed by copy in Go. Any changes we make to the copy will be lost, as the copy is destroyed by the garbage collector once the function exits
.image https://media.giphy.com/media/YOqgY2wQazNKleTJ5D/giphy.gif _ 450
* The Pointer Rabbit Hat-trick
.play code-session1/pointers/pointers_argument_passing.go /START OMIT/,/END OMIT/
* A successful classic hat trick!
As we saw in the hat trick, assigning the rabbit to the hat only succeeds once we passed a pointer to the hat. Otherwise, the rabbit is put inside a copy of the hat, which only lives inside function scope, and the trick fails!
The same rules were valid to with method receivers also, as we saw when we tried to replace the rabbit with flowers.
.image https://media.giphy.com/media/MFlhfP81IpKgaN50fq/giphy.gif _ 300
* The Bullet Catch
In this trick, timing is everything! The assistant fires a gun at the magician and he *magically* catches the bullet. He then reveals the caught bullet and takes a bow.
.play code-session1/waitgroups/failed/wg_magic_show_failed.go /START OMIT/,/END OMIT/
* Another failed trick!
The magician made the career ending mistake of revealing the bullet before the gun was fired. What a disaster!
Remember that the Go runtime is the one that decides the order in which Goroutines execute. Even though we start the `assistant` goroutine before the `magician` goroutine, they can be executed in any order.
The Go runtime is the boss of the goroutines, not us!
.image https://media.giphy.com/media/li0dswKqIZNpm/giphy.gif _ 400
* The WaitGroup
We can use `sync.WaitGroup` to ensure that goroutines wait for each other to finish.
The `sync.WaitGroup`:
- Has three functions: `Add(int)`, `Wait()` and `Done()`
- Has an inner counter that starts at zero and maintains the state of the `WaitGroup`
- Adds `n` to the counter when you call `Add(n)`
- Removes 1 from the counter when you call `Done()`
- `Wait()` blocks the current goroutine and it will be released when the counter is zero
- Should always be passed by reference/pointer to goroutines
* The WaitGroup Bullet Catch
.play code-session1/waitgroups/wg_magic_show.go /START OMIT/,/END OMIT/
* Woah, the magician caught the bullet!
The `sync.WaitGroup` guaranteed that the assistant fires before the magician catches the bullet AND that the main goroutine waited for the trick to end.
.image https://media.giphy.com/media/eIm624c8nnNbiG0V3g/giphy.gif _ 550
* WaitGroup gotchas
aka "Mistakes Adelina's already done many times"
- `WaitGroup` does not need to be passed any params and is usually declared as `var` `wg` `sync.WaitGroup` as shown
- Make sure to use `defer` `wg.Done()` inside your Goroutines. This will ensure you release the `WaitGroup` even if your goroutine errors/panics
- Make sure you pass a pointer to the `WaitGroup` to your Goroutines using `&wg`. Otherwise, a copy will be passed to the function and you will not release anything to the original `main` blocking `WaitGroup`
- Remember that you can `Add(n)` any integer amount, but `Done()` only decreases the counter by 1 and we will not release the `Wait` until the counter reaches 0
* The Infinite Hat Trick - the hat
Let's try the hat trick again with multiple items. Multiple performers will place items in a magically infinite hat!
.code code-session1/mutex/infinite_hat.go /START OMIT/,/END OMIT/
* The Infinite Hat Trick - the trick
.play code-session1/mutex/failed/mutex_magic_show.go /START OMIT/,/END OMIT/
* Oh no! The hat is missing items!
Some goroutines arriving to place items in the hat overwrote other's work and some items were lost. Now, that's some unintentional magic!
.image https://media.giphy.com/media/z1bMHX8k9Z3yg/giphy.gif _ 350
* The Mutex
When a program runs concurrently, the parts of code which modify shared resources should not be accessed by multiple Goroutines at the same time.
In our case, the performers were modifying the hat contents, which was the shared resource. This was the *critical* section of the hat trick.
The `sync.Mutex` is used to provide a locking mechanism:
- The `sync.Mutex` has 2 methods: `Lock` and `Unlock`
- Any code that is present between a call to `Lock` and `Unlock` will be executed by only one Goroutine. This prevents intermittent bugs and inconsistent results
- If one Goroutine already holds the lock and if a new Goroutine is trying to acquire a lock, the new Goroutine will be blocked until the mutex is unlocked
- Mutexes are used to make *threadsafe* structs
- Should always be passed by reference/pointer to goroutines
* The Mutex Infinite Hat Trick - the hat
We will make our infinite hat thread safe with a mutex so only one performer can place items in it at a time.
.code code-session1/mutex/mutex_infinite_hat.go /START OMIT/,/END OMIT/
* The Mutex Infinite Hat Trick - the trick
.play code-session1/mutex/success/mutex_magic_show.go /START OMIT/,/END OMIT/
* Oh woah! Maybe that hat really is infinite!
The mutex made sure that each goroutine gained control of the hat and then placed the item in it. The goroutines are no longer able to overwrite/remove each other's items.
.image https://media.giphy.com/media/2Fazg4XrLLmoGqwMg/giphy.gif _ 500
* The WaitGroup Quick Change Trick
.play code-session1/waitgroups/wg_quick_change.go /START OMIT/,/END OMIT/
* Channels
While the Quick Change Trick works with `sync.WaitGroup`, channels provide a more elegant way for goroutines to communicate and synchronize at the same time.
*Channels* can be thought of as *pipes* through which Goroutines communicate.
- Each channel has a type it is allowed to transport associated with it.
- `chan` `T` is a channel of type `T`. No other type than `T` is allowed to be transported using the channel.
- The zero value of a channel is `nil`.
- Channels are defined using `make` similar to maps and slices - `make(chan` `T)`.
- Channels can be closed to signal that no other values will sent through the pipe - `close(a)`
* Buffered channels
Earlier we said that channels were created with `make(chan` `T)`. This syntax will create an *unbuffered* channel with capacity 0. Once a value is written to it, no other value can be written to the channel until a read takes place.
It is possible to create a channel with a *buffer*. This allows us to send multiple values into the pipe without waiting for reads.
*Buffered* *channels* can be created by passing an additional capacity parameter to the `make` function which specifies the size of the buffer. Buffered channels can hold multiple values, after which they are considered *full*.
`make(chan` `T,` `capacity)`
* Sending and receiving from channel
The syntax to *receive* from a channel `a` is `data` `:=` `<-` `a`.
The arrow points outwards from `a` and hence we are reading from channel `a` and storing the value to the variable `data`.
The syntax to *write* to a channel `a` is `a` `<-` `data`
The arrow points towards `a` and hence we are writing `data` to channel `a`.
.image https://media.giphy.com/media/ZMqgw8Oq7TRKg/giphy.gif _ 450
* Channels under the hood
We can think of each channel consisting of three FIFO (first in, first out) queues:
- the *receiving* goroutine queue: goroutines in this queue are all in blocking state and waiting to receive values from that channel
- the *sending* goroutine queue: goroutines in this queue are all in blocking state and waiting to send values to that channel
- the *value* queue: holds anywhere between 0 and buffer size amount of values
Each channel internally holds a mutex lock which is used to avoid data races in all kinds of operations.
* Behaviour of channels
- Sending to a full channel? Easy, wait in the sending goroutine queue until there is room in the pipe to put the value
- Receiving from an empty channel? Easy, wait in the receiving goroutine queue until there is a value in the pipe
- Receiving from a closed channel? Easy, send the zero value of that type
- Sending to a closed channel? Panic as we don't to add the goroutine to the sending queue and block it when we know other values are not going to come
* The Channel Quick Change Trick
.play code-session1/channels/channel_quick_change.go /START OMIT/,/END OMIT/
* Another successful magic trick!
We have used a channel to get the `main` goroutine to wait for the magician to finish the Quick Change. We have replaced the `sync.WaitGroup` with a channel.
This is possible due to the behaviour of channels which we have presented today, but will examine further in the next magic show!
.image https://media.giphy.com/media/S98HinM7FizN29m97L/giphy.gif _ 500
* Magic show curtain drop
Woah! What a great show! We hope you all enjoyed yourselves!
Our magicians have shown you the following magic tricks:
- *Mentalist* trick: doing basic work with goroutines and waiting for them to complete with sleep
- *Bullet* *Catch* trick: guarantee goroutine execution completion and order using the `sync.WaitGroup`
- *Infinite* *Hat* trick: ensure goroutines acquire orderly control of shared resources
- *Quick* *Change* trick: signalling work is completed using channels instead of `sync.WaitGroup` and discussing the blocking behaviour of channel sends/receives
* Useful links - goroutines
A complete journey with goroutines
.link https://medium.com/@riteeksrivastava/a-complete-journey-with-goroutines-8472630c7f5c
Concurrency and parallelism in Golang
.link https://medium.com/@tilaklodha/concurrency-and-parallelism-in-golang-5333e9a4ba64
Rob Pike's excellent talk on the Go blog
.link https://blog.golang.org/waza-talk
Golangbot - goroutines
.link https://golangbot.com/goroutines/
* Useful links - channels
Golangbot - mutexes
.link https://golangbot.com/mutex/
Golangbot - channels
.link https://golangbot.com/channels/
Channels in Go
.link https://go101.org/article/channel.html
Go by example - channel buffering
.link https://gobyexample.com/channel-buffering
* See you next time!
.image https://media.giphy.com/media/7yojoQtevjOCI/giphy.gif _ 550
* Keep in touch with Cytora!
.image assets/keep-in-touch.png