├── LICENSE ├── README.org ├── an-introduction-to-concurrency └── why-is-concurrency-hard │ ├── deadlocks-livelocks-and-starvation │ ├── livelock │ │ └── fig-livelock-hallway.go │ └── starvation │ │ └── fig-example.go │ ├── memory-access-synchronization │ └── fig-basic-memory-access-sync.go │ └── race-conditions │ └── fig-basic-race-condition.go ├── concurrency-at-scale ├── error-propagation │ ├── fig-error-propagation-corrected.go │ └── fig-error-propagation.go ├── healing-unhealthy-goroutines │ ├── fig-example-steward-usage.go │ └── fig-more-complicated-ward.go ├── heartbeats │ ├── bad_concurrent_test.go │ ├── concurrent_test.go │ ├── dowork_test.go │ ├── fig-interval-heartbeat-misbehaving-goroutine.go │ ├── fig-interval-heartbeat.go │ ├── fig-work-unit-pulse.go │ └── interval_concurrent_test.go └── rate-limiting │ ├── fig-multi-rate-limit.go │ ├── fig-no-rate-limit.go │ ├── fig-simple-rate-limit.go │ └── fig-tiered-rate-limit.go ├── concurrency-patterns-in-go ├── confinement │ ├── fig-confinement-ad-hoc.go │ ├── fig-confinement-ownership.go │ └── fig-confinement-structs.go ├── error-handling │ ├── fig-patterns-imporoper-err-handling.go │ ├── fig-patterns-proper-err-handling.go │ └── fig-stop-after-three-errors.go ├── fan-out-fan-in │ ├── fig-fan-out-naive-prime-finder.go │ └── fig-naive-prime-finder.go ├── pipelines │ ├── best-practices-for-constructing-pipelines │ │ └── fig-pipelines-chan-stream-processing.go │ ├── fig-adding-additional-stage-to-pipeline.go │ ├── fig-functional-pipeline-combination.go │ ├── fig-pipelines-func-stream-processing.go │ └── some-handy-generators │ │ ├── fig-take-and-repeat-pipeline.go │ │ ├── fig-utilizing-string-stage.go │ │ └── pipelines_test.go ├── preventing-goroutine-leaks │ ├── fig-goroutine-leaks-cancellation.go │ ├── fig-goroutine-leaks-example.go │ ├── fig-leak-from-blocked-channel-write-solved.go │ └── fig-leak-from-blocked-channel-write.go ├── queuing │ └── buffering_test.go ├── the-bridge-channel │ └── fig-bridge-channel.go ├── the-context-package │ ├── fig-greeter-with-context.go │ └── fig-greeter-with-done-chan.go └── the-or-channel │ └── fig-or-channel.go ├── gos-concurrency-building-blocks ├── channels │ ├── fig-chan-ownership.go │ ├── fig-chan-recv-multi-value.go │ ├── fig-iterating-over-channel.go │ ├── fig-reading-from-closed-channel.go │ ├── fig-simple-chan.go │ ├── fig-unblocking-goroutines.go │ └── fig-using-buffered-chans.go ├── goroutines │ ├── fig-ctx-switch_test.go │ ├── fig-goroutine-closure-loop-correct.go │ ├── fig-goroutine-closure-loop.go │ ├── fig-goroutine-closure.go │ ├── fig-goroutine-size.go │ └── fig-join-point.go ├── the-defer-statement │ └── fig-defer-before-panic.go ├── the-select-statement │ ├── fig-select-blocking.go │ ├── fig-select-default-clause.go │ ├── fig-select-for-select-default.go │ ├── fig-select-timeouts.go │ └── fig-select-uniform-distribution.go └── the-sync-package │ ├── cond │ ├── fig-cond-based-queue.go │ └── fig-cond-broadcast.go │ ├── mutex-&-rwmutex │ ├── fig-mutex.go │ └── fig-rwlock.go │ ├── once │ ├── fig-sync-once-diff-funcs.go │ ├── fig-sync-once-do-deadlock.go │ ├── fig-sync-once.go │ └── sync-once-go-codebase.sh │ ├── pool │ ├── fig-benchmark-fast-network-service_test.go │ ├── fig-benchmark-slow-network-service_test.go │ ├── fig-sync-pool-basic.go │ └── fig-sync-pool.go │ └── waitgroup │ ├── fig-bulk-add.go │ └── fig-wait-group.go └── notes └── dead-writing └── livelocks ├── livelock-example-fix.go └── livelock-example.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Katherine Cox-Buday 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | This is the full sourcecode for the book, "Concurrency in Go" published by O'Reilly. 2 | 3 | For errata and more information, please refer to the book's hub at [[http://katherine.cox-buday.com/concurrency-in-go]]. 4 | -------------------------------------------------------------------------------- /an-introduction-to-concurrency/why-is-concurrency-hard/deadlocks-livelocks-and-starvation/livelock/fig-livelock-hallway.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sync" 7 | "sync/atomic" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | cadence := sync.NewCond(&sync.Mutex{}) 13 | go func() { 14 | for range time.Tick(1 * time.Millisecond) { 15 | cadence.Broadcast() 16 | } 17 | }() 18 | 19 | takeStep := func() { 20 | cadence.L.Lock() 21 | cadence.Wait() 22 | cadence.L.Unlock() 23 | } 24 | 25 | tryDir := func(dirName string, dir *int32, out *bytes.Buffer) bool { // <1> 26 | fmt.Fprintf(out, " %v", dirName) 27 | atomic.AddInt32(dir, 1) // <2> 28 | takeStep() // <3> 29 | if atomic.LoadInt32(dir) == 1 { 30 | fmt.Fprint(out, ". Success!") 31 | return true 32 | } 33 | takeStep() 34 | atomic.AddInt32(dir, -1) // <4> 35 | return false 36 | } 37 | 38 | var left, right int32 39 | tryLeft := func(out *bytes.Buffer) bool { return tryDir("left", &left, out) } 40 | tryRight := func(out *bytes.Buffer) bool { return tryDir("right", &right, out) } 41 | walk := func(walking *sync.WaitGroup, name string) { 42 | var out bytes.Buffer 43 | defer func() { fmt.Println(out.String()) }() 44 | defer walking.Done() 45 | fmt.Fprintf(&out, "%v is trying to scoot:", name) 46 | for i := 0; i < 5; i++ { // <1> 47 | if tryLeft(&out) || tryRight(&out) { // <2> 48 | return 49 | } 50 | } 51 | fmt.Fprintf(&out, "\n%v tosses her hands up in exasperation!", name) 52 | } 53 | 54 | var peopleInHallway sync.WaitGroup // <3> 55 | peopleInHallway.Add(2) 56 | go walk(&peopleInHallway, "Alice") 57 | go walk(&peopleInHallway, "Barbara") 58 | peopleInHallway.Wait() 59 | } 60 | -------------------------------------------------------------------------------- /an-introduction-to-concurrency/why-is-concurrency-hard/deadlocks-livelocks-and-starvation/starvation/fig-example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | var wg sync.WaitGroup 11 | var sharedLock sync.Mutex 12 | const runtime = 1 * time.Second 13 | 14 | greedyWorker := func() { 15 | defer wg.Done() 16 | 17 | var count int 18 | for begin := time.Now(); time.Since(begin) <= runtime; { 19 | sharedLock.Lock() 20 | time.Sleep(3 * time.Nanosecond) 21 | sharedLock.Unlock() 22 | count++ 23 | } 24 | 25 | fmt.Printf("Greedy worker was able to execute %v work loops\n", count) 26 | } 27 | 28 | politeWorker := func() { 29 | defer wg.Done() 30 | 31 | var count int 32 | for begin := time.Now(); time.Since(begin) <= runtime; { 33 | sharedLock.Lock() 34 | time.Sleep(1 * time.Nanosecond) 35 | sharedLock.Unlock() 36 | 37 | sharedLock.Lock() 38 | time.Sleep(1 * time.Nanosecond) 39 | sharedLock.Unlock() 40 | 41 | sharedLock.Lock() 42 | time.Sleep(1 * time.Nanosecond) 43 | sharedLock.Unlock() 44 | 45 | count++ 46 | } 47 | 48 | fmt.Printf("Polite worker was able to execute %v work loops.\n", count) 49 | } 50 | 51 | wg.Add(2) 52 | go greedyWorker() 53 | go politeWorker() 54 | 55 | wg.Wait() 56 | } 57 | -------------------------------------------------------------------------------- /an-introduction-to-concurrency/why-is-concurrency-hard/memory-access-synchronization/fig-basic-memory-access-sync.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var memoryAccess sync.Mutex // <1> 10 | var value int 11 | go func() { 12 | memoryAccess.Lock() // <2> 13 | value++ 14 | memoryAccess.Unlock() // <3> 15 | }() 16 | 17 | memoryAccess.Lock() // <4> 18 | if value == 0 { 19 | fmt.Printf("the value is %v.\n", value) 20 | } else { 21 | fmt.Printf("the value is %v.\n", value) 22 | } 23 | memoryAccess.Unlock() // <5> 24 | } 25 | -------------------------------------------------------------------------------- /an-introduction-to-concurrency/why-is-concurrency-hard/race-conditions/fig-basic-race-condition.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | var data int 9 | go func() { // <1> 10 | data++ 11 | }() 12 | if data == 0 { 13 | fmt.Printf("the value is %v.\n", data) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /concurrency-at-scale/error-propagation/fig-error-propagation-corrected.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "runtime/debug" 9 | ) 10 | 11 | type MyError struct { 12 | Inner error 13 | Message string 14 | StackTrace string 15 | Misc map[string]interface{} 16 | } 17 | 18 | func wrapError(err error, messagef string, msgArgs ...interface{}) MyError { 19 | return MyError{ 20 | Inner: err, //<1> 21 | Message: fmt.Sprintf(messagef, msgArgs...), 22 | StackTrace: string(debug.Stack()), // <2> 23 | Misc: make(map[string]interface{}), // <3> 24 | } 25 | } 26 | 27 | func (err MyError) Error() string { 28 | return err.Message 29 | } 30 | 31 | // "lowlevel" module 32 | 33 | type LowLevelErr struct { 34 | error 35 | } 36 | 37 | func isGloballyExec(path string) (bool, error) { 38 | info, err := os.Stat(path) 39 | if err != nil { 40 | return false, LowLevelErr{(wrapError(err, err.Error()))} // <1> 41 | } 42 | return info.Mode().Perm()&0100 == 0100, nil 43 | } 44 | 45 | // "intermediate" module 46 | 47 | type IntermediateErr struct { 48 | error 49 | } 50 | 51 | func runJob(id string) error { 52 | const jobBinPath = "/bad/job/binary" 53 | isExecutable, err := isGloballyExec(jobBinPath) 54 | if err != nil { 55 | return IntermediateErr{wrapError( 56 | err, 57 | "cannot run job %q: requisite binaries not available", 58 | id, 59 | )} // <1> 60 | } else if isExecutable == false { 61 | return wrapError( 62 | nil, 63 | "cannot run job %q: requisite binaries are not executable", 64 | id, 65 | ) 66 | } 67 | 68 | return exec.Command(jobBinPath, "--id="+id).Run() 69 | } 70 | 71 | func handleError(key int, err error, message string) { 72 | log.SetPrefix(fmt.Sprintf("[logID: %v]: ", key)) 73 | log.Printf("%#v", err) 74 | fmt.Printf("[%v] %v", key, message) 75 | } 76 | 77 | func main() { 78 | log.SetOutput(os.Stdout) 79 | log.SetFlags(log.Ltime | log.LUTC) 80 | 81 | err := runJob("1") 82 | if err != nil { 83 | msg := "There was an unexpected issue; please report this as a bug." 84 | if _, ok := err.(IntermediateErr); ok { 85 | msg = err.Error() 86 | } 87 | handleError(1, err, msg) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /concurrency-at-scale/error-propagation/fig-error-propagation.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "runtime/debug" 9 | ) 10 | 11 | type MyError struct { 12 | Inner error 13 | Message string 14 | StackTrace string 15 | Misc map[string]interface{} 16 | } 17 | 18 | func wrapError(err error, messagef string, msgArgs ...interface{}) MyError { 19 | return MyError{ 20 | Inner: err, //<1> 21 | Message: fmt.Sprintf(messagef, msgArgs...), 22 | StackTrace: string(debug.Stack()), // <2> 23 | Misc: make(map[string]interface{}), // <3> 24 | } 25 | } 26 | 27 | func (err MyError) Error() string { 28 | return err.Message 29 | } 30 | 31 | // "lowlevel" module 32 | 33 | type LowLevelErr struct { 34 | error 35 | } 36 | 37 | func isGloballyExec(path string) (bool, error) { 38 | info, err := os.Stat(path) 39 | if err != nil { 40 | return false, LowLevelErr{(wrapError(err, err.Error()))} // <1> 41 | } 42 | return info.Mode().Perm()&0100 == 0100, nil 43 | } 44 | 45 | // "intermediate" module 46 | 47 | type IntermediateErr struct { 48 | error 49 | } 50 | 51 | func runJob(id string) error { 52 | const jobBinPath = "/bad/job/binary" 53 | isExecutable, err := isGloballyExec(jobBinPath) 54 | if err != nil { 55 | return err // <1> 56 | } else if isExecutable == false { 57 | return wrapError(nil, "job binary is not executable") 58 | } 59 | 60 | return exec.Command(jobBinPath, "--id="+id).Run() // <1> 61 | } 62 | 63 | func handleError(key int, err error, message string) { 64 | log.SetPrefix(fmt.Sprintf("[logID: %v]: ", key)) 65 | log.Printf("%#v", err) // <3> 66 | fmt.Printf("[%v] %v", key, message) 67 | } 68 | 69 | func main() { 70 | log.SetOutput(os.Stdout) 71 | log.SetFlags(log.Ltime | log.LUTC) 72 | 73 | err := runJob("1") 74 | if err != nil { 75 | msg := "There was an unexpected issue; please report this as a bug." 76 | if _, ok := err.(IntermediateErr); ok { // <1> 77 | msg = err.Error() 78 | } 79 | handleError(1, err, msg) // <2> 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /concurrency-at-scale/healing-unhealthy-goroutines/fig-example-steward-usage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | var or func(channels ...<-chan interface{}) <-chan interface{} 11 | or = func(channels ...<-chan interface{}) <-chan interface{} { // <1> 12 | switch len(channels) { 13 | case 0: // <2> 14 | return nil 15 | case 1: // <3> 16 | return channels[0] 17 | } 18 | 19 | orDone := make(chan interface{}) 20 | go func() { // <4> 21 | defer close(orDone) 22 | 23 | switch len(channels) { 24 | case 2: // <5> 25 | select { 26 | case <-channels[0]: 27 | case <-channels[1]: 28 | } 29 | default: // <6> 30 | select { 31 | case <-channels[0]: 32 | case <-channels[1]: 33 | case <-channels[2]: 34 | case <-or(append(channels[3:], orDone)...): // <6> 35 | } 36 | } 37 | }() 38 | return orDone 39 | } 40 | type startGoroutineFn func( 41 | done <-chan interface{}, 42 | pulseInterval time.Duration, 43 | ) (heartbeat <-chan interface{}) // <1> 44 | 45 | newSteward := func( 46 | timeout time.Duration, 47 | startGoroutine startGoroutineFn, 48 | ) startGoroutineFn { // <2> 49 | return func( 50 | done <-chan interface{}, 51 | pulseInterval time.Duration, 52 | ) <-chan interface{} { 53 | heartbeat := make(chan interface{}) 54 | go func() { 55 | defer close(heartbeat) 56 | 57 | var wardDone chan interface{} 58 | var wardHeartbeat <-chan interface{} 59 | startWard := func() { // <3> 60 | wardDone = make(chan interface{}) // <4> 61 | wardHeartbeat = startGoroutine(or(wardDone, done), timeout/2) // <5> 62 | } 63 | startWard() 64 | pulse := time.Tick(pulseInterval) 65 | 66 | monitorLoop: 67 | for { 68 | timeoutSignal := time.After(timeout) 69 | 70 | for { // <6> 71 | select { 72 | case <-pulse: 73 | select { 74 | case heartbeat <- struct{}{}: 75 | default: 76 | } 77 | case <-wardHeartbeat: // <7> 78 | continue monitorLoop 79 | case <-timeoutSignal: // <8> 80 | log.Println("steward: ward unhealthy; restarting") 81 | close(wardDone) 82 | startWard() 83 | continue monitorLoop 84 | case <-done: 85 | return 86 | } 87 | } 88 | } 89 | }() 90 | 91 | return heartbeat 92 | } 93 | } 94 | log.SetOutput(os.Stdout) 95 | log.SetFlags(log.Ltime | log.LUTC) 96 | 97 | doWork := func(done <-chan interface{}, _ time.Duration) <-chan interface{} { 98 | log.Println("ward: Hello, I'm irresponsible!") 99 | go func() { 100 | <-done // <1> 101 | log.Println("ward: I am halting.") 102 | }() 103 | return nil 104 | } 105 | doWorkWithSteward := newSteward(4*time.Second, doWork) // <2> 106 | 107 | done := make(chan interface{}) 108 | time.AfterFunc(9*time.Second, func() { // <3> 109 | log.Println("main: halting steward and ward.") 110 | close(done) 111 | }) 112 | 113 | for range doWorkWithSteward(done, 4*time.Second) { 114 | } //<4> 115 | log.Println("Done") 116 | } 117 | -------------------------------------------------------------------------------- /concurrency-at-scale/healing-unhealthy-goroutines/fig-more-complicated-ward.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | var or func(channels ...<-chan interface{}) <-chan interface{} 12 | or = func(channels ...<-chan interface{}) <-chan interface{} { // <1> 13 | switch len(channels) { 14 | case 0: // <2> 15 | return nil 16 | case 1: // <3> 17 | return channels[0] 18 | } 19 | 20 | orDone := make(chan interface{}) 21 | go func() { // <4> 22 | defer close(orDone) 23 | 24 | switch len(channels) { 25 | case 2: // <5> 26 | select { 27 | case <-channels[0]: 28 | case <-channels[1]: 29 | } 30 | default: // <6> 31 | select { 32 | case <-channels[0]: 33 | case <-channels[1]: 34 | case <-channels[2]: 35 | case <-or(append(channels[3:], orDone)...): // <6> 36 | } 37 | } 38 | }() 39 | return orDone 40 | } 41 | type startGoroutineFn func( 42 | done <-chan interface{}, 43 | pulseInterval time.Duration, 44 | ) (heartbeat <-chan interface{}) // <1> 45 | 46 | newSteward := func( 47 | timeout time.Duration, 48 | startGoroutine startGoroutineFn, 49 | ) startGoroutineFn { // <2> 50 | return func( 51 | done <-chan interface{}, 52 | pulseInterval time.Duration, 53 | ) <-chan interface{} { 54 | heartbeat := make(chan interface{}) 55 | go func() { 56 | defer close(heartbeat) 57 | 58 | var wardDone chan interface{} 59 | var wardHeartbeat <-chan interface{} 60 | startWard := func() { // <3> 61 | wardDone = make(chan interface{}) // <4> 62 | wardHeartbeat = startGoroutine(or(wardDone, done), timeout/2) // <5> 63 | } 64 | startWard() 65 | pulse := time.Tick(pulseInterval) 66 | 67 | monitorLoop: 68 | for { 69 | timeoutSignal := time.After(timeout) 70 | 71 | for { // <6> 72 | select { 73 | case <-pulse: 74 | select { 75 | case heartbeat <- struct{}{}: 76 | default: 77 | } 78 | case <-wardHeartbeat: // <7> 79 | continue monitorLoop 80 | case <-timeoutSignal: // <8> 81 | log.Println("steward: ward unhealthy; restarting") 82 | close(wardDone) 83 | startWard() 84 | continue monitorLoop 85 | case <-done: 86 | return 87 | } 88 | } 89 | } 90 | }() 91 | 92 | return heartbeat 93 | } 94 | } 95 | take := func( 96 | done <-chan interface{}, 97 | valueStream <-chan interface{}, 98 | num int, 99 | ) <-chan interface{} { 100 | takeStream := make(chan interface{}) 101 | go func() { 102 | defer close(takeStream) 103 | for i := 0; i < num; i++ { 104 | select { 105 | case <-done: 106 | return 107 | case takeStream <- <-valueStream: 108 | } 109 | } 110 | }() 111 | return takeStream 112 | } 113 | //bridge := func( 114 | // done <-chan interface{}, 115 | // chanStream <-chan <-chan interface{}, 116 | //) <-chan interface{} { 117 | // valStream := make(chan interface{}) // <1> 118 | // go func() { 119 | // defer close(valStream) 120 | // for { // <2> 121 | // var stream <-chan interface{} 122 | // select { 123 | // case maybeStream, ok := <-chanStream: 124 | // if ok == false { 125 | // return 126 | // } 127 | // stream = maybeStream 128 | // case <-done: 129 | // return 130 | // } 131 | // for val := range orDone(done, stream) { // <3> 132 | // select { 133 | // case valStream <- val: 134 | // case <-done: 135 | // } 136 | // } 137 | // } 138 | // }() 139 | // return valStream 140 | //} 141 | doWorkFn := func( 142 | done <-chan interface{}, 143 | intList ...int, 144 | ) (startGoroutineFn, <-chan interface{}) { // <1> 145 | intChanStream := make(chan (<-chan interface{})) // <2> 146 | intStream := bridge(done, intChanStream) 147 | doWork := func( 148 | done <-chan interface{}, 149 | pulseInterval time.Duration, 150 | ) <-chan interface{} { // <3> 151 | intStream := make(chan interface{}) // <4> 152 | heartbeat := make(chan interface{}) 153 | go func() { 154 | defer close(intStream) 155 | select { 156 | case intChanStream <- intStream: // <5> 157 | case <-done: 158 | return 159 | } 160 | 161 | pulse := time.Tick(pulseInterval) 162 | 163 | for { 164 | valueLoop: 165 | for _, intVal := range intList { 166 | if intVal < 0 { 167 | log.Printf("negative value: %v\n", intVal) // <6> 168 | return 169 | } 170 | 171 | for { 172 | select { 173 | case <-pulse: 174 | select { 175 | case heartbeat <- struct{}{}: 176 | default: 177 | } 178 | case intStream <- intVal: 179 | continue valueLoop 180 | case <-done: 181 | return 182 | } 183 | } 184 | } 185 | } 186 | }() 187 | return heartbeat 188 | } 189 | return doWork, intStream 190 | } 191 | log.SetFlags(log.Ltime | log.LUTC) 192 | log.SetOutput(os.Stdout) 193 | 194 | done := make(chan interface{}) 195 | defer close(done) 196 | 197 | doWork, intStream := doWorkFn(done, 1, 2, -1, 3, 4, 5) // <1> 198 | doWorkWithSteward := newSteward(1*time.Millisecond, doWork) // <2> 199 | doWorkWithSteward(done, 1*time.Hour) // <3> 200 | 201 | for intVal := range take(done, intStream, 6) { // <4> 202 | fmt.Printf("Received: %v\n", intVal) 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /concurrency-at-scale/heartbeats/bad_concurrent_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func DoWork( 9 | done <-chan interface{}, 10 | nums ...int, 11 | ) (<-chan interface{}, <-chan int) { 12 | heartbeat := make(chan interface{}, 1) 13 | intStream := make(chan int) 14 | go func() { 15 | defer close(heartbeat) 16 | defer close(intStream) 17 | 18 | time.Sleep(2 * time.Second) // <1> 19 | 20 | for _, n := range nums { 21 | select { 22 | case heartbeat <- struct{}{}: 23 | default: 24 | } 25 | 26 | select { 27 | case <-done: 28 | return 29 | case intStream <- n: 30 | } 31 | } 32 | }() 33 | 34 | return heartbeat, intStream 35 | } 36 | func TestDoWork_GeneratesAllNumbers(t *testing.T) { 37 | done := make(chan interface{}) 38 | defer close(done) 39 | 40 | intSlice := []int{0, 1, 2, 3, 5} 41 | _, results := DoWork(done, intSlice...) 42 | 43 | for i, expected := range intSlice { 44 | select { 45 | case r := <-results: 46 | if r != expected { 47 | t.Errorf( 48 | "index %v: expected %v, but received %v,", 49 | i, 50 | expected, 51 | r, 52 | ) 53 | } 54 | case <-time.After(1 * time.Second): // <1> 55 | t.Fatal("test timed out") 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /concurrency-at-scale/heartbeats/concurrent_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func DoWork( 9 | done <-chan interface{}, 10 | nums ...int, 11 | ) (<-chan interface{}, <-chan int) { 12 | heartbeat := make(chan interface{}, 1) 13 | intStream := make(chan int) 14 | go func() { 15 | defer close(heartbeat) 16 | defer close(intStream) 17 | 18 | time.Sleep(2 * time.Second) // <1> 19 | 20 | for _, n := range nums { 21 | select { 22 | case heartbeat <- struct{}{}: 23 | default: 24 | } 25 | 26 | select { 27 | case <-done: 28 | return 29 | case intStream <- n: 30 | } 31 | } 32 | }() 33 | 34 | return heartbeat, intStream 35 | } 36 | 37 | func TestDoWork_GeneratesAllNumbers(t *testing.T) { 38 | done := make(chan interface{}) 39 | defer close(done) 40 | 41 | intSlice := []int{0, 1, 2, 3, 5} 42 | heartbeat, results := DoWork(done, intSlice...) 43 | 44 | <-heartbeat // <1> 45 | 46 | i := 0 47 | for r := range results { 48 | if expected := intSlice[i]; r != expected { 49 | t.Errorf("index %v: expected %v, but received %v,", i, expected, r) 50 | } 51 | i++ 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /concurrency-at-scale/heartbeats/dowork_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func DoWork( 9 | done <-chan interface{}, 10 | nums ...int, 11 | ) (<-chan interface{}, <-chan int) { 12 | heartbeat := make(chan interface{}, 1) 13 | intStream := make(chan int) 14 | go func() { 15 | defer close(heartbeat) 16 | defer close(intStream) 17 | 18 | time.Sleep(2 * time.Second) // <1> 19 | 20 | for _, n := range nums { 21 | select { 22 | case heartbeat <- struct{}{}: 23 | default: 24 | } 25 | 26 | select { 27 | case <-done: 28 | return 29 | case intStream <- n: 30 | } 31 | } 32 | }() 33 | 34 | return heartbeat, intStream 35 | } 36 | -------------------------------------------------------------------------------- /concurrency-at-scale/heartbeats/fig-interval-heartbeat-misbehaving-goroutine.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | doWork := func( 10 | done <-chan interface{}, 11 | pulseInterval time.Duration, 12 | ) (<-chan interface{}, <-chan time.Time) { 13 | heartbeat := make(chan interface{}) 14 | results := make(chan time.Time) 15 | go func() { 16 | pulse := time.Tick(pulseInterval) 17 | workGen := time.Tick(2 * pulseInterval) 18 | 19 | sendPulse := func() { 20 | select { 21 | case heartbeat <- struct{}{}: 22 | default: 23 | } 24 | } 25 | sendResult := func(r time.Time) { 26 | for { 27 | select { 28 | case <-pulse: 29 | sendPulse() 30 | case results <- r: 31 | return 32 | } 33 | } 34 | } 35 | 36 | for i := 0; i < 2; i++ { // <1> 37 | select { 38 | case <-done: 39 | return 40 | case <-pulse: 41 | sendPulse() 42 | case r := <-workGen: 43 | sendResult(r) 44 | } 45 | } 46 | }() 47 | return heartbeat, results 48 | } 49 | 50 | done := make(chan interface{}) 51 | time.AfterFunc(10*time.Second, func() { close(done) }) 52 | 53 | const timeout = 2 * time.Second 54 | heartbeat, results := doWork(done, timeout/2) 55 | for { 56 | select { 57 | case _, ok := <-heartbeat: 58 | if ok == false { 59 | return 60 | } 61 | fmt.Println("pulse") 62 | case r, ok := <-results: 63 | if ok == false { 64 | return 65 | } 66 | fmt.Printf("results %v\n", r) 67 | case <-time.After(timeout): 68 | fmt.Println("worker goroutine is not healthy!") 69 | return 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /concurrency-at-scale/heartbeats/fig-interval-heartbeat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | doWork := func( 10 | done <-chan interface{}, 11 | pulseInterval time.Duration, 12 | ) (<-chan interface{}, <-chan time.Time) { 13 | heartbeat := make(chan interface{}) // <1> 14 | results := make(chan time.Time) 15 | go func() { 16 | defer close(heartbeat) 17 | defer close(results) 18 | 19 | pulse := time.Tick(pulseInterval) // <2> 20 | workGen := time.Tick(2 * pulseInterval) // <3> 21 | 22 | sendPulse := func() { 23 | select { 24 | case heartbeat <- struct{}{}: 25 | default: // <4> 26 | } 27 | } 28 | sendResult := func(r time.Time) { 29 | for { 30 | select { 31 | case <-done: 32 | return 33 | case <-pulse: // <5> 34 | sendPulse() 35 | case results <- r: 36 | return 37 | } 38 | } 39 | } 40 | 41 | for { 42 | select { 43 | case <-done: 44 | return 45 | case <-pulse: // <5> 46 | sendPulse() 47 | case r := <-workGen: 48 | sendResult(r) 49 | } 50 | } 51 | }() 52 | return heartbeat, results 53 | } 54 | done := make(chan interface{}) 55 | time.AfterFunc(10*time.Second, func() { close(done) }) // <1> 56 | 57 | const timeout = 2 * time.Second // <2> 58 | heartbeat, results := doWork(done, timeout/2) // <3> 59 | for { 60 | select { 61 | case _, ok := <-heartbeat: // <4> 62 | if ok == false { 63 | return 64 | } 65 | fmt.Println("pulse") 66 | case r, ok := <-results: // <5> 67 | if ok == false { 68 | return 69 | } 70 | fmt.Printf("results %v\n", r.Second()) 71 | case <-time.After(timeout): // <6> 72 | return 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /concurrency-at-scale/heartbeats/fig-work-unit-pulse.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | ) 7 | 8 | func main() { 9 | doWork := func(done <-chan interface{}) (<-chan interface{}, <-chan int) { 10 | heartbeatStream := make(chan interface{}, 1) // <1> 11 | workStream := make(chan int) 12 | go func() { 13 | defer close(heartbeatStream) 14 | defer close(workStream) 15 | 16 | for i := 0; i < 10; i++ { 17 | select { // <2> 18 | case heartbeatStream <- struct{}{}: 19 | default: // <3> 20 | } 21 | 22 | select { 23 | case <-done: 24 | return 25 | case workStream <- rand.Intn(10): 26 | } 27 | } 28 | }() 29 | 30 | return heartbeatStream, workStream 31 | } 32 | 33 | done := make(chan interface{}) 34 | defer close(done) 35 | 36 | heartbeat, results := doWork(done) 37 | for { 38 | select { 39 | case _, ok := <-heartbeat: 40 | if ok { 41 | fmt.Println("pulse") 42 | } else { 43 | return 44 | } 45 | case r, ok := <-results: 46 | if ok { 47 | fmt.Printf("results %v\n", r) 48 | } else { 49 | return 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /concurrency-at-scale/heartbeats/interval_concurrent_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func DoWork( 9 | done <-chan interface{}, 10 | pulseInterval time.Duration, 11 | nums ...int, 12 | ) (<-chan interface{}, <-chan int) { 13 | heartbeat := make(chan interface{}, 1) 14 | intStream := make(chan int) 15 | go func() { 16 | defer close(heartbeat) 17 | defer close(intStream) 18 | 19 | time.Sleep(2 * time.Second) 20 | 21 | pulse := time.Tick(pulseInterval) 22 | numLoop: // <2> 23 | for _, n := range nums { 24 | for { // <1> 25 | select { 26 | case <-done: 27 | return 28 | case <-pulse: 29 | select { 30 | case heartbeat <- struct{}{}: 31 | default: 32 | } 33 | case intStream <- n: 34 | continue numLoop // <3> 35 | } 36 | } 37 | } 38 | }() 39 | 40 | return heartbeat, intStream 41 | } 42 | 43 | func TestDoWork_GeneratesAllNumbers(t *testing.T) { 44 | done := make(chan interface{}) 45 | defer close(done) 46 | 47 | intSlice := []int{0, 1, 2, 3, 5} 48 | const timeout = 2 * time.Second 49 | heartbeat, results := DoWork(done, timeout/2, intSlice...) 50 | 51 | <-heartbeat // <4> 52 | 53 | i := 0 54 | for { 55 | select { 56 | case r, ok := <-results: 57 | if ok == false { 58 | return 59 | } else if expected := intSlice[i]; r != expected { 60 | t.Errorf("index %v: expected %v, but received %v,", i, expected, r) 61 | } 62 | i++ 63 | case <-heartbeat: // <5> 64 | case <-time.After(timeout): 65 | t.Fatal("test timed out") 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /concurrency-at-scale/rate-limiting/fig-multi-rate-limit.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "golang.org/x/time/rate" 6 | "log" 7 | "os" 8 | "sort" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | defer log.Printf("Done.") 15 | log.SetOutput(os.Stdout) 16 | log.SetFlags(log.Ltime | log.LUTC) 17 | 18 | apiConnection := Open() 19 | var wg sync.WaitGroup 20 | wg.Add(20) 21 | 22 | for i := 0; i < 10; i++ { 23 | go func() { 24 | defer wg.Done() 25 | err := apiConnection.ReadFile(context.Background()) 26 | if err != nil { 27 | log.Printf("cannot ReadFile: %v", err) 28 | } 29 | log.Printf("ReadFile") 30 | }() 31 | } 32 | 33 | for i := 0; i < 10; i++ { 34 | go func() { 35 | defer wg.Done() 36 | err := apiConnection.ResolveAddress(context.Background()) 37 | if err != nil { 38 | log.Printf("cannot ResolveAddress: %v", err) 39 | } 40 | log.Printf("ResolveAddress") 41 | }() 42 | } 43 | 44 | wg.Wait() 45 | } 46 | func Per(eventCount int, duration time.Duration) rate.Limit { 47 | return rate.Every(duration / time.Duration(eventCount)) 48 | } 49 | func Open() *APIConnection { 50 | secondLimit := rate.NewLimiter(Per(2, time.Second), 1) // <1> 51 | minuteLimit := rate.NewLimiter(Per(10, time.Minute), 10) // <2> 52 | return &APIConnection{ 53 | rateLimiter: MultiLimiter(secondLimit, minuteLimit), // <3> 54 | } 55 | } 56 | 57 | type APIConnection struct { 58 | rateLimiter RateLimiter 59 | } 60 | 61 | func (a *APIConnection) ReadFile(ctx context.Context) error { 62 | if err := a.rateLimiter.Wait(ctx); err != nil { 63 | return err 64 | } 65 | // Pretend we do work here 66 | return nil 67 | } 68 | 69 | func (a *APIConnection) ResolveAddress(ctx context.Context) error { 70 | if err := a.rateLimiter.Wait(ctx); err != nil { 71 | return err 72 | } 73 | // Pretend we do work here 74 | return nil 75 | } 76 | 77 | type RateLimiter interface { // <1> 78 | Wait(context.Context) error 79 | Limit() rate.Limit 80 | } 81 | 82 | func MultiLimiter(limiters ...RateLimiter) *multiLimiter { 83 | byLimit := func(i, j int) bool { 84 | return limiters[i].Limit() < limiters[j].Limit() 85 | } 86 | sort.Slice(limiters, byLimit) // <2> 87 | return &multiLimiter{limiters: limiters} 88 | } 89 | 90 | type multiLimiter struct { 91 | limiters []RateLimiter 92 | } 93 | 94 | func (l *multiLimiter) Wait(ctx context.Context) error { 95 | for _, l := range l.limiters { 96 | if err := l.Wait(ctx); err != nil { 97 | return err 98 | } 99 | } 100 | return nil 101 | } 102 | 103 | func (l *multiLimiter) Limit() rate.Limit { 104 | return l.limiters[0].Limit() // <3> 105 | } 106 | -------------------------------------------------------------------------------- /concurrency-at-scale/rate-limiting/fig-no-rate-limit.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | func main() { 11 | defer log.Printf("Done.") 12 | log.SetOutput(os.Stdout) 13 | log.SetFlags(log.Ltime | log.LUTC) 14 | 15 | apiConnection := Open() 16 | var wg sync.WaitGroup 17 | wg.Add(20) 18 | 19 | for i := 0; i < 10; i++ { 20 | go func() { 21 | defer wg.Done() 22 | err := apiConnection.ReadFile(context.Background()) 23 | if err != nil { 24 | log.Printf("cannot ReadFile: %v", err) 25 | } 26 | log.Printf("ReadFile") 27 | }() 28 | } 29 | 30 | for i := 0; i < 10; i++ { 31 | go func() { 32 | defer wg.Done() 33 | err := apiConnection.ResolveAddress(context.Background()) 34 | if err != nil { 35 | log.Printf("cannot ResolveAddress: %v", err) 36 | } 37 | log.Printf("ResolveAddress") 38 | }() 39 | } 40 | 41 | wg.Wait() 42 | } 43 | func Open() *APIConnection { 44 | return &APIConnection{} 45 | } 46 | 47 | type APIConnection struct{} 48 | 49 | func (a *APIConnection) ReadFile(ctx context.Context) error { 50 | // Pretend we do work here 51 | return nil 52 | } 53 | 54 | func (a *APIConnection) ResolveAddress(ctx context.Context) error { 55 | // Pretend we do work here 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /concurrency-at-scale/rate-limiting/fig-simple-rate-limit.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "golang.org/x/time/rate" 6 | "log" 7 | "os" 8 | "sync" 9 | ) 10 | 11 | func main() { 12 | defer log.Printf("Done.") 13 | log.SetOutput(os.Stdout) 14 | log.SetFlags(log.Ltime | log.LUTC) 15 | 16 | apiConnection := Open() 17 | var wg sync.WaitGroup 18 | wg.Add(20) 19 | 20 | for i := 0; i < 10; i++ { 21 | go func() { 22 | defer wg.Done() 23 | err := apiConnection.ReadFile(context.Background()) 24 | if err != nil { 25 | log.Printf("cannot ReadFile: %v", err) 26 | } 27 | log.Printf("ReadFile") 28 | }() 29 | } 30 | 31 | for i := 0; i < 10; i++ { 32 | go func() { 33 | defer wg.Done() 34 | err := apiConnection.ResolveAddress(context.Background()) 35 | if err != nil { 36 | log.Printf("cannot ResolveAddress: %v", err) 37 | } 38 | log.Printf("ResolveAddress") 39 | }() 40 | } 41 | 42 | wg.Wait() 43 | } 44 | func Open() *APIConnection { 45 | return &APIConnection{ 46 | rateLimiter: rate.NewLimiter(rate.Limit(1), 1), // <1> 47 | } 48 | } 49 | 50 | type APIConnection struct { 51 | rateLimiter *rate.Limiter 52 | } 53 | 54 | func (a *APIConnection) ReadFile(ctx context.Context) error { 55 | if err := a.rateLimiter.Wait(ctx); err != nil { // <2> 56 | return err 57 | } 58 | // Pretend we do work here 59 | return nil 60 | } 61 | 62 | func (a *APIConnection) ResolveAddress(ctx context.Context) error { 63 | if err := a.rateLimiter.Wait(ctx); err != nil { // <2> 64 | return err 65 | } 66 | // Pretend we do work here 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /concurrency-at-scale/rate-limiting/fig-tiered-rate-limit.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "golang.org/x/time/rate" 6 | "log" 7 | "os" 8 | "sort" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | defer log.Printf("Done.") 15 | log.SetOutput(os.Stdout) 16 | log.SetFlags(log.Ltime | log.LUTC) 17 | 18 | apiConnection := Open() 19 | var wg sync.WaitGroup 20 | wg.Add(20) 21 | 22 | for i := 0; i < 10; i++ { 23 | go func() { 24 | defer wg.Done() 25 | err := apiConnection.ReadFile(context.Background()) 26 | if err != nil { 27 | log.Printf("cannot ReadFile: %v", err) 28 | } 29 | log.Printf("ReadFile") 30 | }() 31 | } 32 | 33 | for i := 0; i < 10; i++ { 34 | go func() { 35 | defer wg.Done() 36 | err := apiConnection.ResolveAddress(context.Background()) 37 | if err != nil { 38 | log.Printf("cannot ResolveAddress: %v", err) 39 | } 40 | log.Printf("ResolveAddress") 41 | }() 42 | } 43 | 44 | wg.Wait() 45 | } 46 | func Per(eventCount int, duration time.Duration) rate.Limit { 47 | return rate.Every(duration / time.Duration(eventCount)) 48 | } 49 | 50 | type RateLimiter interface { // <1> 51 | Wait(context.Context) error 52 | Limit() rate.Limit 53 | } 54 | 55 | func MultiLimiter(limiters ...RateLimiter) *multiLimiter { 56 | byLimit := func(i, j int) bool { 57 | return limiters[i].Limit() < limiters[j].Limit() 58 | } 59 | sort.Slice(limiters, byLimit) // <2> 60 | return &multiLimiter{limiters: limiters} 61 | } 62 | 63 | type multiLimiter struct { 64 | limiters []RateLimiter 65 | } 66 | 67 | func (l *multiLimiter) Wait(ctx context.Context) error { 68 | for _, l := range l.limiters { 69 | if err := l.Wait(ctx); err != nil { 70 | return err 71 | } 72 | } 73 | return nil 74 | } 75 | 76 | func (l *multiLimiter) Limit() rate.Limit { 77 | return l.limiters[0].Limit() // <3> 78 | } 79 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/confinement/fig-confinement-ad-hoc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | data := make([]int, 4) 9 | 10 | loopData := func(handleData chan<- int) { 11 | defer close(handleData) 12 | for i := range data { 13 | handleData <- data[i] 14 | } 15 | } 16 | 17 | handleData := make(chan int) 18 | go loopData(handleData) 19 | 20 | for num := range handleData { 21 | fmt.Println(num) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/confinement/fig-confinement-ownership.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | chanOwner := func() <-chan int { 9 | results := make(chan int, 5) // <1> 10 | go func() { 11 | defer close(results) 12 | for i := 0; i <= 5; i++ { 13 | results <- i 14 | } 15 | }() 16 | return results 17 | } 18 | 19 | consumer := func(results <-chan int) { // <3> 20 | for result := range results { 21 | fmt.Printf("Received: %d\n", result) 22 | } 23 | fmt.Println("Done receiving!") 24 | } 25 | 26 | results := chanOwner() // <2> 27 | consumer(results) 28 | } 29 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/confinement/fig-confinement-structs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sync" 7 | ) 8 | 9 | func main() { 10 | printData := func(wg *sync.WaitGroup, data []byte) { 11 | defer wg.Done() 12 | 13 | var buff bytes.Buffer 14 | for _, b := range data { 15 | fmt.Fprintf(&buff, "%c", b) 16 | } 17 | fmt.Println(buff.String()) 18 | } 19 | 20 | var wg sync.WaitGroup 21 | wg.Add(2) 22 | data := []byte("golang") 23 | go printData(&wg, data[:3]) // <1> 24 | go printData(&wg, data[3:]) // <2> 25 | 26 | wg.Wait() 27 | } 28 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/error-handling/fig-patterns-imporoper-err-handling.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | checkStatus := func( 10 | done <-chan interface{}, 11 | urls ...string, 12 | ) <-chan *http.Response { 13 | responses := make(chan *http.Response) 14 | go func() { 15 | defer close(responses) 16 | for _, url := range urls { 17 | resp, err := http.Get(url) 18 | if err != nil { 19 | fmt.Println(err) // <1> 20 | continue 21 | } 22 | select { 23 | case <-done: 24 | return 25 | case responses <- resp: 26 | } 27 | } 28 | }() 29 | return responses 30 | } 31 | 32 | done := make(chan interface{}) 33 | defer close(done) 34 | 35 | urls := []string{"https://www.google.com", "https://badhost"} 36 | for response := range checkStatus(done, urls...) { 37 | fmt.Printf("Response: %v\n", response.Status) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/error-handling/fig-patterns-proper-err-handling.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | type Result struct { // <1> 10 | Error error 11 | Response *http.Response 12 | } 13 | checkStatus := func(done <-chan interface{}, urls ...string) <-chan Result { // <2> 14 | results := make(chan Result) 15 | go func() { 16 | defer close(results) 17 | 18 | for _, url := range urls { 19 | var result Result 20 | resp, err := http.Get(url) 21 | result = Result{Error: err, Response: resp} // <3> 22 | select { 23 | case <-done: 24 | return 25 | case results <- result: // <4> 26 | } 27 | } 28 | }() 29 | return results 30 | } 31 | 32 | done := make(chan interface{}) 33 | defer close(done) 34 | 35 | urls := []string{"https://www.google.com", "https://badhost"} 36 | for result := range checkStatus(done, urls...) { 37 | if result.Error != nil { // <5> 38 | fmt.Printf("error: %v", result.Error) 39 | continue 40 | } 41 | fmt.Printf("Response: %v\n", result.Response.Status) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/error-handling/fig-stop-after-three-errors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | type Result struct { // <1> 10 | Error error 11 | Response *http.Response 12 | } 13 | checkStatus := func(done <-chan interface{}, urls ...string) <-chan Result { // <2> 14 | results := make(chan Result) 15 | go func() { 16 | defer close(results) 17 | 18 | for _, url := range urls { 19 | var result Result 20 | resp, err := http.Get(url) 21 | result = Result{Error: err, Response: resp} // <3> 22 | select { 23 | case <-done: 24 | return 25 | case results <- result: // <4> 26 | } 27 | } 28 | }() 29 | return results 30 | } 31 | done := make(chan interface{}) 32 | defer close(done) 33 | 34 | errCount := 0 35 | urls := []string{"a", "https://www.google.com", "b", "c", "d"} 36 | for result := range checkStatus(done, urls...) { 37 | if result.Error != nil { 38 | fmt.Printf("error: %v\n", result.Error) 39 | errCount++ 40 | if errCount >= 3 { 41 | fmt.Println("Too many errors, breaking!") 42 | break 43 | } 44 | continue 45 | } 46 | fmt.Printf("Response: %v\n", result.Response.Status) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/fan-out-fan-in/fig-fan-out-naive-prime-finder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "runtime" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | repeatFn := func( 13 | done <-chan interface{}, 14 | fn func() interface{}, 15 | ) <-chan interface{} { 16 | valueStream := make(chan interface{}) 17 | go func() { 18 | defer close(valueStream) 19 | for { 20 | select { 21 | case <-done: 22 | return 23 | case valueStream <- fn(): 24 | } 25 | } 26 | }() 27 | return valueStream 28 | } 29 | take := func( 30 | done <-chan interface{}, 31 | valueStream <-chan interface{}, 32 | num int, 33 | ) <-chan interface{} { 34 | takeStream := make(chan interface{}) 35 | go func() { 36 | defer close(takeStream) 37 | for i := 0; i < num; i++ { 38 | select { 39 | case <-done: 40 | return 41 | case takeStream <- <-valueStream: 42 | } 43 | } 44 | }() 45 | return takeStream 46 | } 47 | toInt := func(done <-chan interface{}, valueStream <-chan interface{}) <-chan int { 48 | intStream := make(chan int) 49 | go func() { 50 | defer close(intStream) 51 | for v := range valueStream { 52 | select { 53 | case <-done: 54 | return 55 | case intStream <- v.(int): 56 | } 57 | } 58 | }() 59 | return intStream 60 | } 61 | primeFinder := func(done <-chan interface{}, intStream <-chan int) <-chan interface{} { 62 | primeStream := make(chan interface{}) 63 | go func() { 64 | defer close(primeStream) 65 | for integer := range intStream { 66 | integer -= 1 67 | prime := true 68 | for divisor := integer - 1; divisor > 1; divisor-- { 69 | if integer%divisor == 0 { 70 | prime = false 71 | break 72 | } 73 | } 74 | 75 | if prime { 76 | select { 77 | case <-done: 78 | return 79 | case primeStream <- integer: 80 | } 81 | } 82 | } 83 | }() 84 | return primeStream 85 | } 86 | fanIn := func( 87 | done <-chan interface{}, 88 | channels ...<-chan interface{}, 89 | ) <-chan interface{} { // <1> 90 | var wg sync.WaitGroup // <2> 91 | multiplexedStream := make(chan interface{}) 92 | 93 | multiplex := func(c <-chan interface{}) { // <3> 94 | defer wg.Done() 95 | for i := range c { 96 | select { 97 | case <-done: 98 | return 99 | case multiplexedStream <- i: 100 | } 101 | } 102 | } 103 | 104 | // Select from all the channels 105 | wg.Add(len(channels)) // <4> 106 | for _, c := range channels { 107 | go multiplex(c) 108 | } 109 | 110 | // Wait for all the reads to complete 111 | go func() { // <5> 112 | wg.Wait() 113 | close(multiplexedStream) 114 | }() 115 | 116 | return multiplexedStream 117 | } 118 | 119 | done := make(chan interface{}) 120 | defer close(done) 121 | 122 | start := time.Now() 123 | 124 | rand := func() interface{} { return rand.Intn(50000000) } 125 | 126 | randIntStream := toInt(done, repeatFn(done, rand)) 127 | 128 | numFinders := runtime.NumCPU() 129 | fmt.Printf("Spinning up %d prime finders.\n", numFinders) 130 | finders := make([]<-chan interface{}, numFinders) 131 | fmt.Println("Primes:") 132 | for i := 0; i < numFinders; i++ { 133 | finders[i] = primeFinder(done, randIntStream) 134 | } 135 | 136 | for prime := range take(done, fanIn(done, finders...), 10) { 137 | fmt.Printf("\t%d\n", prime) 138 | } 139 | 140 | fmt.Printf("Search took: %v", time.Since(start)) 141 | } 142 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/fan-out-fan-in/fig-naive-prime-finder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | repeatFn := func( 11 | done <-chan interface{}, 12 | fn func() interface{}, 13 | ) <-chan interface{} { 14 | valueStream := make(chan interface{}) 15 | go func() { 16 | defer close(valueStream) 17 | for { 18 | select { 19 | case <-done: 20 | return 21 | case valueStream <- fn(): 22 | } 23 | } 24 | }() 25 | return valueStream 26 | } 27 | take := func( 28 | done <-chan interface{}, 29 | valueStream <-chan interface{}, 30 | num int, 31 | ) <-chan interface{} { 32 | takeStream := make(chan interface{}) 33 | go func() { 34 | defer close(takeStream) 35 | for i := 0; i < num; i++ { 36 | select { 37 | case <-done: 38 | return 39 | case takeStream <- <-valueStream: 40 | } 41 | } 42 | }() 43 | return takeStream 44 | } 45 | toInt := func(done <-chan interface{}, valueStream <-chan interface{}) <-chan int { 46 | intStream := make(chan int) 47 | go func() { 48 | defer close(intStream) 49 | for v := range valueStream { 50 | select { 51 | case <-done: 52 | return 53 | case intStream <- v.(int): 54 | } 55 | } 56 | }() 57 | return intStream 58 | } 59 | primeFinder := func(done <-chan interface{}, intStream <-chan int) <-chan interface{} { 60 | primeStream := make(chan interface{}) 61 | go func() { 62 | defer close(primeStream) 63 | for integer := range intStream { 64 | integer -= 1 65 | prime := true 66 | for divisor := integer - 1; divisor > 1; divisor-- { 67 | if integer%divisor == 0 { 68 | prime = false 69 | break 70 | } 71 | } 72 | 73 | if prime { 74 | select { 75 | case <-done: 76 | return 77 | case primeStream <- integer: 78 | } 79 | } 80 | } 81 | }() 82 | return primeStream 83 | } 84 | rand := func() interface{} { return rand.Intn(50000000) } 85 | 86 | done := make(chan interface{}) 87 | defer close(done) 88 | 89 | start := time.Now() 90 | 91 | randIntStream := toInt(done, repeatFn(done, rand)) 92 | fmt.Println("Primes:") 93 | for prime := range take(done, primeFinder(done, randIntStream), 10) { 94 | fmt.Printf("\t%d\n", prime) 95 | } 96 | 97 | fmt.Printf("Search took: %v", time.Since(start)) 98 | } 99 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/pipelines/best-practices-for-constructing-pipelines/fig-pipelines-chan-stream-processing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | generator := func(done <-chan interface{}, integers ...int) <-chan int { 9 | intStream := make(chan int) 10 | go func() { 11 | defer close(intStream) 12 | for _, i := range integers { 13 | select { 14 | case <-done: 15 | return 16 | case intStream <- i: 17 | } 18 | } 19 | }() 20 | return intStream 21 | } 22 | 23 | multiply := func( 24 | done <-chan interface{}, 25 | intStream <-chan int, 26 | multiplier int, 27 | ) <-chan int { 28 | multipliedStream := make(chan int) 29 | go func() { 30 | defer close(multipliedStream) 31 | for i := range intStream { 32 | select { 33 | case <-done: 34 | return 35 | case multipliedStream <- i * multiplier: 36 | } 37 | } 38 | }() 39 | return multipliedStream 40 | } 41 | 42 | add := func( 43 | done <-chan interface{}, 44 | intStream <-chan int, 45 | additive int, 46 | ) <-chan int { 47 | addedStream := make(chan int) 48 | go func() { 49 | defer close(addedStream) 50 | for i := range intStream { 51 | select { 52 | case <-done: 53 | return 54 | case addedStream <- i + additive: 55 | } 56 | } 57 | }() 58 | return addedStream 59 | } 60 | 61 | done := make(chan interface{}) 62 | defer close(done) 63 | 64 | intStream := generator(done, 1, 2, 3, 4) 65 | pipeline := multiply(done, add(done, multiply(done, intStream, 2), 1), 2) 66 | 67 | for v := range pipeline { 68 | fmt.Println(v) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/pipelines/fig-adding-additional-stage-to-pipeline.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | multiply := func(values []int, multiplier int) []int { 9 | multipliedValues := make([]int, len(values)) 10 | for i, v := range values { 11 | multipliedValues[i] = v * multiplier 12 | } 13 | return multipliedValues 14 | } 15 | add := func(values []int, additive int) []int { 16 | addedValues := make([]int, len(values)) 17 | for i, v := range values { 18 | addedValues[i] = v + additive 19 | } 20 | return addedValues 21 | } 22 | ints := []int{1, 2, 3, 4} 23 | for _, v := range multiply(add(multiply(ints, 2), 1), 2) { 24 | fmt.Println(v) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/pipelines/fig-functional-pipeline-combination.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | multiply := func(values []int, multiplier int) []int { 9 | multipliedValues := make([]int, len(values)) 10 | for i, v := range values { 11 | multipliedValues[i] = v * multiplier 12 | } 13 | return multipliedValues 14 | } 15 | add := func(values []int, additive int) []int { 16 | addedValues := make([]int, len(values)) 17 | for i, v := range values { 18 | addedValues[i] = v + additive 19 | } 20 | return addedValues 21 | } 22 | 23 | ints := []int{1, 2, 3, 4} 24 | for _, v := range add(multiply(ints, 2), 1) { 25 | fmt.Println(v) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/pipelines/fig-pipelines-func-stream-processing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | multiply := func(value, multiplier int) int { 9 | return value * multiplier 10 | } 11 | 12 | add := func(value, additive int) int { 13 | return value + additive 14 | } 15 | 16 | ints := []int{1, 2, 3, 4} 17 | for _, v := range ints { 18 | fmt.Println(multiply(add(multiply(v, 2), 1), 2)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/pipelines/some-handy-generators/fig-take-and-repeat-pipeline.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | repeat := func( 9 | done <-chan interface{}, 10 | values ...interface{}, 11 | ) <-chan interface{} { 12 | valueStream := make(chan interface{}) 13 | go func() { 14 | defer close(valueStream) 15 | for { 16 | for _, v := range values { 17 | select { 18 | case <-done: 19 | return 20 | case valueStream <- v: 21 | } 22 | } 23 | } 24 | }() 25 | return valueStream 26 | } 27 | take := func( 28 | done <-chan interface{}, 29 | valueStream <-chan interface{}, 30 | num int, 31 | ) <-chan interface{} { 32 | takeStream := make(chan interface{}) 33 | go func() { 34 | defer close(takeStream) 35 | for i := 0; i < num; i++ { 36 | select { 37 | case <-done: 38 | return 39 | case takeStream <- <-valueStream: 40 | } 41 | } 42 | }() 43 | return takeStream 44 | } 45 | done := make(chan interface{}) 46 | defer close(done) 47 | 48 | for num := range take(done, repeat(done, 1), 10) { 49 | fmt.Printf("%v ", num) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/pipelines/some-handy-generators/fig-utilizing-string-stage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | take := func( 9 | done <-chan interface{}, 10 | valueStream <-chan interface{}, 11 | num int, 12 | ) <-chan interface{} { 13 | takeStream := make(chan interface{}) 14 | go func() { 15 | defer close(takeStream) 16 | for i := 0; i < num; i++ { 17 | select { 18 | case <-done: 19 | return 20 | case takeStream <- <-valueStream: 21 | } 22 | } 23 | }() 24 | return takeStream 25 | } 26 | repeat := func( 27 | done <-chan interface{}, 28 | values ...interface{}, 29 | ) <-chan interface{} { 30 | valueStream := make(chan interface{}) 31 | go func() { 32 | defer close(valueStream) 33 | for { 34 | for _, v := range values { 35 | select { 36 | case <-done: 37 | return 38 | case valueStream <- v: 39 | } 40 | } 41 | } 42 | }() 43 | return valueStream 44 | } 45 | toString := func( 46 | done <-chan interface{}, 47 | valueStream <-chan interface{}, 48 | ) <-chan string { 49 | stringStream := make(chan string) 50 | go func() { 51 | defer close(stringStream) 52 | for v := range valueStream { 53 | select { 54 | case <-done: 55 | return 56 | case stringStream <- v.(string): 57 | } 58 | } 59 | }() 60 | return stringStream 61 | } 62 | done := make(chan interface{}) 63 | defer close(done) 64 | 65 | var message string 66 | for token := range toString(done, take(done, repeat(done, "I", "am."), 5)) { 67 | message += token 68 | } 69 | 70 | fmt.Printf("message: %s...", message) 71 | } 72 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/pipelines/some-handy-generators/pipelines_test.go: -------------------------------------------------------------------------------- 1 | package pipelines 2 | 3 | import () 4 | 5 | func BenchmarkGeneric(b *testing.B) { 6 | repeat := func( 7 | done <-chan interface{}, 8 | values ...interface{}, 9 | ) <-chan interface{} { 10 | valueStream := make(chan interface{}) 11 | go func() { 12 | defer close(valueStream) 13 | for { 14 | for _, v := range values { 15 | select { 16 | case <-done: 17 | return 18 | case valueStream <- v: 19 | } 20 | } 21 | } 22 | }() 23 | return valueStream 24 | } 25 | take := func( 26 | done <-chan interface{}, 27 | valueStream <-chan interface{}, 28 | num int, 29 | ) <-chan interface{} { 30 | takeStream := make(chan interface{}) 31 | go func() { 32 | defer close(takeStream) 33 | for i := 0; i < num; i++ { 34 | select { 35 | case <-done: 36 | return 37 | case takeStream <- <-valueStream: 38 | } 39 | } 40 | }() 41 | return takeStream 42 | } 43 | toString := func( 44 | done <-chan interface{}, 45 | valueStream <-chan interface{}, 46 | ) <-chan string { 47 | stringStream := make(chan string) 48 | go func() { 49 | defer close(stringStream) 50 | for v := range valueStream { 51 | select { 52 | case <-done: 53 | return 54 | case stringStream <- v.(string): 55 | } 56 | } 57 | }() 58 | return stringStream 59 | } 60 | done := make(chan interface{}) 61 | defer close(done) 62 | 63 | b.ResetTimer() 64 | for range toString(done, take(done, repeat(done, "a"), b.N)) { 65 | } 66 | } 67 | 68 | func BenchmarkTyped(b *testing.B) { 69 | repeat := func(done <-chan interface{}, values ...string) <-chan string { 70 | valueStream := make(chan string) 71 | go func() { 72 | defer close(valueStream) 73 | for { 74 | for _, v := range values { 75 | select { 76 | case <-done: 77 | return 78 | case valueStream <- v: 79 | } 80 | } 81 | } 82 | }() 83 | return valueStream 84 | } 85 | 86 | take := func( 87 | done <-chan interface{}, 88 | valueStream <-chan string, 89 | num int, 90 | ) <-chan string { 91 | takeStream := make(chan string) 92 | go func() { 93 | defer close(takeStream) 94 | for i := num; i > 0 || i == -1; { 95 | if i != -1 { 96 | i-- 97 | } 98 | select { 99 | case <-done: 100 | return 101 | case takeStream <- <-valueStream: 102 | } 103 | } 104 | }() 105 | return takeStream 106 | } 107 | 108 | done := make(chan interface{}) 109 | defer close(done) 110 | 111 | b.ResetTimer() 112 | for range take(done, repeat(done, "a"), b.N) { 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/preventing-goroutine-leaks/fig-goroutine-leaks-cancellation.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | doWork := func( 10 | done <-chan interface{}, 11 | strings <-chan string, 12 | ) <-chan interface{} { // <1> 13 | terminated := make(chan interface{}) 14 | go func() { 15 | defer fmt.Println("doWork exited.") 16 | defer close(terminated) 17 | for { 18 | select { 19 | case s := <-strings: 20 | // Do something interesting 21 | fmt.Println(s) 22 | case <-done: // <2> 23 | return 24 | } 25 | } 26 | }() 27 | return terminated 28 | } 29 | 30 | done := make(chan interface{}) 31 | terminated := doWork(done, nil) 32 | 33 | go func() { // <3> 34 | // Cancel the operation after 1 second. 35 | time.Sleep(1 * time.Second) 36 | fmt.Println("Canceling doWork goroutine...") 37 | close(done) 38 | }() 39 | 40 | <-terminated // <4> 41 | fmt.Println("Done.") 42 | } 43 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/preventing-goroutine-leaks/fig-goroutine-leaks-example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import () 4 | 5 | func main() { 6 | doWork := func(strings <-chan string) <-chan interface{} { 7 | completed := make(chan interface{}) 8 | go func() { 9 | defer fmt.Println("doWork exited.") 10 | defer close(completed) 11 | for s := range strings { 12 | // Do something interesting 13 | fmt.Println(s) 14 | } 15 | }() 16 | return completed 17 | } 18 | 19 | doWork(nil) 20 | // Perhaps more work is done here 21 | fmt.Println("Done.") 22 | } 23 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/preventing-goroutine-leaks/fig-leak-from-blocked-channel-write-solved.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | newRandStream := func(done <-chan interface{}) <-chan int { 11 | randStream := make(chan int) 12 | go func() { 13 | defer fmt.Println("newRandStream closure exited.") 14 | defer close(randStream) 15 | for { 16 | select { 17 | case randStream <- rand.Int(): 18 | case <-done: 19 | return 20 | } 21 | } 22 | }() 23 | 24 | return randStream 25 | } 26 | 27 | done := make(chan interface{}) 28 | randStream := newRandStream(done) 29 | fmt.Println("3 random ints:") 30 | for i := 1; i <= 3; i++ { 31 | fmt.Printf("%d: %d\n", i, <-randStream) 32 | } 33 | close(done) 34 | 35 | // Simulate ongoing work 36 | time.Sleep(1 * time.Second) 37 | } 38 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/preventing-goroutine-leaks/fig-leak-from-blocked-channel-write.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | ) 7 | 8 | func main() { 9 | newRandStream := func() <-chan int { 10 | randStream := make(chan int) 11 | go func() { 12 | defer fmt.Println("newRandStream closure exited.") // <1> 13 | defer close(randStream) 14 | for { 15 | randStream <- rand.Int() 16 | } 17 | }() 18 | 19 | return randStream 20 | } 21 | 22 | randStream := newRandStream() 23 | fmt.Println("3 random ints:") 24 | for i := 1; i <= 3; i++ { 25 | fmt.Printf("%d: %d\n", i, <-randStream) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/queuing/buffering_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func BenchmarkUnbufferedWrite(b *testing.B) { 13 | performWrite(b, tmpFileOrFatal()) 14 | } 15 | 16 | func BenchmarkBufferedWrite(b *testing.B) { 17 | bufferredFile := bufio.NewWriter(tmpFileOrFatal()) 18 | performWrite(b, bufio.NewWriter(bufferredFile)) 19 | } 20 | 21 | func tmpFileOrFatal() *os.File { 22 | file, err := ioutil.TempFile("", "tmp") 23 | if err != nil { 24 | log.Fatal("error: %v", err) 25 | } 26 | return file 27 | } 28 | 29 | func performWrite(b *testing.B, writer io.Writer) { 30 | repeat := func( 31 | done <-chan interface{}, 32 | values ...interface{}, 33 | ) <-chan interface{} { 34 | valueStream := make(chan interface{}) 35 | go func() { 36 | defer close(valueStream) 37 | for { 38 | for _, v := range values { 39 | select { 40 | case <-done: 41 | return 42 | case valueStream <- v: 43 | } 44 | } 45 | } 46 | }() 47 | return valueStream 48 | } 49 | take := func( 50 | done <-chan interface{}, 51 | valueStream <-chan interface{}, 52 | num int, 53 | ) <-chan interface{} { 54 | takeStream := make(chan interface{}) 55 | go func() { 56 | defer close(takeStream) 57 | for i := 0; i < num; i++ { 58 | select { 59 | case <-done: 60 | return 61 | case takeStream <- <-valueStream: 62 | } 63 | } 64 | }() 65 | return takeStream 66 | } 67 | 68 | done := make(chan interface{}) 69 | defer close(done) 70 | 71 | b.ResetTimer() 72 | for bt := range take(done, repeat(done, byte(0)), b.N) { 73 | writer.Write([]byte{bt.(byte)}) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/the-bridge-channel/fig-bridge-channel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | orDone := func(done, c <-chan interface{}) <-chan interface{} { 9 | valStream := make(chan interface{}) 10 | go func() { 11 | defer close(valStream) 12 | for { 13 | select { 14 | case <-done: 15 | return 16 | case v, ok := <-c: 17 | if ok == false { 18 | return 19 | } 20 | select { 21 | case valStream <- v: 22 | case <-done: 23 | } 24 | } 25 | } 26 | }() 27 | return valStream 28 | } 29 | bridge := func( 30 | done <-chan interface{}, 31 | chanStream <-chan <-chan interface{}, 32 | ) <-chan interface{} { 33 | valStream := make(chan interface{}) // <1> 34 | go func() { 35 | defer close(valStream) 36 | for { // <2> 37 | var stream <-chan interface{} 38 | select { 39 | case maybeStream, ok := <-chanStream: 40 | if ok == false { 41 | return 42 | } 43 | stream = maybeStream 44 | case <-done: 45 | return 46 | } 47 | for val := range orDone(done, stream) { // <3> 48 | select { 49 | case valStream <- val: 50 | case <-done: 51 | } 52 | } 53 | } 54 | }() 55 | return valStream 56 | } 57 | genVals := func() <-chan <-chan interface{} { 58 | chanStream := make(chan (<-chan interface{})) 59 | go func() { 60 | defer close(chanStream) 61 | for i := 0; i < 10; i++ { 62 | stream := make(chan interface{}, 1) 63 | stream <- i 64 | close(stream) 65 | chanStream <- stream 66 | } 67 | }() 68 | return chanStream 69 | } 70 | 71 | for v := range bridge(nil, genVals()) { 72 | fmt.Printf("%v ", v) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/the-context-package/fig-greeter-with-context.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | var wg sync.WaitGroup 12 | ctx, cancel := context.WithCancel(context.Background()) // <1> 13 | defer cancel() 14 | 15 | wg.Add(1) 16 | go func() { 17 | defer wg.Done() 18 | 19 | if err := printGreeting(ctx); err != nil { 20 | fmt.Printf("cannot print greeting: %v\n", err) 21 | cancel() // <2> 22 | } 23 | }() 24 | 25 | wg.Add(1) 26 | go func() { 27 | defer wg.Done() 28 | if err := printFarewell(ctx); err != nil { 29 | fmt.Printf("cannot print farewell: %v\n", err) 30 | } 31 | }() 32 | 33 | wg.Wait() 34 | } 35 | 36 | func printGreeting(ctx context.Context) error { 37 | greeting, err := genGreeting(ctx) 38 | if err != nil { 39 | return err 40 | } 41 | fmt.Printf("%s world!\n", greeting) 42 | return nil 43 | } 44 | 45 | func printFarewell(ctx context.Context) error { 46 | farewell, err := genFarewell(ctx) 47 | if err != nil { 48 | return err 49 | } 50 | fmt.Printf("%s world!\n", farewell) 51 | return nil 52 | } 53 | 54 | func genGreeting(ctx context.Context) (string, error) { 55 | ctx, cancel := context.WithTimeout(ctx, 1*time.Second) // <3> 56 | defer cancel() 57 | 58 | switch locale, err := locale(ctx); { 59 | case err != nil: 60 | return "", err 61 | case locale == "EN/US": 62 | return "hello", nil 63 | } 64 | return "", fmt.Errorf("unsupported locale") 65 | } 66 | 67 | func genFarewell(ctx context.Context) (string, error) { 68 | switch locale, err := locale(ctx); { 69 | case err != nil: 70 | return "", err 71 | case locale == "EN/US": 72 | return "goodbye", nil 73 | } 74 | return "", fmt.Errorf("unsupported locale") 75 | } 76 | 77 | func locale(ctx context.Context) (string, error) { 78 | select { 79 | case <-ctx.Done(): 80 | return "", ctx.Err() // <4> 81 | case <-time.After(1 * time.Minute): 82 | } 83 | return "EN/US", nil 84 | } 85 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/the-context-package/fig-greeter-with-done-chan.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | var wg sync.WaitGroup 11 | done := make(chan interface{}) 12 | defer close(done) 13 | 14 | wg.Add(1) 15 | go func() { 16 | defer wg.Done() 17 | if err := printGreeting(done); err != nil { 18 | fmt.Printf("%v", err) 19 | return 20 | } 21 | }() 22 | 23 | wg.Add(1) 24 | go func() { 25 | defer wg.Done() 26 | if err := printFarewell(done); err != nil { 27 | fmt.Printf("%v", err) 28 | return 29 | } 30 | }() 31 | 32 | wg.Wait() 33 | } 34 | 35 | func printGreeting(done <-chan interface{}) error { 36 | greeting, err := genGreeting(done) 37 | if err != nil { 38 | return err 39 | } 40 | fmt.Printf("%s world!\n", greeting) 41 | return nil 42 | } 43 | 44 | func printFarewell(done <-chan interface{}) error { 45 | farewell, err := genFarewell(done) 46 | if err != nil { 47 | return err 48 | } 49 | fmt.Printf("%s world!\n", farewell) 50 | return nil 51 | } 52 | 53 | func genGreeting(done <-chan interface{}) (string, error) { 54 | switch locale, err := locale(done); { 55 | case err != nil: 56 | return "", err 57 | case locale == "EN/US": 58 | return "hello", nil 59 | } 60 | return "", fmt.Errorf("unsupported locale") 61 | } 62 | 63 | func genFarewell(done <-chan interface{}) (string, error) { 64 | switch locale, err := locale(done); { 65 | case err != nil: 66 | return "", err 67 | case locale == "EN/US": 68 | return "goodbye", nil 69 | } 70 | return "", fmt.Errorf("unsupported locale") 71 | } 72 | 73 | func locale(done <-chan interface{}) (string, error) { 74 | select { 75 | case <-done: 76 | return "", fmt.Errorf("canceled") 77 | case <-time.After(1 * time.Minute): 78 | } 79 | return "EN/US", nil 80 | } 81 | -------------------------------------------------------------------------------- /concurrency-patterns-in-go/the-or-channel/fig-or-channel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | var or func(channels ...<-chan interface{}) <-chan interface{} 10 | or = func(channels ...<-chan interface{}) <-chan interface{} { // <1> 11 | switch len(channels) { 12 | case 0: // <2> 13 | return nil 14 | case 1: // <3> 15 | return channels[0] 16 | } 17 | 18 | orDone := make(chan interface{}) 19 | go func() { // <4> 20 | defer close(orDone) 21 | 22 | switch len(channels) { 23 | case 2: // <5> 24 | select { 25 | case <-channels[0]: 26 | case <-channels[1]: 27 | } 28 | default: // <6> 29 | select { 30 | case <-channels[0]: 31 | case <-channels[1]: 32 | case <-channels[2]: 33 | case <-or(append(channels[3:], orDone)...): // <6> 34 | } 35 | } 36 | }() 37 | return orDone 38 | } 39 | sig := func(after time.Duration) <-chan interface{} { // <1> 40 | c := make(chan interface{}) 41 | go func() { 42 | defer close(c) 43 | time.Sleep(after) 44 | }() 45 | return c 46 | } 47 | 48 | start := time.Now() // <2> 49 | <-or( 50 | sig(2*time.Hour), 51 | sig(5*time.Minute), 52 | sig(1*time.Second), 53 | sig(1*time.Hour), 54 | sig(1*time.Minute), 55 | ) 56 | fmt.Printf("done after %v", time.Since(start)) // <3> 57 | } 58 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/channels/fig-chan-ownership.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | chanOwner := func() <-chan int { 9 | resultStream := make(chan int, 5) // <1> 10 | go func() { // <2> 11 | defer close(resultStream) // <3> 12 | for i := 0; i <= 5; i++ { 13 | resultStream <- i 14 | } 15 | }() 16 | return resultStream // <4> 17 | } 18 | 19 | resultStream := chanOwner() 20 | for result := range resultStream { // <5> 21 | fmt.Printf("Received: %d\n", result) 22 | } 23 | fmt.Println("Done receiving!") 24 | } 25 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/channels/fig-chan-recv-multi-value.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | stringStream := make(chan string) 9 | go func() { 10 | stringStream <- "Hello channels!" 11 | }() 12 | salutation, ok := <-stringStream // <1> 13 | fmt.Printf("(%v): %v", ok, salutation) 14 | } 15 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/channels/fig-iterating-over-channel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | intStream := make(chan int) 9 | go func() { 10 | defer close(intStream) // <1> 11 | for i := 1; i <= 5; i++ { 12 | intStream <- i 13 | } 14 | }() 15 | 16 | for integer := range intStream { // <2> 17 | fmt.Printf("%v ", integer) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/channels/fig-reading-from-closed-channel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | intStream := make(chan int) 9 | close(intStream) 10 | integer, ok := <-intStream // <1> 11 | fmt.Printf("(%v): %v", ok, integer) 12 | } 13 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/channels/fig-simple-chan.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | stringStream := make(chan string) 9 | go func() { 10 | stringStream <- "Hello channels!" // <1> 11 | }() 12 | fmt.Println(<-stringStream) // <2> 13 | } 14 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/channels/fig-unblocking-goroutines.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | begin := make(chan interface{}) 10 | var wg sync.WaitGroup 11 | for i := 0; i < 5; i++ { 12 | wg.Add(1) 13 | go func(i int) { 14 | defer wg.Done() 15 | <-begin // <1> 16 | fmt.Printf("%v has begun\n", i) 17 | }(i) 18 | } 19 | 20 | fmt.Println("Unblocking goroutines...") 21 | close(begin) // <2> 22 | wg.Wait() 23 | } 24 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/channels/fig-using-buffered-chans.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | var stdoutBuff bytes.Buffer // <1> 11 | defer stdoutBuff.WriteTo(os.Stdout) // <2> 12 | 13 | intStream := make(chan int, 4) // <3> 14 | go func() { 15 | defer close(intStream) 16 | defer fmt.Fprintln(&stdoutBuff, "Producer Done.") 17 | for i := 0; i < 5; i++ { 18 | fmt.Fprintf(&stdoutBuff, "Sending: %d\n", i) 19 | intStream <- i 20 | } 21 | }() 22 | 23 | for integer := range intStream { 24 | fmt.Fprintf(&stdoutBuff, "Received %v.\n", integer) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/goroutines/fig-ctx-switch_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkContextSwitch(b *testing.B) { 9 | var wg sync.WaitGroup 10 | begin := make(chan struct{}) 11 | c := make(chan struct{}) 12 | 13 | var token struct{} 14 | sender := func() { 15 | defer wg.Done() 16 | <-begin // <1> 17 | for i := 0; i < b.N; i++ { 18 | c <- token // <2> 19 | } 20 | } 21 | receiver := func() { 22 | defer wg.Done() 23 | <-begin // <1> 24 | for i := 0; i < b.N; i++ { 25 | <-c // <3> 26 | } 27 | } 28 | 29 | wg.Add(2) 30 | go sender() 31 | go receiver() 32 | b.StartTimer() // <4> 33 | close(begin) // <5> 34 | wg.Wait() 35 | } 36 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/goroutines/fig-goroutine-closure-loop-correct.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var wg sync.WaitGroup 10 | for _, salutation := range []string{"hello", "greetings", "good day"} { 11 | wg.Add(1) 12 | go func(salutation string) { // <1> 13 | defer wg.Done() 14 | fmt.Println(salutation) 15 | }(salutation) // <2> 16 | } 17 | wg.Wait() 18 | } 19 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/goroutines/fig-goroutine-closure-loop.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var wg sync.WaitGroup 10 | for _, salutation := range []string{"hello", "greetings", "good day"} { 11 | wg.Add(1) 12 | go func() { 13 | defer wg.Done() 14 | fmt.Println(salutation) // <1> 15 | }() 16 | } 17 | wg.Wait() 18 | } 19 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/goroutines/fig-goroutine-closure.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var wg sync.WaitGroup 10 | salutation := "hello" 11 | wg.Add(1) 12 | go func() { 13 | defer wg.Done() 14 | salutation = "welcome" // <1> 15 | }() 16 | wg.Wait() 17 | fmt.Println(salutation) 18 | } 19 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/goroutines/fig-goroutine-size.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync" 7 | ) 8 | 9 | func main() { 10 | memConsumed := func() uint64 { 11 | runtime.GC() 12 | var s runtime.MemStats 13 | runtime.ReadMemStats(&s) 14 | return s.Sys 15 | } 16 | 17 | var c <-chan interface{} 18 | var wg sync.WaitGroup 19 | noop := func() { wg.Done(); <-c } // <1> 20 | 21 | const numGoroutines = 1e4 // <2> 22 | wg.Add(numGoroutines) 23 | before := memConsumed() // <3> 24 | for i := numGoroutines; i > 0; i-- { 25 | go noop() 26 | } 27 | wg.Wait() 28 | after := memConsumed() // <4> 29 | fmt.Printf("%.3fkb", float64(after-before)/numGoroutines/1000) 30 | } 31 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/goroutines/fig-join-point.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var wg sync.WaitGroup 10 | sayHello := func() { 11 | defer wg.Done() 12 | fmt.Println("hello") 13 | } 14 | wg.Add(1) 15 | go sayHello() 16 | wg.Wait() // <1> 17 | } 18 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-defer-statement/fig-defer-before-panic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | defer func() { fmt.Println("before the panic") }() 9 | panic("paniced") 10 | } 11 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-select-statement/fig-select-blocking.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | start := time.Now() 10 | c := make(chan interface{}) 11 | go func() { 12 | time.Sleep(5 * time.Second) 13 | close(c) // <1> 14 | }() 15 | 16 | fmt.Println("Blocking on read...") 17 | select { 18 | case <-c: // <2> 19 | fmt.Printf("Unblocked %v later.\n", time.Since(start)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-select-statement/fig-select-default-clause.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | start := time.Now() 10 | var c1, c2 <-chan int 11 | select { 12 | case <-c1: 13 | case <-c2: 14 | default: 15 | fmt.Printf("In default after %v\n\n", time.Since(start)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-select-statement/fig-select-for-select-default.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | done := make(chan interface{}) 10 | go func() { 11 | time.Sleep(5 * time.Second) 12 | close(done) 13 | }() 14 | 15 | workCounter := 0 16 | loop: 17 | for { 18 | select { 19 | case <-done: 20 | break loop 21 | default: 22 | } 23 | 24 | // Simulate work 25 | workCounter++ 26 | time.Sleep(1 * time.Second) 27 | } 28 | 29 | fmt.Printf("Achieved %v cycles of work before signalled to stop.\n", workCounter) 30 | } 31 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-select-statement/fig-select-timeouts.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | var c <-chan int 10 | select { 11 | case <-c: // <1> 12 | case <-time.After(1 * time.Second): 13 | fmt.Println("Timed out.") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-select-statement/fig-select-uniform-distribution.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | c1 := make(chan interface{}) 9 | close(c1) 10 | c2 := make(chan interface{}) 11 | close(c2) 12 | 13 | var c1Count, c2Count int 14 | for i := 1000; i >= 0; i-- { 15 | select { 16 | case <-c1: 17 | c1Count++ 18 | case <-c2: 19 | c2Count++ 20 | } 21 | } 22 | 23 | fmt.Printf("c1Count: %d\nc2Count: %d\n", c1Count, c2Count) 24 | } 25 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-sync-package/cond/fig-cond-based-queue.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | c := sync.NewCond(&sync.Mutex{}) // <1> 11 | queue := make([]interface{}, 0, 10) // <2> 12 | 13 | removeFromQueue := func(delay time.Duration) { 14 | time.Sleep(delay) 15 | c.L.Lock() // <8> 16 | queue = queue[1:] // <9> 17 | fmt.Println("Removed from queue") 18 | c.L.Unlock() // <10> 19 | c.Signal() // <11> 20 | } 21 | 22 | for i := 0; i < 10; i++ { 23 | c.L.Lock() // <3> 24 | for len(queue) == 2 { // <4> 25 | c.Wait() // <5> 26 | } 27 | fmt.Println("Adding to queue") 28 | queue = append(queue, struct{}{}) 29 | go removeFromQueue(1 * time.Second) // <6> 30 | c.L.Unlock() // <7> 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-sync-package/cond/fig-cond-broadcast.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | type Button struct { // <1> 10 | Clicked *sync.Cond 11 | } 12 | button := Button{Clicked: sync.NewCond(&sync.Mutex{})} 13 | 14 | subscribe := func(c *sync.Cond, fn func()) { // <2> 15 | var goroutineRunning sync.WaitGroup 16 | goroutineRunning.Add(1) 17 | go func() { 18 | goroutineRunning.Done() 19 | c.L.Lock() 20 | defer c.L.Unlock() 21 | c.Wait() 22 | fn() 23 | }() 24 | goroutineRunning.Wait() 25 | } 26 | 27 | var clickRegistered sync.WaitGroup // <3> 28 | clickRegistered.Add(3) 29 | subscribe(button.Clicked, func() { // <4> 30 | fmt.Println("Maximizing window.") 31 | clickRegistered.Done() 32 | }) 33 | subscribe(button.Clicked, func() { // <5> 34 | fmt.Println("Displaying annoying dialogue box!") 35 | clickRegistered.Done() 36 | }) 37 | subscribe(button.Clicked, func() { // <6> 38 | fmt.Println("Mouse clicked.") 39 | clickRegistered.Done() 40 | }) 41 | 42 | button.Clicked.Broadcast() // <7> 43 | 44 | clickRegistered.Wait() 45 | } 46 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-sync-package/mutex-&-rwmutex/fig-mutex.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() // <1> 14 | defer lock.Unlock() // <2> 15 | count++ 16 | fmt.Printf("Incrementing: %d\n", count) 17 | } 18 | 19 | decrement := func() { 20 | lock.Lock() // <1> 21 | defer lock.Unlock() // <2> 22 | count-- 23 | fmt.Printf("Decrementing: %d\n", count) 24 | } 25 | 26 | // Increment 27 | var arithmetic sync.WaitGroup 28 | for i := 0; i <= 5; i++ { 29 | arithmetic.Add(1) 30 | go func() { 31 | defer arithmetic.Done() 32 | increment() 33 | }() 34 | } 35 | 36 | // Decrement 37 | for i := 0; i <= 5; i++ { 38 | arithmetic.Add(1) 39 | go func() { 40 | defer arithmetic.Done() 41 | decrement() 42 | }() 43 | } 44 | 45 | arithmetic.Wait() 46 | fmt.Println("Arithmetic complete.") 47 | } 48 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-sync-package/mutex-&-rwmutex/fig-rwlock.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) { // <1> 14 | defer wg.Done() 15 | for i := 5; i > 0; i-- { 16 | l.Lock() 17 | l.Unlock() 18 | time.Sleep(1) // <2> 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, rwMutex) 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 | 44 | var m sync.RWMutex 45 | fmt.Fprintf(tw, "Readers\tRWMutext\tMutex\n") 46 | for i := 0; i < 20; i++ { 47 | count := int(math.Pow(2, float64(i))) 48 | fmt.Fprintf( 49 | tw, 50 | "%d\t%v\t%v\n", 51 | count, 52 | test(count, &m, m.RLocker()), 53 | test(count, &m, &m), 54 | ) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-sync-package/once/fig-sync-once-diff-funcs.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 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-sync-package/once/fig-sync-once-do-deadlock.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | func main() { 8 | var onceA, onceB sync.Once 9 | var initB func() 10 | initA := func() { onceB.Do(initB) } 11 | initB = func() { onceA.Do(initA) } // <1> 12 | onceA.Do(initA) // <2> 13 | } 14 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-sync-package/once/fig-sync-once.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 | 15 | var once sync.Once 16 | 17 | var increments sync.WaitGroup 18 | increments.Add(100) 19 | for i := 0; i < 100; i++ { 20 | go func() { 21 | defer increments.Done() 22 | once.Do(increment) 23 | }() 24 | } 25 | 26 | increments.Wait() 27 | fmt.Printf("Count is %d\n", count) 28 | } 29 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-sync-package/once/sync-once-go-codebase.sh: -------------------------------------------------------------------------------- 1 | grep -ir sync.Once $(go env GOROOT)/src |wc -l 2 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-sync-package/pool/fig-benchmark-fast-network-service_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "net" 8 | "sync" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func connectToService() interface{} { 14 | time.Sleep(1 * time.Second) 15 | return struct{}{} 16 | } 17 | func warmServiceConnCache() *sync.Pool { 18 | p := &sync.Pool{ 19 | New: connectToService, 20 | } 21 | for i := 0; i < 10; i++ { 22 | p.Put(p.New()) 23 | } 24 | return p 25 | } 26 | 27 | func startNetworkDaemon() *sync.WaitGroup { 28 | var wg sync.WaitGroup 29 | wg.Add(1) 30 | go func() { 31 | connPool := warmServiceConnCache() 32 | 33 | server, err := net.Listen("tcp", "localhost:8080") 34 | if err != nil { 35 | log.Fatalf("cannot listen: %v", err) 36 | } 37 | defer server.Close() 38 | 39 | wg.Done() 40 | 41 | for { 42 | conn, err := server.Accept() 43 | if err != nil { 44 | log.Printf("cannot accept connection: %v", err) 45 | continue 46 | } 47 | svcConn := connPool.Get() 48 | fmt.Fprintln(conn, "") 49 | connPool.Put(svcConn) 50 | conn.Close() 51 | } 52 | }() 53 | return &wg 54 | } 55 | func init() { 56 | daemonStarted := startNetworkDaemon() 57 | daemonStarted.Wait() 58 | } 59 | 60 | func BenchmarkNetworkRequest(b *testing.B) { 61 | for i := 0; i < b.N; i++ { 62 | conn, err := net.Dial("tcp", "localhost:8080") 63 | if err != nil { 64 | b.Fatalf("cannot dial host: %v", err) 65 | } 66 | if _, err := ioutil.ReadAll(conn); err != nil { 67 | b.Fatalf("cannot read: %v", err) 68 | } 69 | conn.Close() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-sync-package/pool/fig-benchmark-slow-network-service_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "net" 8 | "sync" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func connectToService() interface{} { 14 | time.Sleep(1 * time.Second) 15 | return struct{}{} 16 | } 17 | func startNetworkDaemon() *sync.WaitGroup { 18 | var wg sync.WaitGroup 19 | wg.Add(1) 20 | go func() { 21 | server, err := net.Listen("tcp", "localhost:8080") 22 | if err != nil { 23 | log.Fatalf("cannot listen: %v", err) 24 | } 25 | defer server.Close() 26 | 27 | wg.Done() 28 | 29 | for { 30 | conn, err := server.Accept() 31 | if err != nil { 32 | log.Printf("cannot accept connection: %v", err) 33 | continue 34 | } 35 | connectToService() 36 | fmt.Fprintln(conn, "") 37 | conn.Close() 38 | } 39 | }() 40 | return &wg 41 | } 42 | func init() { 43 | daemonStarted := startNetworkDaemon() 44 | daemonStarted.Wait() 45 | } 46 | 47 | func BenchmarkNetworkRequest(b *testing.B) { 48 | for i := 0; i < b.N; i++ { 49 | conn, err := net.Dial("tcp", "localhost:8080") 50 | if err != nil { 51 | b.Fatalf("cannot dial host: %v", err) 52 | } 53 | if _, err := ioutil.ReadAll(conn); err != nil { 54 | b.Fatalf("cannot read: %v", err) 55 | } 56 | conn.Close() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-sync-package/pool/fig-sync-pool-basic.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() // <1> 17 | instance := myPool.Get() // <1> 18 | myPool.Put(instance) // <2> 19 | myPool.Get() // <3> 20 | } 21 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-sync-package/pool/fig-sync-pool.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var numCalcsCreated int 10 | calcPool := &sync.Pool{ 11 | New: func() interface{} { 12 | numCalcsCreated += 1 13 | mem := make([]byte, 1024) 14 | return &mem // <1> 15 | }, 16 | } 17 | 18 | // Seed the pool with 4KB 19 | calcPool.Put(calcPool.New()) 20 | calcPool.Put(calcPool.New()) 21 | calcPool.Put(calcPool.New()) 22 | calcPool.Put(calcPool.New()) 23 | 24 | const numWorkers = 1024 * 1024 25 | var wg sync.WaitGroup 26 | wg.Add(numWorkers) 27 | for i := numWorkers; i > 0; i-- { 28 | go func() { 29 | defer wg.Done() 30 | 31 | mem := calcPool.Get().(*[]byte) // <2> 32 | defer calcPool.Put(mem) 33 | 34 | // Assume something interesting, but quick is being done with 35 | // this memory. 36 | }() 37 | } 38 | 39 | wg.Wait() 40 | fmt.Printf("%d calculators were created.", numCalcsCreated) 41 | } 42 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-sync-package/waitgroup/fig-bulk-add.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | hello := func(wg *sync.WaitGroup, id int) { 10 | defer wg.Done() 11 | fmt.Printf("Hello from %v!\n", id) 12 | } 13 | 14 | const numGreeters = 5 15 | var wg sync.WaitGroup 16 | wg.Add(numGreeters) 17 | for i := 0; i < numGreeters; i++ { 18 | go hello(&wg, i+1) 19 | } 20 | wg.Wait() 21 | } 22 | -------------------------------------------------------------------------------- /gos-concurrency-building-blocks/the-sync-package/waitgroup/fig-wait-group.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | var wg sync.WaitGroup 11 | 12 | wg.Add(1) // <1> 13 | go func() { 14 | defer wg.Done() // <2> 15 | fmt.Println("1st goroutine sleeping...") 16 | time.Sleep(1) 17 | }() 18 | 19 | wg.Add(1) // <1> 20 | go func() { 21 | defer wg.Done() // <2> 22 | fmt.Println("2nd goroutine sleeping...") 23 | time.Sleep(2) 24 | }() 25 | 26 | wg.Wait() // <3> 27 | fmt.Println("All goroutines complete.") 28 | } 29 | -------------------------------------------------------------------------------- /notes/dead-writing/livelocks/livelock-example-fix.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | type value struct { 11 | sync.Mutex 12 | id string 13 | locked bool 14 | value int 15 | } 16 | 17 | lock := func(v *value) { 18 | v.Lock() 19 | v.locked = true 20 | } 21 | unlock := func(v *value) { 22 | v.Unlock() 23 | v.locked = false 24 | } 25 | printSum := func(wg *sync.WaitGroup, id string, v1, v2 *value) { 26 | defer wg.Done() 27 | var sum int 28 | for i := 0; ; i++ { // <4> 29 | if i >= 5 { 30 | fmt.Println("canceling goroutine...") 31 | return 32 | } 33 | 34 | fmt.Printf("%v: acquiring lock on %v\n", id, v1.id) 35 | lock(v1) // <1> 36 | 37 | time.Sleep(2 * time.Second) 38 | 39 | if v2.locked { // <2> 40 | fmt.Printf("%v: releasing lock on %v\n", id, v1.id) 41 | unlock(v1) // <3> 42 | fmt.Printf("%v: %v locked, retrying\n", id, v2.id) 43 | continue 44 | } 45 | 46 | fmt.Printf("%v: acquiring lock on %v\n", id, v2.id) 47 | lock(v2) 48 | 49 | sum = v1.value + v2.value 50 | fmt.Printf("%v: releasing lock on %v\n", id, v1.id) 51 | unlock(v1) 52 | 53 | fmt.Printf("%v: releasing lock on %v\n", id, v2.id) 54 | unlock(v2) 55 | break 56 | } 57 | 58 | fmt.Printf("sum: %v\n", sum) 59 | } 60 | a, b := value{id: "a"}, value{id: "b"} 61 | var wg sync.WaitGroup 62 | wg.Add(2) 63 | go printSum(&wg, "first", &a, &b) 64 | go printSum(&wg, "second", &a, &b) // <1> 65 | 66 | wg.Wait() 67 | } 68 | -------------------------------------------------------------------------------- /notes/dead-writing/livelocks/livelock-example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | type value struct { 11 | sync.Mutex 12 | id string 13 | locked bool 14 | value int 15 | } 16 | 17 | lock := func(v *value) { 18 | v.Lock() 19 | v.locked = true 20 | } 21 | unlock := func(v *value) { 22 | v.Unlock() 23 | v.locked = false 24 | } 25 | printSum := func(wg *sync.WaitGroup, id string, v1, v2 *value) { 26 | defer wg.Done() 27 | var sum int 28 | for i := 0; ; i++ { // <4> 29 | if i >= 5 { 30 | fmt.Println("canceling goroutine...") 31 | return 32 | } 33 | 34 | fmt.Printf("%v: acquiring lock on %v\n", id, v1.id) 35 | lock(v1) // <1> 36 | 37 | time.Sleep(2 * time.Second) 38 | 39 | if v2.locked { // <2> 40 | fmt.Printf("%v: releasing lock on %v\n", id, v1.id) 41 | unlock(v1) // <3> 42 | fmt.Printf("%v: %v locked, retrying\n", id, v2.id) 43 | continue 44 | } 45 | 46 | fmt.Printf("%v: acquiring lock on %v\n", id, v2.id) 47 | lock(v2) 48 | 49 | sum = v1.value + v2.value 50 | fmt.Printf("%v: releasing lock on %v\n", id, v1.id) 51 | unlock(v1) 52 | 53 | fmt.Printf("%v: releasing lock on %v\n", id, v2.id) 54 | unlock(v2) 55 | break 56 | } 57 | 58 | fmt.Printf("sum: %v\n", sum) 59 | } 60 | a, b := value{id: "a"}, value{id: "b"} 61 | var wg sync.WaitGroup 62 | wg.Add(2) 63 | go printSum(&wg, "first", &a, &b) 64 | go printSum(&wg, "second", &b, &a) 65 | 66 | wg.Wait() 67 | } 68 | --------------------------------------------------------------------------------