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

Go Sync or Go Home: Advanced Concurrency Techni...

Go Sync or Go Home: Advanced Concurrency Techniques for Better Go Programs

Yarden Laifenfeld

September 06, 2023
Tweet

More Decks by Yarden Laifenfeld

Other Decks in Programming

Transcript

  1. 2 Who Am I? • Software Engineer at Rookout •

    Go, Java, Ruby • C#, Python, JavaScript, C++ • Amateur sewist
  2. Features • Mutex - Mutual exclusion lock • Once -

    Run an action once only • Pool - Memory pool to save on allocations • RWMutex - Lock that allows several concurrent reads but only one write • WaitGroup - Wait for a group of goroutines to finish • Semaphore - Weighted lock • Map - Map implementation that is safe for concurrent use 5
  3. File Transfer App 6 • Transfer each file to multiple

    destinations • Given: type File struct { ... } type Conn interface { Send(context.Context, *File) error } • Implement: func TransferFile(ctx context.Context, conns []Conn, file *File)
  4. File Transfer App - WaitGroup 7 func TransferFile(ctx context.Context, conns

    []Conn, file *File) { wg := sync.WaitGroup{} for _, conn := range conns { wg.Add(1) go func(c Conn) { defer wg.Done() c.Send(ctx, file) }(conn) } wg.Wait() }
  5. File Transfer App - WaitGroup 8 func TransferFile(ctx context.Context, conns

    []Conn, file *File) { wg := sync.WaitGroup{} for _, conn := range conns { wg.Add(1) go func(c Conn) { defer wg.Done() c.Send(ctx, file) }(conn) } wg.Wait() }
  6. File Transfer App - WaitGroup 9 func TransferFile(ctx context.Context, conns

    []Conn, file *File) { wg := sync.WaitGroup{} for _, conn := range conns { wg.Add(1) go func(c Conn) { defer wg.Done() c.Send(ctx, file) }(conn) } wg.Wait() }
  7. File Transfer App - WaitGroup 10 func TransferFile(ctx context.Context, conns

    []Conn, file *File) { wg := sync.WaitGroup{} for _, conn := range conns { wg.Add(1) go func(c Conn) { defer wg.Done() c.Send(ctx, file) }(conn) } wg.Wait() }
  8. File Transfer App - WaitGroup 11 func TransferFile(ctx context.Context, conns

    []Conn, file *File) { wg := sync.WaitGroup{} for _, conn := range conns { wg.Add(1) go func(c Conn) { defer wg.Done() c.Send(ctx, file) }(conn) } wg.Wait() }
  9. File Transfer App - WaitGroup 12 func TransferFile(ctx context.Context, conns

    []Conn, file *File) { wg := sync.WaitGroup{} for _, conn := range conns { wg.Add(1) go func(c Conn) { defer wg.Done() c.Send(ctx, file) }(conn) } wg.Wait() }
  10. File Transfer App - WaitGroup 13 func TransferFile(ctx context.Context, conns

    []Conn, file *File) { wg := sync.WaitGroup{} for _, conn := range conns { wg.Add(1) go func(c Conn) { defer wg.Done() c.Send(ctx, file) }(conn) } wg.Wait() }
  11. File Transfer App - WaitGroup 14 func TransferFile(ctx context.Context, conns

    []Conn, file *File) { wg := sync.WaitGroup{} for _, conn := range conns { wg.Add(1) go func(c Conn) { defer wg.Done() c.Send(ctx, file) }(conn) } wg.Wait() } Send(context.Context, *File) error
  12. 16 WaitGroup ErrGroup wg := sync.WaitGroup{} wg.Add(n) for i :=

    0; i < n; i++ { go func() { defer wg.Done() task() }() } wg.Wait() eg := errgroup.Group{} for i := 0; i < n; i++ { eg.Go(task) } err := eg.Wait()
  13. 17 WaitGroup ErrGroup wg := sync.WaitGroup{} wg.Add(n) for i :=

    0; i < n; i++ { go func() { defer wg.Done() task() }() } wg.Wait() eg := errgroup.Group{} for i := 0; i < n; i++ { eg.Go(task) } err := eg.Wait()
  14. 18 WaitGroup ErrGroup wg := sync.WaitGroup{} wg.Add(n) for i :=

    0; i < n; i++ { go func() { defer wg.Done() task() }() } wg.Wait() eg := errgroup.Group{} for i := 0; i < n; i++ { eg.Go(task) } err := eg.Wait()
  15. File Transfer App - ErrGroup 19 func TransferFile(ctx context.Context, conns

    []Conn, file *File) error { eg := errgroup.Group{} for _, conn := range conns { func(c Conn) { eg.Go(func() error { return c.Send(ctx, file) }) }(conn) } return eg.Wait() }
  16. File Transfer App - ErrGroup 20 func TransferFile(ctx context.Context, conns

    []Conn, file *File) error { eg := errgroup.Group{} for _, conn := range conns { func(c Conn) { eg.Go(func() error { return c.Send(ctx, file) }) }(conn) } return eg.Wait() }
  17. File Transfer App - ErrGroup 21 func TransferFile(ctx context.Context, conns

    []Conn, file *File) error { eg := errgroup.Group{} for _, conn := range conns { func(c Conn) { eg.Go(func() error { return c.Send(ctx, file) }) }(conn) } return eg.Wait() }
  18. File Transfer App - ErrGroup 22 func TransferFile(ctx context.Context, conns

    []Conn, file *File) error { eg := errgroup.Group{} for _, conn := range conns { func(c Conn) { eg.Go(func() error { return c.Send(ctx, file) }) }(conn) } return eg.Wait() }
  19. File Transfer App - ErrGroup 23 func TransferFile(ctx context.Context, conns

    []Conn, file *File) error { eg := errgroup.Group{} for _, conn := range conns { func(c Conn) { eg.Go(func() error { return c.Send(ctx, file) }) }(conn) } return eg.Wait() }
  20. File Transfer App - ErrGroup 24 func TransferFile(ctx context.Context, conns

    []Conn, file *File) error { eg := errgroup.Group{} for _, conn := range conns { func(c Conn) { eg.Go(func() error { return c.Send(ctx, file) }) }(conn) } return eg.Wait() } Send(context.Context, *File) error
  21. File Transfer App - ErrGroup 28 func TransferFile(ctx context.Context, conns

    []Conn, file *File) error { eg, egCtx := errgroup.WithContext(ctx) for _, conn := range conns { func(c Conn) { eg.Go(func() error { return c.Send(egCtx, file) }) }(conn) } return eg.Wait() }
  22. ErrGroup - SetLimit • SetLimit(n int) Set maximum number of

    goroutines running at once • TryGo(f func() error) bool Try to run task in new goroutine if limit hasn’t been reached • Go(f func() error) Block until able to run goroutine if limit has been reached 29
  23. SingleFlight • Imported by 1,215 projects • The golang repo

    has its own internal copy of singleflight • Duplicate function call suppression mechanism 31
  24. Token Authenticator 32 type Token interface { AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) } type Response struct { ... }
  25. Token Authenticator Cache 33 type cacheRecord struct { resp *Response

    ok bool err error } type cache interface { get(key string) (value *cacheRecord, exists bool) set(key string, value *cacheRecord, ttl time.Duration) remove(key string) }
  26. Cached Token Authenticator 34 type cta struct { authenticator Token

    cache cache } func (a *cta) AuthenticateToken(ctx context.Context, token string) (*Response, bool, error)
  27. Cached Token Authenticator 35 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) { if record, exists := a.cache.get(token); exists { return record.resp, record.ok, record.err } resp, ok, err := a.authenticator.AuthenticateToken(ctx, token) record := &cacheRecord{resp, ok, err} a.cache.set(token, record, defaultTTL) return resp, ok, err }
  28. Cached Token Authenticator 36 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) { if record, exists := a.cache.get(token); exists { return record.resp, record.ok, record.err } resp, ok, err := a.authenticator.AuthenticateToken(ctx, token) record := &cacheRecord{resp, ok, err} a.cache.set(token, record, defaultTTL) return resp, ok, err }
  29. Cached Token Authenticator 37 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) { if record, exists := a.cache.get(token); exists { return record.resp, record.ok, record.err } resp, ok, err := a.authenticator.AuthenticateToken(ctx, token) record := &cacheRecord{resp, ok, err} a.cache.set(token, record, defaultTTL) return resp, ok, err }
  30. Cached Token Authenticator 38 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) { if record, exists := a.cache.get(token); exists { return record.resp, record.ok, record.err } resp, ok, err := a.authenticator.AuthenticateToken(ctx, token) record := &cacheRecord{resp, ok, err} a.cache.set(token, record, defaultTTL) return resp, ok, err }
  31. Cached Token Authenticator 39 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) { if record, exists := a.cache.get(token); exists { return record.resp, record.ok, record.err } resp, ok, err := a.authenticator.AuthenticateToken(ctx, token) record := &cacheRecord{resp, ok, err} a.cache.set(token, record, defaultTTL) return resp, ok, err }
  32. Cached Token Authenticator 40 func authTwice(a *cta, token string) {

    go a.AuthenticateToken(token) go a.AuthenticateToken(token) } • Check cache • Authenticate • Add to cache • Return response
  33. Cached Token Authenticator 41 func authTwice(a *cta, token string) {

    go a.AuthenticateToken(token) go a.AuthenticateToken(token) } • Check cache • Authenticate • Add to cache • Return response 1 2
  34. Cached Token Authenticator 42 func authTwice(a *cta, token string) {

    go a.AuthenticateToken(token) go a.AuthenticateToken(token) } • Check cache • Authenticate • Add to cache • Return response 1 2 1
  35. Cached Token Authenticator 43 func authTwice(a *cta, token string) {

    go a.AuthenticateToken(token) go a.AuthenticateToken(token) } • Check cache • Authenticate • Add to cache • Return response 1 2 1
  36. Cached Token Authenticator 44 func authTwice(a *cta, token string) {

    go a.AuthenticateToken(token) go a.AuthenticateToken(token) } • Check cache • Authenticate • Add to cache • Return response 1 2 1 2
  37. Cached Token Authenticator 45 func authTwice(a *cta, token string) {

    go a.AuthenticateToken(token) go a.AuthenticateToken(token) } • Check cache • Authenticate • Add to cache • Return response 1 2 1 2
  38. Cached Token Authenticator 46 func authTwice(a *cta, token string) {

    go a.AuthenticateToken(token) go a.AuthenticateToken(token) } • Check cache • Authenticate • Add to cache • Return response 1 2 1 2
  39. Cached Token Authenticator 47 func authTwice(a *cta, token string) {

    go a.AuthenticateToken(token) go a.AuthenticateToken(token) } • Check cache • Authenticate • Add to cache • Return response 1 2 1 2
  40. SingleFlight - `Do` Method func (g *Group) Do(key string, fn

    func() (interface{}, error)) (v interface{}, err error, shared bool) 48
  41. SingleFlight - `Do` Method func (g *Group) Do(key string, fn

    func() (interface{}, error)) (v interface{}, err error, shared bool) 49 g := singleflight.Group{} g.Do(...)
  42. SingleFlight - `Do` Method func (g *Group) Do(key string, fn

    func() (interface{}, error)) (v interface{}, err error, shared bool) 50 g := singleflight.Group{} g.Do(key, func() (interface{}, error) { value, err := getValue(key) return value, err })
  43. SingleFlight - `Do` Method func (g *Group) Do(key string, fn

    func() (interface{}, error)) (v interface{}, err error, shared bool) 51 g := singleflight.Group{} value, err, _ := g.Do(key, func() (interface{}, error) { value, err := getValue(key) return value, err })
  44. Cached Token Authenticator 53 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) { if record, exists := a.cache.get(token); exists { return record.resp, record.ok, record.err } record, _, _ := a.group.Do(token, func() (interface{}, error) { resp, ok, err := a.authenticator.AuthenticateToken(ctx, token) record := &cacheRecord{resp, ok, err} a.cache.set(token, record, defaultTTL) return record, nil }) realRecord := record.(*cacheRecord) return realRecord.resp, realRecord.ok, realRecord.err }
  45. Cached Token Authenticator 54 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) { if record, exists := a.cache.get(token); exists { return record.resp, record.ok, record.err } record, _, _ := a.group.Do(token, func() (interface{}, error) { resp, ok, err := a.authenticator.AuthenticateToken(ctx, token) record := &cacheRecord{resp, ok, err} a.cache.set(token, record, defaultTTL) return record, nil }) realRecord := record.(*cacheRecord) return realRecord.resp, realRecord.ok, realRecord.err }
  46. Cached Token Authenticator 55 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) { if record, exists := a.cache.get(token); exists { return record.resp, record.ok, record.err } record, _, _ := a.group.Do(token, func() (interface{}, error) { resp, ok, err := a.authenticator.AuthenticateToken(ctx, token) record := &cacheRecord{resp, ok, err} a.cache.set(token, record, defaultTTL) return record, nil }) realRecord := record.(*cacheRecord) return realRecord.resp, realRecord.ok, realRecord.err }
  47. Cached Token Authenticator 56 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) { if record, exists := a.cache.get(token); exists { return record.resp, record.ok, record.err } record, _, _ := a.group.Do(token, func() (interface{}, error) { resp, ok, err := a.authenticator.AuthenticateToken(ctx, token) record := &cacheRecord{resp, ok, err} a.cache.set(token, record, defaultTTL) return record, nil }) realRecord := record.(*cacheRecord) return realRecord.resp, realRecord.ok, realRecord.err }
  48. Cached Token Authenticator 57 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) { if record, exists := a.cache.get(token); exists { return record.resp, record.ok, record.err } record, _, _ := a.group.Do(token, func() (interface{}, error) { resp, ok, err := a.authenticator.AuthenticateToken(ctx, token) record := &cacheRecord{resp, ok, err} a.cache.set(token, record, defaultTTL) return record, nil }) realRecord := record.(*cacheRecord) return realRecord.resp, realRecord.ok, realRecord.err }
  49. Cached Token Authenticator 58 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) { if record, exists := a.cache.get(token); exists { return record.resp, record.ok, record.err } record, _, _ := a.group.Do(token, func() (interface{}, error) { resp, ok, err := a.authenticator.AuthenticateToken(ctx, token) record := &cacheRecord{resp, ok, err} a.cache.set(token, record, defaultTTL) return record, nil }) realRecord := record.(*cacheRecord) return realRecord.resp, realRecord.ok, realRecord.err }
  50. Cached Token Authenticator 59 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) { if record, exists := a.cache.get(token); exists { return record.resp, record.ok, record.err } record, _, _ := a.group.Do(token, func() (interface{}, error) { resp, ok, err := a.authenticator.AuthenticateToken(ctx, token) record := &cacheRecord{resp, ok, err} a.cache.set(token, record, defaultTTL) return record, nil }) realRecord := record.(*cacheRecord) return realRecord.resp, realRecord.ok, realRecord.err }
  51. Cached Token Authenticator 60 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) type Token interface { AuthenticateToken(ctx context.Context, token string) (*Response, bool, error) }
  52. SingleFlight - `DoChan` Method func (g *Group) Do(key string, fn

    func() (interface{}, error)) (v interface{}, err error, shared bool) func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result type Result struct { Val interface{} Err error Shared bool } 61
  53. SingleFlight - `DoChan` Method func (g *Group) Do(key string, fn

    func() (interface{}, error)) (v interface{}, err error, shared bool) func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result type Result struct { Val interface{} Err error Shared bool } 62
  54. SingleFlight - `DoChan` Method func (g *Group) Do(key string, fn

    func() (interface{}, error)) (v interface{}, err error, shared bool) func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result type Result struct { Val interface{} Err error Shared bool } 63
  55. Cached Token Authenticator 64 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) { // Check cache c := a.group.DoChan(token, func() (interface{}, error) { // Authenticate and set cache }) select { case result := <-c: record := result.Val.(*cacheRecord) return record.resp, record.ok, record.err case <-ctx.Done(): return nil, false, ctx.Err() } }
  56. Cached Token Authenticator 65 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) { // Check cache c := a.group.DoChan(token, func() (interface{}, error) { // Authenticate and set cache }) select { case result := <-c: record := result.Val.(*cacheRecord) return record.resp, record.ok, record.err case <-ctx.Done(): return nil, false, ctx.Err() } }
  57. Cached Token Authenticator 66 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) { // Check cache c := a.group.DoChan(token, func() (interface{}, error) { // Authenticate and set cache }) select { case result := <-c: record := result.Val.(*cacheRecord) return record.resp, record.ok, record.err case <-ctx.Done(): return nil, false, ctx.Err() } }
  58. Cached Token Authenticator 67 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) { // Check cache c := a.group.DoChan(token, func() (interface{}, error) { // Authenticate and set cache }) select { case result := <-c: record := result.Val.(*cacheRecord) return record.resp, record.ok, record.err case <-ctx.Done(): return nil, false, ctx.Err() } }
  59. Cached Token Authenticator 68 func (a *cta) AuthenticateToken(ctx context.Context, token

    string) (*Response, bool, error) { // Check cache c := a.group.DoChan(token, func() (interface{}, error) { // Authenticate and set cache }) select { case result := <-c: record := result.Val.(*cacheRecord) return record.resp, record.ok, record.err case <-ctx.Done(): return nil, false, ctx.Err() } }
  60. Cond • Broadcast • Signal • Wait • For many

    simple use cases, users will be better off using channels than a Cond (Broadcast corresponds to closing a channel, and Signal corresponds to sending on a channel). • For more on replacements for sync.Cond, see Roberto Clapis's series on advanced concurrency patterns, as well as Bryan Mills's talk on concurrency patterns 71
  61. 73