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.

4618c5e97c59abd315cc2d7dc809f8c8?s=128

Alexey Palazhchenko

July 05, 2019
Tweet

Transcript

  1. None
  2. • Some guidelines are indeed strict

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

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

    • Use your own judgment
  5. • Some guidelines are indeed strict • Most are not

    • Use your own judgment • But learn from best practices
  6. Who am I

  7. Who am I • Go programmer since r60

  8. Who am I • Go programmer since r60 • m[k]

    = v, false
  9. Who am I • Go programmer since r60 • m[k]

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

    = v, false • Go meetups and conferences organizer • gophership.ru in 2 weeks
  11. 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
  12. 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
  13. How to Go Wrong with Concurrency Alexey Palazhchenko
 https://twitter.com/paaleksey

  14. Concurrency axioms and theorems

  15. Axioms

  16. Axioms • A send to a full channel blocks •

    A receive from an empty channel blocks
  17. 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
  18. 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
  19. 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.
  20. Theorems

  21. Theorems • Closing the channel is a send-like communication from

    the senders to the receivers that there will be no more sends.
  22. 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.
  23. 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.
  24. Theorems

  25. • Do not use the same channel for both sends

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

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

    and receives in the same place. • Except for toy ping-pong examples. • And semaphores. Theorems
  28. • 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!
  29. • 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
  30. Fun practice

  31. 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;
  32. Initial parser type Event struct { // … } type

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

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

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

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

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

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

    error } type Parser interface { Start(ctx context.Context, eventsCap int) Events() <-chan *Event }
  39. 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 }
  40. 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 }
  41. Using our perfect parser func collect(ch <-chan *Event) { for

    event := range ch { // ... } }
  42. Using our perfect (?) parser func collect(ch <-chan *Event) {

    for event := range ch { if fatal(event) { return } }
  43. 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 } } }
  44. Using our quite bad parser func collect(ch <-chan *Event) {

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

    { defer func() { /* ... */ }() for event := range ch { if fatal(event) { cancel() return } } }
  46. 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 } } }
  47. Can we do better?

  48. 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;
  49. What are we parsing anyway? • Two parts: block reader

    and block parser
  50. What are we parsing anyway? • Two parts: block reader

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

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

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

    want to block the main thread" • There is no "main thread"
  54. 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"
  55. 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
  56. 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?
  57. The best API • The best exposed APIs are synchronous,


    hard to misuse, and boring
  58. The best API • The best exposed APIs are synchronous,


    hard to misuse, and boring • Let caller decide if concurrency is desired
  59. Boring practice

  60. Reader type Reader interface { NextBlock() ([]string, error) Close() error

    }
  61. Parser

  62. Parser func Parse(block []string) (*Event, error) { // ... }

  63. Stateful parser type Parser interface { NextEvent() (*Event, error) Close()

    error }
  64. Real parser // NextEvent and Close can be called concurrently.

    type Parser interface { NextEvent() (*Event, error) Close() error }
  65. 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 }
  66. How to go wrong with concurrency? • By using it

    where it is not needed
  67. How to go wrong with concurrency? • By using it

    where it is not needed • "Concurrency Semantics" Workshop tickets are still on sale!
  68. 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
  69. Are there exceptions?

  70. Are there exceptions? • There are always exceptions

  71. Are there exceptions? • There are always exceptions • When

    there are already async APIs
  72. Are there exceptions? • There are always exceptions • When

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

    there are already async APIs • Avoid the worst variant • When you observe async events
  74. 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
  75. 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
  76. 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
  77. Thank you Alexey Palazhchenko
 https://twitter.com/paaleksey

  78. 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?