Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to Go Wrong with Concurrency

How to Go Wrong with Concurrency

Go provides powerful tools for designing concurrent programs, but does not give you strict guidelines on how to use them.

In my talk, I will explore this area, especially in the context for designing package APIs.

Alexey Palazhchenko

July 05, 2019
Tweet

More Decks by Alexey Palazhchenko

Other Decks in Programming

Transcript

  1. • Some guidelines are indeed strict • Most are not

    • Use your own judgment • But learn from best practices
  2. Who am I • Go programmer since r60 • m[k]

    = v, false • Go meetups and conferences organizer
  3. Who am I • Go programmer since r60 • m[k]

    = v, false • Go meetups and conferences organizer • gophership.ru in 2 weeks
  4. Who am I • Go programmer since r60 • m[k]

    = v, false • Go meetups and conferences organizer • gophership.ru in 2 weeks • golang-ru Slack admin
  5. Who am I • Go programmer since r60 • m[k]

    = v, false • Go meetups and conferences organizer • gophership.ru in 2 weeks • golang-ru Slack admin • #school channel
  6. Axioms • A send to a full channel blocks •

    A receive from an empty channel blocks
  7. Axioms • A send to a full channel blocks •

    A receive from an empty channel blocks • A send to a nil channel blocks forever • A receive from a nil channel blocks forever
  8. Axioms • A send to a full channel blocks •

    A receive from an empty channel blocks • A send to a nil channel blocks forever • A receive from a nil channel blocks forever • A send to a closed channel panics • A receive from a closed channel returns the zero value immediately
  9. Axioms • A send to a full channel blocks •

    A receive from an empty channel blocks • A send to a nil channel blocks forever • A receive from a nil channel blocks forever • A send to a closed channel panics • A receive from a closed channel returns the zero value immediately *That’s actually Dave Cheney, not me.
  10. Theorems • Closing the channel is a send-like communication from

    the senders to the receivers that there will be no more sends.
  11. Theorems • Closing the channel is a send-like communication from

    the senders to the receivers that there will be no more sends. • It comes up repeatedly that people want to use close as a reverse signal from receivers to senders to say "stop sending to me". That is not what it means.
  12. Theorems • Closing the channel is a send-like communication from

    the senders to the receivers that there will be no more sends. • It comes up repeatedly that people want to use close as a reverse signal from receivers to senders to say "stop sending to me". That is not what it means. *That’s actually Russ Cox, not me.
  13. • Do not use the same channel for both sends

    and receives in the same place. Theorems
  14. • Do not use the same channel for both sends

    and receives in the same place. • Except for toy ping-pong examples. Theorems
  15. • Do not use the same channel for both sends

    and receives in the same place. • Except for toy ping-pong examples. • And semaphores. Theorems
  16. • Do not use the same channel for both sends

    and receives in the same place. • Except for toy ping-pong examples. • And semaphores. Theorems *That’s actually me this time!
  17. • Do not use the same channel for both sends

    and receives in the same place. • Except for toy ping-pong examples. • And semaphores. Theorems *That’s actually me this time! **Maybe not
  18. MySQL slowlog parser # Time: 131128 1:05:31
 # User@Host: user1[user1]

    @ localhost [127.0.0.1] Id: 69194
 # Schema: maindb Last_errno: 0 Killed: 0
 # Query_time: 0.000228 Rows_sent: 1 Rows_examined: 1
 # Bytes_sent: 545 Tmp_tables: 0 Tmp_disk_tables: 0 Tmp_table_sizes: 0
 # InnoDB_trx_id: 1A88583F
 # QC_Hit: No Full_scan: No Full_join: No Tmp_table: No
 # Log_slow_rate_type: query Log_slow_rate_limit: 2
 SET timestamp=1385600731;
 SELECT foo FROM bar WHERE id=1;
  19. Initial parser type Event struct { // … } type

    Parser interface { Events() chan Event }
  20. A better parser type Event struct { // … }

    type Parser interface { Events() <-chan Event }
  21. An even better parser type Event struct { // …

    } type Parser interface { Events() <-chan *Event }
  22. An even better parser type Event struct { Metrics map[string]float64

    } type Parser interface { Events() <-chan *Event }
  23. A good parser type Event struct { Metrics map[string]float64 }

    type Parser interface { Start(ctx context.Context) Events() <-chan *Event }
  24. A great parser type Event struct { Metrics map[string]float64 }

    type Parser interface { Start(ctx context.Context, eventsCap int) Events() <-chan *Event }
  25. A wonderful parser type Event struct { Metrics map[string]float64 Error

    error } type Parser interface { Start(ctx context.Context, eventsCap int) Events() <-chan *Event }
  26. An awesome parser type Event struct { Metrics map[string]float64 Error

    error } type Parser interface { // Start starts parser in a separate goroutine. // Context cancelation starts parser shutdown. // Events channel is closed when a shutdown is complete. Start(ctx context.Context, eventsCap int) Events() <-chan *Event }
  27. An perfect parser type Parser interface { // Start starts

    parser in a separate goroutine. // Context cancelation starts parser shutdown. // Events channel is closed when a shutdown is complete. Start(ctx context.Context, eventsCap int) // A parser is blocked when Events channel is full. Events() <-chan *Event }
  28. Using our perfect (?) parser func collect(ch <-chan *Event) {

    for event := range ch { if fatal(event) { return } }
  29. Using our no-so-perfect parser func collect(ch <-chan *Event) { defer

    func() { for range ch {} // drain }() for event := range ch { if fatal(event) { return } } }
  30. Using our quite bad parser func collect(ch <-chan *Event) {

    defer func() { /* ... */ }() for event := range ch { if fatal(event) { // how to stop parser? return } } }
  31. Using our ugly parser func collect(ch <-chan *Event, cancel context.CancelFunc)

    { defer func() { /* ... */ }() for event := range ch { if fatal(event) { cancel() return } } }
  32. Using our ugly parser func collect(ch <-chan *Event, cancel context.CancelFunc)

    { defer func() { /* ... */ }() for event := range ch { if fatal(event) { // how to let caller know we stopped parser? cancel() return } } }
  33. What are we parsing anyway? # Time: 131128 1:05:31
 #

    User@Host: user1[user1] @ localhost [127.0.0.1] Id: 69194
 # Schema: maindb Last_errno: 0 Killed: 0
 # Query_time: 0.000228 Rows_sent: 1 Rows_examined: 1
 # Bytes_sent: 545 Tmp_tables: 0 Tmp_disk_tables: 0 Tmp_table_sizes: 0
 # InnoDB_trx_id: 1A88583F
 # QC_Hit: No Full_scan: No Full_join: No Tmp_table: No
 # Log_slow_rate_type: query Log_slow_rate_limit: 2
 SET timestamp=1385600731;
 SELECT foo FROM bar WHERE id=1;
  34. What are we parsing anyway? • Two parts: block reader

    and block parser • No reason for the reader to be asynchronous
  35. What are we parsing anyway? • Two parts: block reader

    and block parser • No reason for the reader to be asynchronous • For parser – maybe
  36. Do we have to expose async API? • "Do not

    want to block the main thread"
  37. Do we have to expose async API? • "Do not

    want to block the main thread" • There is no "main thread"
  38. Do we have to expose async API? • "Do not

    want to block the main thread" • There is no "main thread" • "Pipelines a great and enable concurrency"
  39. Do we have to expose async API? • "Do not

    want to block the main thread" • There is no "main thread" • "Pipelines a great and enable concurrency" • Sure, but they have downsides too
  40. Do we have to expose async API? • "Do not

    want to block the main thread" • There is no "main thread" • "Pipelines a great and enable concurrency" • Sure, but they have downsides too • And do you really need concurrency?
  41. The best API • The best exposed APIs are synchronous,


    hard to misuse, and boring • Let caller decide if concurrency is desired
  42. Real parser // NextEvent and Close can be called concurrently.

    type Parser interface { NextEvent() (*Event, error) Close() error }
  43. The WORST parser type Parser interface { NextEvent() *Event //

    A caller should continue to call NextEvent // until error is returned. Close() error } func (p *SlowLogParser) NextEvent() *Event { return <-p.events }
  44. How to go wrong with concurrency? • By using it

    where it is not needed • "Concurrency Semantics" Workshop tickets are still on sale!
  45. How to go wrong with concurrency? • By using it

    where it is not needed • "Concurrency Semantics" Workshop tickets are still on sale! • By exporting concurrent APIs where it is not needed
  46. Are there exceptions? • There are always exceptions • When

    there are already async APIs • Avoid the worst variant
  47. Are there exceptions? • There are always exceptions • When

    there are already async APIs • Avoid the worst variant • When you observe async events
  48. Are there exceptions? • There are always exceptions • When

    there are already async APIs • Avoid the worst variant • When you observe async events • And don't want to poll
  49. Are there exceptions? • There are always exceptions • When

    there are already async APIs • Avoid the worst variant • When you observe async events • And don't want to poll • For consistency with other APIs, time pressure, and other real-life events
  50. How to go wrong with concurrency? • By using it

    where it is not needed • "Concurrency Semantics" Workshop tickets are still on sale! • By exporting concurrent APIs where it is not needed • By not thinking critically about it
  51. Extra links • Dave Cheney - Concurrency made easy •

    Dave Cheney - Practical Go: Real world advice for writing maintainable Go programs • Bob Nystrom - What Color is Your Function?