├── .gitignore ├── README.md └── concurrency_building_blocks ├── 3_cond ├── README.md ├── 2_cond_broadcast_experiment.go ├── 1_cond_signal_example.go ├── 2_cond_broadcast_example.go └── 1_cond_signal_experiment.go ├── 5_pool └── 1_pool_example.go ├── 4_once ├── 1_once_example_3.go ├── 1_once_example.go └── 1_once_example_2.go ├── 6_orchannel ├── 1_or_channel.go └── 2_fanout_example.go ├── 1_waitgroup └── 1_waitgroup.go └── 2_mutexes ├── 1_mutex_example.go └── 2_rwmutex_example.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .idea/ 3 | .vscode 4 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Concurrency in Go 2 | 3 | This repository includes the source code for examples from the Concurrency in Go book by O'Reilly 4 | -------------------------------------------------------------------------------- /concurrency_building_blocks/3_cond/README.md: -------------------------------------------------------------------------------- 1 | # sync.Cond 2 | 3 | These examples cover the `sync.Cond` package. 4 | 5 | Some good examples with real-world applications can be found in the following GopherCon 2018 talk by Bryan Mills. 6 | - [Bryan C. Mills - Rethinking Classical Concurrency Patterns](https://youtu.be/5zXAHh5tJqQ?t=691) -------------------------------------------------------------------------------- /concurrency_building_blocks/5_pool/1_pool_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | myPool := &sync.Pool{ 10 | New: func() interface{} { 11 | fmt.Println("Creating new instance.") 12 | return struct{}{} 13 | }, 14 | } 15 | 16 | myPool.Get() 17 | instance := myPool.Get() 18 | myPool.Put(instance) 19 | myPool.Get() 20 | } 21 | -------------------------------------------------------------------------------- /concurrency_building_blocks/4_once/1_once_example_3.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | func main() { 6 | var onceA, onceB sync.Once 7 | var initB func() 8 | initA := func() { onceB.Do(initB) } 9 | initB = func() { onceA.Do(initA) } // 1 10 | onceA.Do(initA) // 2 11 | /* 12 | This deadlocks. Why? Because the call to Do at 1 won't proceed until the call 13 | to Do at 2 exits. 14 | */ 15 | } 16 | -------------------------------------------------------------------------------- /concurrency_building_blocks/6_orchannel/1_or_channel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | 5 | //or := func(channels ...<-chan interface{}) <-chan interface{} { 6 | // switch len(channels) { 7 | // case 0: 8 | // return nil 9 | // case 1: 10 | // return channels[0] 11 | // } 12 | // 13 | // orDone := make(chan interface{}) 14 | // go func() { 15 | // defer close(orDone) 16 | // 17 | // switch len(channels) { 18 | // 19 | // } 20 | // }() 21 | //} 22 | 23 | } 24 | -------------------------------------------------------------------------------- /concurrency_building_blocks/4_once/1_once_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var count int 10 | 11 | increment := func() { 12 | count++ 13 | } 14 | var once sync.Once 15 | 16 | var increments sync.WaitGroup 17 | increments.Add(100) 18 | for i := 0; i < 100; i++ { 19 | go func() { 20 | defer increments.Done() 21 | once.Do(increment) 22 | }() 23 | } 24 | 25 | increments.Wait() 26 | fmt.Printf("Count is %d\n", count) 27 | } 28 | -------------------------------------------------------------------------------- /concurrency_building_blocks/4_once/1_once_example_2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var count int 10 | increment := func() { count++ } 11 | decrement := func() { count-- } 12 | 13 | var once sync.Once 14 | once.Do(increment) 15 | once.Do(decrement) 16 | 17 | fmt.Printf("Count: %d\n", count) 18 | /* 19 | Prints Count is 1, this is because sync.Once only counts the number of times 20 | Do is called, not how many times unique functions passed are called. 21 | */ 22 | } 23 | -------------------------------------------------------------------------------- /concurrency_building_blocks/3_cond/2_cond_broadcast_experiment.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | 11 | c := sync.NewCond(&sync.Mutex{}) 12 | var wg sync.WaitGroup 13 | 14 | fn := func(i int) { 15 | defer wg.Done() 16 | 17 | c.L.Lock() 18 | time.Sleep(time.Duration(i) * time.Second) 19 | fmt.Printf("Go %d...", i) 20 | c.L.Unlock() 21 | } 22 | 23 | for i := 0; i < 5; i++ { 24 | wg.Add(1) 25 | go fn(i) 26 | } 27 | 28 | c.Broadcast() 29 | 30 | wg.Wait() 31 | fmt.Println("Done") 32 | 33 | } 34 | -------------------------------------------------------------------------------- /concurrency_building_blocks/1_waitgroup/1_waitgroup.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | 11 | // WaitGroups are for fanning in 12 | var wg sync.WaitGroup 13 | 14 | wg.Add(1) 15 | go func() { 16 | defer wg.Done() // Decrement wg counter by one 17 | fmt.Println("1st goroutine sleeping...") 18 | time.Sleep(1) 19 | }() 20 | 21 | wg.Add(1) 22 | go func() { 23 | defer wg.Done() 24 | fmt.Println("2nd goroutine sleeping...") 25 | time.Sleep(2) 26 | }() 27 | 28 | wg.Wait() // Blocks until all goroutines have completed and the `wg` is zero. 29 | 30 | fmt.Println("All goroutines complete.") 31 | } 32 | -------------------------------------------------------------------------------- /concurrency_building_blocks/3_cond/1_cond_signal_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | cond := sync.NewCond(&sync.Mutex{}) 11 | queue := make([]interface{}, 0, 10) 12 | 13 | removeFromQueue := func(delay time.Duration) { 14 | time.Sleep(delay) 15 | cond.L.Lock() 16 | queue = queue[1:] 17 | fmt.Println("Removed from queue") 18 | cond.L.Unlock() 19 | cond.Signal() 20 | } 21 | 22 | for i := 0; i < 10; i++ { 23 | cond.L.Lock() 24 | for len(queue) == 2 { 25 | fmt.Println("Waiting...") 26 | cond.Wait() // wait atomically unlocks cond.L and suspends the goroutine 27 | } 28 | fmt.Println("Adding to queue") 29 | queue = append(queue, struct{}{}) 30 | go removeFromQueue(1 * time.Second) 31 | cond.L.Unlock() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /concurrency_building_blocks/2_mutexes/1_mutex_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var count int 10 | var lock sync.Mutex 11 | 12 | increment := func() { 13 | lock.Lock() 14 | defer lock.Unlock() 15 | count++ 16 | fmt.Printf("Incrementing: %d\n", count) 17 | } 18 | 19 | decrement := func() { 20 | lock.Lock() 21 | defer lock.Unlock() 22 | count-- 23 | fmt.Printf("Decrementing: %d\n", count) 24 | } 25 | 26 | var arithmetic sync.WaitGroup 27 | 28 | // Increment 29 | for i := 0; i <= 5; i++ { 30 | arithmetic.Add(1) 31 | go func() { 32 | defer arithmetic.Done() 33 | increment() 34 | }() 35 | } 36 | 37 | // Decrement 38 | for i := 0; i <= 5; i++ { 39 | arithmetic.Add(1) 40 | go func() { 41 | defer arithmetic.Done() 42 | decrement() 43 | }() 44 | } 45 | 46 | arithmetic.Wait() 47 | fmt.Println("Arithmetic complete.") 48 | } 49 | -------------------------------------------------------------------------------- /concurrency_building_blocks/3_cond/2_cond_broadcast_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | type Button struct { 9 | Clicked *sync.Cond 10 | } 11 | 12 | func main() { 13 | 14 | button := Button{Clicked: sync.NewCond(&sync.Mutex{})} 15 | 16 | subscribe := func(c *sync.Cond, fn func()) { 17 | var goroutineRunning sync.WaitGroup 18 | goroutineRunning.Add(1) 19 | 20 | go func() { 21 | goroutineRunning.Done() 22 | c.L.Lock() 23 | defer c.L.Unlock() 24 | c.Wait() 25 | fn() 26 | }() 27 | 28 | goroutineRunning.Wait() 29 | } 30 | 31 | var clickRegistered sync.WaitGroup 32 | clickRegistered.Add(3) 33 | 34 | subscribe(button.Clicked, func() { 35 | fmt.Println("Maximising window.") 36 | clickRegistered.Done() 37 | }) 38 | subscribe(button.Clicked, func() { 39 | fmt.Println("Displaying annoying dialog box!") 40 | clickRegistered.Done() 41 | }) 42 | subscribe(button.Clicked, func() { 43 | fmt.Println("Mouse clicked.") 44 | clickRegistered.Done() 45 | }) 46 | 47 | button.Clicked.Broadcast() 48 | clickRegistered.Wait() 49 | 50 | } 51 | -------------------------------------------------------------------------------- /concurrency_building_blocks/2_mutexes/2_rwmutex_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "os" 7 | "sync" 8 | "text/tabwriter" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | producer := func(wg *sync.WaitGroup, l sync.Locker) { 14 | defer wg.Done() 15 | for i := 5; i > 0; i-- { 16 | l.Lock() 17 | l.Unlock() 18 | time.Sleep(1) 19 | } 20 | } 21 | 22 | observer := func(wg *sync.WaitGroup, l sync.Locker) { 23 | defer wg.Done() 24 | l.Lock() 25 | defer l.Unlock() 26 | } 27 | 28 | test := func(count int, mutex, rwMutex sync.Locker) time.Duration { 29 | var wg sync.WaitGroup 30 | wg.Add(count + 1) 31 | beginTestTime := time.Now() 32 | go producer(&wg, mutex) 33 | for i := count; i > 0; i-- { 34 | go observer(&wg, mutex) 35 | } 36 | 37 | wg.Wait() 38 | return time.Since(beginTestTime) 39 | } 40 | 41 | tw := tabwriter.NewWriter(os.Stdout, 0, 1, 2, ' ', 0) 42 | defer tw.Flush() 43 | var m sync.RWMutex 44 | fmt.Fprintf(tw, "Readers\tRWMutex\tMutex\n") 45 | for i := 0; i < 20; i++ { 46 | count := int(math.Pow(2, float64(i))) 47 | fmt.Fprintf( 48 | tw, 49 | "%d\t%v\t%v\n", 50 | count, 51 | test(count, &m, m.RLocker()), 52 | test(count, &m, &m)) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /concurrency_building_blocks/6_orchannel/2_fanout_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | type Result struct { 10 | response *http.Response 11 | err error 12 | url string 13 | } 14 | 15 | func main() { 16 | // https://stackoverflow.com/questions/30526946/time-http-response-in-go 17 | 18 | urls := []string{"https://stackoverflow.com", "https://google.co.uk", "https://bbc.co.uk"} 19 | 20 | done := make(chan struct{}) 21 | resCount := 0 22 | 23 | for result := range checkStatus(done, urls...) { 24 | resCount++ 25 | if result.err != nil { 26 | fmt.Printf("Error: %v", result.err) 27 | continue 28 | } 29 | fmt.Printf("Response: %v = %v\n", result.url, result.response.Status) 30 | if resCount == len(urls) { 31 | done <- struct{}{} 32 | break 33 | } 34 | } 35 | 36 | // done <- struct{}{} 37 | // Can you use context instead of done? Can you share it between channels 38 | 39 | fmt.Printf("Finished!") 40 | } 41 | 42 | func checkStatus(done chan struct{}, urls ...string) <-chan Result { 43 | results := make(chan Result) 44 | 45 | for _, v := range urls { 46 | go func(done <-chan struct{}, url string) { 47 | // Heavy work is already done here, it should be moved into case after done. 48 | time.Sleep(2 * time.Second) 49 | res, err := http.Get(url) 50 | r := Result{response: res, err: err, url: url} 51 | 52 | select { 53 | case <-done: 54 | return 55 | case results <- r: 56 | } 57 | }(done, v) 58 | } 59 | 60 | return results 61 | } 62 | -------------------------------------------------------------------------------- /concurrency_building_blocks/3_cond/1_cond_signal_experiment.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type Queue struct { 10 | mu sync.Mutex 11 | items []Item 12 | itemAdded sync.Cond 13 | } 14 | 15 | type Item struct { 16 | Value string 17 | } 18 | 19 | func newQueue() *Queue { 20 | q := new(Queue) 21 | q.itemAdded.L = &q.mu 22 | return q 23 | } 24 | 25 | func (q *Queue) Get() Item { 26 | q.mu.Lock() 27 | defer q.mu.Unlock() 28 | for len(q.items) == 0 { 29 | fmt.Println("Get() - waiting for signal...") 30 | q.itemAdded.Wait() 31 | } 32 | item := q.items[0] 33 | q.items = q.items[1:] 34 | return item 35 | } 36 | 37 | func (q *Queue) Put(item Item) { 38 | q.mu.Lock() 39 | defer q.mu.Unlock() 40 | q.items = append(q.items, item) 41 | fmt.Println("Item added, signalling one gorountine (if it exists)") 42 | q.itemAdded.Signal() // Locks mutex then wakes goroutine 43 | } 44 | 45 | func main() { 46 | var wg sync.WaitGroup 47 | q := newQueue() 48 | 49 | wg.Add(2) 50 | go func() { 51 | fmt.Println("Launching a goroutine to get from queue") 52 | defer wg.Done() 53 | item := q.Get() 54 | fmt.Printf("Hello %s 1\n", item.Value) 55 | }() 56 | go func() { 57 | fmt.Println("Launching a goroutine to get from queue") 58 | defer wg.Done() 59 | item := q.Get() 60 | fmt.Printf("Hello %s 2\n", item.Value) 61 | }() 62 | 63 | // A bit of sleep so we can see things happening 64 | time.Sleep(2 * time.Second) 65 | q.Put(Item{"World"}) // Wakes goroutine 1 66 | 67 | time.Sleep(2 * time.Second) 68 | q.Put(Item{"World"}) // Wakes goroutines 2 69 | 70 | wg.Wait() 71 | } 72 | --------------------------------------------------------------------------------