Slide 1

Slide 1 text

Go Sync or Go Home Advanced Concurrency Techniques for Better Go Programs @yardenlaif

Slide 2

Slide 2 text

2 Who Am I? β€’ Software Engineer at Rookout β€’ Go, Java, Ruby β€’ C#, Python, JavaScript, C++ β€’ Amateur sewist

Slide 3

Slide 3 text

3 Go + concurrency = 🩡

Slide 4

Slide 4 text

sync & x/sync ● sync - standard library ● x/sync - go get golang.org/x/sync 4

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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)

Slide 7

Slide 7 text

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() }

Slide 8

Slide 8 text

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() }

Slide 9

Slide 9 text

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() }

Slide 10

Slide 10 text

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() }

Slide 11

Slide 11 text

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() }

Slide 12

Slide 12 text

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() }

Slide 13

Slide 13 text

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() }

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

15 WaitGroup ErrGroup Add(int) Done() Go(f func() error) Wait() Wait() error

Slide 16

Slide 16 text

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()

Slide 17

Slide 17 text

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()

Slide 18

Slide 18 text

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()

Slide 19

Slide 19 text

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() }

Slide 20

Slide 20 text

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() }

Slide 21

Slide 21 text

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() }

Slide 22

Slide 22 text

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() }

Slide 23

Slide 23 text

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() }

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

ErrGroup - WithContext func WithContext(ctx context.Context) (*Group, context.Context) 25

Slide 26

Slide 26 text

ErrGroup - WithContext func WithContext(ctx context.Context) (*Group, context.Context) eg := errgroup.Group{} eg, _ := errgroup.WithContext(...) 26

Slide 27

Slide 27 text

ErrGroup - WithContext func WithContext(ctx context.Context) (*Group, context.Context) 27

Slide 28

Slide 28 text

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() }

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

30 SingleFlight

Slide 31

Slide 31 text

SingleFlight ● Imported by 1,215 projects ● The golang repo has its own internal copy of singleflight ● Duplicate function call suppression mechanism 31

Slide 32

Slide 32 text

Token Authenticator 32 type Token interface { AuthenticateToken(ctx context.Context, token string) (*Response, bool, error) } type Response struct { ... }

Slide 33

Slide 33 text

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) }

Slide 34

Slide 34 text

Cached Token Authenticator 34 type cta struct { authenticator Token cache cache } func (a *cta) AuthenticateToken(ctx context.Context, token string) (*Response, bool, error)

Slide 35

Slide 35 text

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 }

Slide 36

Slide 36 text

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 }

Slide 37

Slide 37 text

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 }

Slide 38

Slide 38 text

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 }

Slide 39

Slide 39 text

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 }

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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(...)

Slide 50

Slide 50 text

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 })

Slide 51

Slide 51 text

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 })

Slide 52

Slide 52 text

Cached Token Authenticator 52 type cta struct { authenticator Token group singleflight.Group cache cache }

Slide 53

Slide 53 text

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 }

Slide 54

Slide 54 text

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 }

Slide 55

Slide 55 text

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 }

Slide 56

Slide 56 text

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 }

Slide 57

Slide 57 text

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 }

Slide 58

Slide 58 text

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 }

Slide 59

Slide 59 text

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 }

Slide 60

Slide 60 text

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) }

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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() } }

Slide 65

Slide 65 text

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() } }

Slide 66

Slide 66 text

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() } }

Slide 67

Slide 67 text

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() } }

Slide 68

Slide 68 text

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() } }

Slide 69

Slide 69 text

SingleFlight Extras func (g *Group) Forget(key string) ● https://pkg.go.dev/tailscale.com/uti l/singleflight - Generics! 69

Slide 70

Slide 70 text

Cond ● Broadcast ● Signal ● Wait 70

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

Discovering Packages ● pkg.go.dev ● github.com ● https://pkg.go.dev/std ● https://pkg.go.dev/golang.org/x 72

Slide 73

Slide 73 text

73

Slide 74

Slide 74 text

Cached Token Authenticator? 74 ● https://github.com/kubernetes/apiserver/blob/d8bdd263c241c be9f5c1357541f622f48457207e/pkg/authentication/token/cach e/cached_token_authenticator.go

Slide 75

Slide 75 text

Thank You! @yardenlaif 75