Text file talks/2012/concurrency.slide

     1  Go Concurrency Patterns
     2  
     3  Rob Pike
     4  Google
     5  https://go.dev
     6  
     7  * Video
     8  
     9  This talk was presented at Google I/O in June 2012.
    10  
    11  .link http://www.youtube.com/watch?v=f6kdp27TYZs Watch the talk on YouTube
    12  
    13  * Introduction
    14  
    15  * Concurrency features in Go
    16  
    17  People seemed fascinated by the concurrency features of Go when the language was first announced.
    18  
    19  Questions:
    20  
    21  - Why is concurrency supported?
    22  - What is concurrency, anyway?
    23  - Where does the idea come from?
    24  - What is it good for?
    25  - How do I use it?
    26  
    27  * Why?
    28  
    29  Look around you. What do you see?
    30  
    31  Do you see a single-stepping world doing one thing at a time?
    32  
    33  Or do you see a complex world of interacting, independently behaving pieces?
    34  
    35  That's why. Sequential processing on its own does not model the world's behavior.
    36  
    37  * What is concurrency?
    38  
    39  Concurrency is the composition of independently executing computations.
    40  
    41  Concurrency is a way to structure software, particularly as a way to write clean code that interacts well with the real world.
    42  
    43  It is not parallelism.
    44  
    45  * Concurrency is not parallelism
    46  
    47  Concurrency is not parallelism, although it enables parallelism.
    48  
    49  If you have only one processor, your program can still be concurrent  but it cannot be parallel.
    50  
    51  On the other hand, a well-written concurrent program might run efficiently in parallel on a multiprocessor. That property could be important...
    52  
    53  For more on that distinction, see the link below. Too much to discuss here.
    54  
    55  .link /s/concurrency-is-not-parallelism go.dev/s/concurrency-is-not-parallelism
    56  
    57  * A model for software construction
    58  
    59  Easy to understand.
    60  
    61  Easy to use.
    62  
    63  Easy to reason about.
    64  
    65  You don't need to be an expert!
    66  
    67  (Much nicer than dealing with the minutiae of parallelism (threads, semaphores, locks, barriers, etc.))
    68  
    69  * History
    70  
    71  To many, the concurrency features of Go seemed new.
    72  
    73  But they are rooted in a long history, reaching back to Hoare's CSP in 1978 and even Dijkstra's guarded commands (1975).
    74  
    75  Languages with similar features:
    76  
    77  - Occam (May, 1983)
    78  - Erlang (Armstrong, 1986)
    79  - Newsqueak (Pike, 1988)
    80  - Concurrent ML (Reppy, 1993)
    81  - Alef (Winterbottom, 1995)
    82  - Limbo (Dorward, Pike, Winterbottom, 1996).
    83  
    84  * Distinction
    85  
    86  Go is the latest on the Newsqueak-Alef-Limbo branch, distinguished by first-class channels.
    87  
    88  Erlang is closer to the original CSP, where you communicate to a process by name rather than over a channel.
    89  
    90  The models are equivalent but express things differently.
    91  
    92  Rough analogy: writing to a file by name (process, Erlang) vs. writing to a file descriptor (channel, Go).
    93  
    94  * Basic Examples
    95  
    96  * A boring function
    97  
    98  We need an example to show the interesting properties of the concurrency primitives.
    99  
   100  To avoid distraction, we make it a boring example.
   101  
   102  .play concurrency/support/boring.go /START/,/STOP.*/
   103  
   104  * Slightly less boring
   105  
   106  Make the intervals between messages unpredictable (still under a second).
   107  
   108  .play concurrency/support/lessboring.go /START/,/STOP/
   109  
   110  * Running it
   111  
   112  The boring function runs on forever, like a boring party guest.
   113  
   114  .play concurrency/support/lessboring.go /^func.main/,$
   115  
   116  * Ignoring it
   117  
   118  The go statement runs the function as usual, but doesn't make the caller wait.
   119  
   120  It launches a goroutine.
   121  
   122  The functionality is analogous to the & on the end of a shell command.
   123  
   124  .play concurrency/support/goboring.go 1,/^}/
   125  
   126  * Ignoring it a little less
   127  
   128  When main returns, the program exits and takes the boring function down with it.
   129  
   130  We can hang around a little, and on the way show that both main and the launched goroutine are running.
   131  
   132  .play concurrency/support/waitgoboring.go /func.main/,/^}/
   133  
   134  * Goroutines
   135  
   136  What is a goroutine? It's an independently executing function, launched by a go statement.
   137  
   138  It has its own call stack, which grows and shrinks as required.
   139  
   140  It's very cheap. It's practical to have thousands, even hundreds of thousands of goroutines.
   141  
   142  It's not a thread.
   143  
   144  There might be only one thread in a program with thousands of goroutines.
   145  
   146  Instead, goroutines are multiplexed dynamically onto threads as needed to keep all the goroutines running.
   147  
   148  But if you think of it as a very cheap thread, you won't be far off.
   149  
   150  * Communication
   151  
   152  Our boring examples cheated: the main function couldn't see the output from the other goroutine.
   153  
   154  It was just printed to the screen, where we pretended we saw a conversation.
   155  
   156  Real conversations require communication.
   157  
   158  * Channels
   159  
   160  A channel in Go provides a connection between two goroutines, allowing them to communicate.
   161  
   162  .code concurrency/support/helpers.go /START1/,/STOP1/
   163  .code concurrency/support/helpers.go /START2/,/STOP2/
   164  .code concurrency/support/helpers.go /START3/,/STOP3/
   165  
   166  * Using channels
   167  
   168  A channel connects the main and boring goroutines so they can communicate.
   169  
   170  .play concurrency/support/changoboring.go /START1/,/STOP1/
   171  .code concurrency/support/changoboring.go /START2/,/STOP2/
   172  
   173  * Synchronization
   174  
   175  When the main function executes <–c, it will wait for a value to be sent.
   176  
   177  Similarly, when the boring function executes c <– value, it waits for a receiver to be ready.
   178  
   179  A sender and receiver must both be ready to play their part in the communication. Otherwise we wait until they are.
   180  
   181  Thus channels both communicate and synchronize.
   182  
   183  * An aside about buffered channels
   184  
   185  Note for experts: Go channels can also be created with a buffer.
   186  
   187  Buffering removes synchronization.
   188  
   189  Buffering makes them more like Erlang's mailboxes.
   190  
   191  Buffered channels can be important for some problems but they are more subtle to reason about.
   192  
   193  We won't need them today.
   194  
   195  * The Go approach
   196  
   197  Don't communicate by sharing memory, share memory by communicating.
   198  
   199  * "Patterns"
   200  
   201  * Generator: function that returns a channel
   202  
   203  Channels are first-class values, just like strings or integers.
   204  
   205  .play concurrency/support/generatorboring.go /START1/,/STOP1/
   206  .code concurrency/support/generatorboring.go /START2/,/STOP2/
   207  
   208  * Channels as a handle on a service
   209  
   210  Our boring function returns a channel that lets us communicate with the boring service it provides.
   211  
   212  We can have more instances of the service.
   213  
   214  .play concurrency/support/generator2boring.go /START1/,/STOP1/
   215  
   216  * Multiplexing
   217  
   218  These programs make Joe and Ann count in lockstep.
   219  We can instead use a fan-in function to let whosoever is ready talk.
   220  
   221  .code concurrency/support/faninboring.go /START3/,/STOP3/
   222  .play concurrency/support/faninboring.go /START1/,/STOP1/
   223  
   224  * Fan-in
   225  
   226  .image concurrency/images/gophermegaphones.jpg
   227  
   228  * Restoring sequencing
   229  
   230  Send a channel on a channel, making goroutine wait its turn.
   231  
   232  Receive all messages, then enable them again by sending on a private channel.
   233  
   234  First we define a message type that contains a channel for the reply.
   235  
   236  .code concurrency/support/sequenceboring.go /START0/,/STOP0/
   237  
   238  * Restoring sequencing.
   239  
   240  Each speaker must wait for a go-ahead.
   241  
   242  .code concurrency/support/sequenceboring.go /START1/,/STOP1/
   243  .code concurrency/support/sequenceboring.go /START2/,/STOP2/
   244  .play concurrency/support/sequenceboring.go /START3/,/STOP3/
   245  
   246  * Select
   247  
   248  A control structure unique to concurrency.
   249  
   250  The reason channels and goroutines are built into the language.
   251  
   252  * Select
   253  
   254  The select statement provides another way to handle multiple channels.
   255  It's like a switch, but each case is a communication:
   256  - All channels are evaluated.
   257  - Selection blocks until one communication can proceed, which then does.
   258  - If multiple can proceed, select chooses pseudo-randomly.
   259  - A default clause, if present, executes immediately if no channel is ready.
   260  
   261  .code concurrency/support/select.go /START0/,/STOP0/
   262  
   263  * Fan-in again
   264  
   265  Rewrite our original fanIn function. Only one goroutine is needed. Old:
   266  
   267  .code concurrency/support/faninboring.go /START3/,/STOP3/
   268  
   269  * Fan-in using select
   270  
   271  Rewrite our original fanIn function. Only one goroutine is needed. New:
   272  
   273  .play concurrency/support/selectboring.go /START3/,/STOP3/
   274  
   275  * Timeout using select
   276  
   277  The time.After function returns a channel that blocks for the specified duration.
   278  After the interval, the channel delivers the current time, once.
   279  
   280  .play concurrency/support/timeout.go /START1/,/STOP1/
   281  
   282  * Timeout for whole conversation using select
   283  
   284  Create the timer once, outside the loop, to time out the entire conversation.
   285  (In the previous program, we had a timeout for each message.)
   286  
   287  .play concurrency/support/timeoutall.go /START1/,/STOP1/
   288  
   289  
   290  * Quit channel
   291  
   292  We can turn this around and tell Joe to stop when we're tired of listening to him.
   293  
   294  .code concurrency/support/quit.go /START1/,/STOP1/
   295  .play concurrency/support/quit.go /START2/,/STOP2/
   296  
   297  
   298  * Receive on quit channel
   299  
   300  How do we know it's finished? Wait for it to tell us it's done: receive on the quit channel
   301  
   302  .code concurrency/support/rcvquit.go /START1/,/STOP1/
   303  .play concurrency/support/rcvquit.go /START2/,/STOP2/
   304  
   305  * Daisy-chain
   306  
   307  .play concurrency/support/daisy.go /func/,$
   308  
   309  * Chinese whispers, gopher style
   310  
   311  .image concurrency/images/gophereartrumpet.jpg
   312  
   313  * Systems software
   314  
   315  Go was designed for writing systems software.
   316  Let's see how the concurrency features come into play.
   317  
   318  * Example: Google Search
   319  
   320  Q: What does Google search do?
   321  
   322  A: Given a query, return a page of search results (and some ads).
   323  
   324  Q: How do we get the search results?
   325  
   326  A: Send the query to Web search, Image search, YouTube, Maps, News,etc., then mix the results.
   327  
   328  How do we implement this?
   329  
   330  * Google Search: A fake framework
   331  
   332  We can simulate the search function, much as we simulated conversation before.
   333  
   334  .code concurrency/support/google.go /START2/,/STOP2/
   335  
   336  * Google Search: Test the framework
   337  
   338  .play concurrency/support/google.go /func.main/,/}/
   339  
   340  * Google Search 1.0
   341  
   342  The Google function takes a query and returns a slice of Results (which are just strings).
   343  
   344  Google invokes Web, Image, and Video searches serially, appending them to the results slice.
   345  
   346  .play concurrency/support/google.go /START1/,/STOP1/
   347  
   348  * Google Search 2.0
   349  
   350  Run the Web, Image, and Video searches concurrently, and wait for all results.
   351  
   352  No locks.  No condition variables.  No callbacks.
   353  
   354  .play concurrency/support/google2.1.go /Google/,/^}/
   355  
   356  * Google Search 2.1
   357  
   358  Don't wait for slow servers. No locks.  No condition variables.  No callbacks.
   359  
   360  .play concurrency/support/google2.2.go /START/,/STOP/
   361  
   362  * Avoid timeout
   363  
   364  Q: How do we avoid discarding results from slow servers?
   365  
   366  A: Replicate the servers.  Send requests to multiple replicas, and use the first response.
   367  
   368  .code concurrency/support/google2.3.go /START1/,/STOP1/
   369  
   370  * Using the First function
   371  
   372  .play concurrency/support/google2.3.go /START2/,/STOP2/
   373  
   374  * Google Search 3.0
   375  
   376  Reduce tail latency using replicated search servers.
   377  
   378  .play concurrency/support/google3.0.go /START/,/STOP/
   379  
   380  * And still…
   381  
   382  No locks.  No condition variables.  No callbacks.
   383  
   384  * Summary
   385  
   386  In just a few simple transformations we used Go's concurrency primitives to convert a
   387  
   388  - slow
   389  - sequential
   390  - failure-sensitive
   391  
   392  program into one that is
   393  
   394  - fast
   395  - concurrent
   396  - replicated
   397  - robust.
   398  
   399  * More party tricks
   400  
   401  There are endless ways to use these tools, many presented elsewhere.
   402  
   403  Chatroulette toy:
   404  
   405  .link /s/chat-roulette go.dev/s/chat-roulette
   406  
   407  Load balancer:
   408  
   409  .link /s/load-balancer go.dev/s/load-balancer
   410  
   411  Concurrent prime sieve:
   412  
   413  .link /s/prime-sieve go.dev/s/prime-sieve
   414  
   415  Concurrent power series (by McIlroy):
   416  
   417  .link /s/power-series go.dev/s/power-series
   418  
   419  * Don't overdo it
   420  
   421  They're fun to play with, but don't overuse these ideas.
   422  
   423  Goroutines and channels are big ideas. They're tools for program construction.
   424  
   425  But sometimes all you need is a reference counter.
   426  
   427  Go has "sync" and "sync/atomic" packages that provide mutexes, condition variables, etc. They provide tools for smaller problems.
   428  
   429  Often, these things will work together to solve a bigger problem.
   430  
   431  Always use the right tool for the job.
   432  
   433  * Conclusions
   434  
   435  Goroutines and channels make it easy to express complex operations dealing with
   436  
   437  - multiple inputs
   438  - multiple outputs
   439  - timeouts
   440  - failure
   441  
   442  And they're fun to use.
   443  
   444  
   445  * Links
   446  
   447  Go Home Page:
   448  
   449  .link / go.dev
   450  
   451  Go Tour (learn Go in your browser)
   452  
   453  .link /tour/ go.dev/tour
   454  
   455  Package documentation:
   456  
   457  .link /pkg go.dev/pkg
   458  
   459  Articles galore:
   460  
   461  .link /doc go.dev/doc
   462  
   463  Concurrency is not parallelism:
   464  
   465  .link /s/concurrency-is-not-parallelism go.dev/s/concurrency-is-not-parallelism
   466  

View as plain text