├── 1-1-basics.go ├── 1-2-channel.go ├── 1-3-generator.go ├── 1-4-lockstep.go ├── 1-5-fanin.go ├── 1-6-restoring-sequencing.go ├── 1-7-select-fanin.go ├── 1-8-timeout-select.go ├── 1-9-timeout-direct-select.go ├── 2-1-quit-select.go ├── 2-2-receive-quit.go ├── 2-3-daisy-chain.go ├── 2-4-google-search.go └── README.md /1-1-basics.go: -------------------------------------------------------------------------------- 1 | // Kevin Chen (2017) 2 | // Patterns from Pike's Google I/O talk, "Go Concurrency Patterns" 3 | 4 | // Exposition of Golang's concurrency primitives 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | go regular_print("Hello") 15 | fmt.Println("Second print statement!") 16 | time.Sleep(3 * time.Second) 17 | fmt.Println("Third print statement!") // when main returns, the goroutines also end 18 | } 19 | 20 | func regular_print(msg string) { 21 | for i := 0; ; i++ { 22 | fmt.Println(msg, i) 23 | time.Sleep(time.Second) 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /1-2-channel.go: -------------------------------------------------------------------------------- 1 | // Kevin Chen (2017) 2 | // Patterns from Pike's Google I/O talk, "Go Concurrency Patterns" 3 | 4 | // Golang channels 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | ch := make(chan string) 15 | go channel_print("Hello", ch) 16 | for i := 0; i < 3; i++ { 17 | fmt.Println(<-ch) // ends of channel block until both are ready 18 | // NOTE: golang supports buffered channels, like mailboxes (no sync) 19 | } 20 | fmt.Println("Done!") 21 | } 22 | 23 | func channel_print(msg string, ch chan<- string) { 24 | for i := 0; ; i++ { 25 | ch <- fmt.Sprintf("%s %d", msg, i) 26 | time.Sleep(time.Second) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /1-3-generator.go: -------------------------------------------------------------------------------- 1 | // Kevin Chen (2017) 2 | // Patterns from Pike's Google I/O talk, "Go Concurrency Patterns" 3 | 4 | // Golang generator pattern: functions that return channels 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "time" 11 | ) 12 | 13 | // goroutine is launched inside the called function (more idiomatic) 14 | // multiple instances of the generator may be called 15 | 16 | func main() { 17 | ch := generator("Hello") 18 | for i := 0; i < 5; i++ { 19 | fmt.Println(<- ch) 20 | } 21 | } 22 | 23 | func generator(msg string) <-chan string { // returns receive-only channel 24 | ch := make(chan string) 25 | go func() { // anonymous goroutine 26 | for i := 0; ; i++ { 27 | ch <- fmt.Sprintf("%s %d", msg, i) 28 | time.Sleep(time.Second) 29 | } 30 | }() 31 | return ch 32 | } -------------------------------------------------------------------------------- /1-4-lockstep.go: -------------------------------------------------------------------------------- 1 | // Kevin Chen (2017) 2 | // Patterns from Pike's Google I/O talk, "Go Concurrency Patterns" 3 | 4 | // Golang channels as handles on a service (working in lockstep) 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | ch1 := generator("Hello") 15 | ch2 := generator("Bye") 16 | for i := 0; i < 5; i++ { 17 | fmt.Println(<- ch1) 18 | fmt.Println(<- ch2) 19 | } 20 | } 21 | 22 | func generator(msg string) <-chan string { // returns receive-only channel 23 | ch := make(chan string) 24 | go func() { // anonymous goroutine 25 | for i := 0; ; i++ { 26 | ch <- fmt.Sprintf("%s %d", msg, i) 27 | time.Sleep(time.Second) 28 | } 29 | }() 30 | return ch 31 | } -------------------------------------------------------------------------------- /1-5-fanin.go: -------------------------------------------------------------------------------- 1 | // Kevin Chen (2017) 2 | // Patterns from Pike's Google I/O talk, "Go Concurrency Patterns" 3 | 4 | // Golang multiplexing (fan-in) function to allow multiple channels go through one channel 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | ch := fanIn(generator("Hello"), generator("Bye")) 15 | for i := 0; i < 10; i++ { 16 | fmt.Println(<- ch) 17 | } 18 | } 19 | 20 | // fanIn is itself a generator 21 | func fanIn(ch1, ch2 <-chan string) <-chan string { // receives two read-only channels 22 | new_ch := make(chan string) 23 | go func() { for { new_ch <- <-ch1 } }() // launch two goroutine while loops to continuously pipe to new channel 24 | go func() { for { new_ch <- <-ch2 } }() 25 | return new_ch 26 | } 27 | 28 | func generator(msg string) <-chan string { // returns receive-only channel 29 | ch := make(chan string) 30 | go func() { // anonymous goroutine 31 | for i := 0; ; i++ { 32 | ch <- fmt.Sprintf("%s %d", msg, i) 33 | time.Sleep(time.Second) 34 | } 35 | }() 36 | return ch 37 | } -------------------------------------------------------------------------------- /1-6-restoring-sequencing.go: -------------------------------------------------------------------------------- 1 | // Kevin Chen (2017) 2 | // Patterns from Pike's Google I/O talk, "Go Concurrency Patterns" 3 | 4 | // Golang restoring sequencing after multiplexing 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "time" 11 | ) 12 | 13 | type Message struct { 14 | str string 15 | block chan int 16 | } 17 | 18 | func main() { 19 | ch := fanIn(generator("Hello"), generator("Bye")) 20 | for i := 0; i < 10; i++ { 21 | msg1 := <-ch 22 | fmt.Println(msg1.str) 23 | 24 | msg2 := <-ch 25 | fmt.Println(msg2.str) 26 | 27 | <- msg1.block // reset channel, stop blocking 28 | <- msg2.block 29 | } 30 | } 31 | 32 | // fanIn is itself a generator 33 | func fanIn(ch1, ch2 <-chan Message) <-chan Message { // receives two read-only channels 34 | new_ch := make(chan Message) 35 | go func() { for { new_ch <- <-ch1 } }() // launch two goroutine while loops to continuously pipe to new channel 36 | go func() { for { new_ch <- <-ch2 } }() 37 | return new_ch 38 | } 39 | 40 | func generator(msg string) <-chan Message { // returns receive-only channel 41 | ch := make(chan Message) 42 | blockingStep := make(chan int) // channel within channel to control exec, set false default 43 | go func() { // anonymous goroutine 44 | for i := 0; ; i++ { 45 | ch <- Message{fmt.Sprintf("%s %d", msg, i), blockingStep} 46 | time.Sleep(time.Second) 47 | blockingStep <- 1 // block by waiting for input 48 | } 49 | }() 50 | return ch 51 | } -------------------------------------------------------------------------------- /1-7-select-fanin.go: -------------------------------------------------------------------------------- 1 | // Kevin Chen (2017) 2 | // Patterns from Pike's Google I/O talk, "Go Concurrency Patterns" 3 | 4 | // Select is a control structure for concurrency (why channels/goroutines are built in; not library) 5 | // Based off of Dijkstra's guarded commands... providing an idiomatic way for concurrent processes to 6 | // pass in data without programmer having to worry about 'steps' 7 | 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "time" 13 | ) 14 | 15 | func main() { 16 | ch := fanIn(generator("Hello"), generator("Bye")) 17 | for i := 0; i < 10; i++ { 18 | fmt.Println(<- ch) 19 | } 20 | } 21 | 22 | // fanIn is itself a generator 23 | func fanIn(ch1, ch2 <-chan string) <-chan string { // receives two read-only channels 24 | new_ch := make(chan string) 25 | go func() { 26 | for { 27 | select { 28 | case s := <-ch1: new_ch <- s 29 | case s := <-ch2: new_ch <- s 30 | } 31 | } 32 | }() 33 | return new_ch 34 | } 35 | 36 | func generator(msg string) <-chan string { // returns receive-only channel 37 | ch := make(chan string) 38 | go func() { // anonymous goroutine 39 | for i := 0; ; i++ { 40 | ch <- fmt.Sprintf("%s %d", msg, i) 41 | time.Sleep(time.Second) 42 | } 43 | }() 44 | return ch 45 | } -------------------------------------------------------------------------------- /1-8-timeout-select.go: -------------------------------------------------------------------------------- 1 | // Kevin Chen (2017) 2 | // Patterns from Pike's Google I/O talk, "Go Concurrency Patterns" 3 | 4 | // In non deterministic select control block, 1 second timer (created each iteration) may 5 | // time out if channel does not return a string in a second 6 | 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "time" 12 | ) 13 | 14 | func main() { 15 | ch := generator("Hi!") 16 | for i := 0; i < 10; i++ { 17 | select { 18 | case s := <-ch: 19 | fmt.Println(s) 20 | case <-time.After(1 * time.Second): // time.After returns a channel that waits N time to send a message 21 | fmt.Println("Waited too long!") 22 | return 23 | } 24 | } 25 | } 26 | 27 | func generator(msg string) <-chan string { // returns receive-only channel 28 | ch := make(chan string) 29 | go func() { // anonymous goroutine 30 | for i := 0; ; i++ { 31 | ch <- fmt.Sprintf("%s %d", msg, i) 32 | time.Sleep(time.Second) 33 | } 34 | }() 35 | return ch 36 | } 37 | -------------------------------------------------------------------------------- /1-9-timeout-direct-select.go: -------------------------------------------------------------------------------- 1 | // Kevin Chen (2017) 2 | // Patterns from Pike's Google I/O talk, "Go Concurrency Patterns" 3 | 4 | // Global timer returns after 5 seconds, stopping execution in select control block 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | ch := generator("Hi!") 15 | timeout := time.After(5 * time.Second) 16 | for i := 0; i < 10; i++ { 17 | select { 18 | case s := <-ch: 19 | fmt.Println(s) 20 | case <-timeout: // time.After returns a channel that waits N time to send a message 21 | fmt.Println("5s Timeout!") 22 | return 23 | } 24 | } 25 | } 26 | 27 | func generator(msg string) <-chan string { // returns receive-only channel 28 | ch := make(chan string) 29 | go func() { // anonymous goroutine 30 | for i := 0; ; i++ { 31 | ch <- fmt.Sprintf("%s %d", msg, i) 32 | time.Sleep(time.Second) 33 | } 34 | }() 35 | return ch 36 | } 37 | -------------------------------------------------------------------------------- /2-1-quit-select.go: -------------------------------------------------------------------------------- 1 | // Kevin Chen (2017) 2 | // Patterns from Pike's Google I/O talk, "Go Concurrency Patterns" 3 | 4 | // Deterministically quit goroutine with quit channel option in select 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "math/rand" 11 | ) 12 | 13 | func main() { 14 | quit := make(chan bool) 15 | ch := generator("Hi!", quit) 16 | for i := rand.Intn(50); i >= 0; i-- { 17 | fmt.Println(<-ch, i) 18 | } 19 | quit <- true 20 | } 21 | 22 | func generator(msg string, quit chan bool) <-chan string { // returns receive-only channel 23 | ch := make(chan string) 24 | go func() { // anonymous goroutine 25 | for { 26 | select { 27 | case ch <- fmt.Sprintf("%s", msg): 28 | // nothing 29 | case <-quit: 30 | fmt.Println("Goroutine done") 31 | return 32 | } 33 | } 34 | }() 35 | return ch 36 | } 37 | -------------------------------------------------------------------------------- /2-2-receive-quit.go: -------------------------------------------------------------------------------- 1 | // Kevin Chen (2017) 2 | // Patterns from Pike's Google I/O talk, "Go Concurrency Patterns" 3 | 4 | // Deterministically quit goroutine with quit channel option in select 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "math/rand" 11 | ) 12 | 13 | func main() { 14 | quit := make(chan string) 15 | ch := generator("Hi!", quit) 16 | for i := rand.Intn(10); i >= 0; i-- { 17 | fmt.Println(<-ch, i) 18 | } 19 | quit <- "Bye!" 20 | fmt.Printf("Generator says %s", <-quit) 21 | } 22 | 23 | func generator(msg string, quit chan string) <-chan string { // returns receive-only channel 24 | ch := make(chan string) 25 | go func() { // anonymous goroutine 26 | for { 27 | select { 28 | case ch <- fmt.Sprintf("%s", msg): 29 | // nothing 30 | case <-quit: 31 | quit <- "See you!" 32 | return 33 | } 34 | } 35 | }() 36 | return ch 37 | } 38 | -------------------------------------------------------------------------------- /2-3-daisy-chain.go: -------------------------------------------------------------------------------- 1 | // Kevin Chen (2017) 2 | // Patterns from Pike's Google I/O talk, "Go Concurrency Patterns" 3 | 4 | // Daisy chaining goroutines... all routines at once, so if one is fulfilled 5 | // everything after should also have their blocking commitment (input) fulfilled 6 | 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | ) 12 | 13 | // takes two int channels, stores right val (+1) into left 14 | func f(left, right chan int) { 15 | left <- 1 + <-right // bafter 1st right read, locks until left read 16 | } 17 | 18 | func main() { 19 | const n = 10000 20 | 21 | // construct an array of n+1 int channels 22 | var channels [n + 1]chan int 23 | for i := range channels { 24 | channels[i] = make(chan int) 25 | } 26 | 27 | // wire n goroutines in a chain 28 | for i := 0; i < n; i++ { 29 | go f(channels[i], channels[i+1]) 30 | } 31 | 32 | // insert a value into right-hand end 33 | go func(c chan<- int) { c <- 1 }(channels[n]) 34 | 35 | // get value from the left-hand end 36 | fmt.Println(<-channels[0]) 37 | } 38 | -------------------------------------------------------------------------------- /2-4-google-search.go: -------------------------------------------------------------------------------- 1 | // Kevin Chen (2017) 2 | // Patterns from Pike's Google I/O talk, "Go Concurrency Patterns" 3 | 4 | // Using go patterns with experiment 'Google Search' i.e concurrent goroutines getting results 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "math/rand" 11 | "time" 12 | ) 13 | 14 | var ( 15 | Web = fakeSearch("web") 16 | Web2 = fakeSearch("web") 17 | Image = fakeSearch("image") 18 | Image2 = fakeSearch("image") 19 | Video = fakeSearch("video") 20 | Video2 = fakeSearch("video") 21 | ) 22 | 23 | type Result string 24 | type Search func(query string) Result 25 | 26 | func main() { 27 | rand.Seed(time.Now().UnixNano()) 28 | start := time.Now() 29 | results := Google("golang") // collate results 30 | elapsed := time.Since(start) 31 | fmt.Println(results) 32 | fmt.Println(elapsed) 33 | } 34 | 35 | func fakeSearch(kind string) Search { 36 | return func(query string) Result { 37 | time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) 38 | return Result(fmt.Sprintf("%s result for %q\n", kind, query)) 39 | } 40 | } 41 | 42 | func First(query string, replicas ...Search) Result { 43 | c := make(chan Result) 44 | searchReplica := func(i int) { c <- replicas[i](query) } 45 | for i := range replicas { 46 | go searchReplica(i) 47 | } 48 | return <-c 49 | } 50 | 51 | func Google(query string) (results []Result) { 52 | c := make(chan Result) 53 | go func() { c <- First(query, Web, Web2) }() 54 | go func() { c <- First(query, Image, Image2) }() 55 | go func() { c <- First(query, Video, Video2) }() 56 | 57 | timeout := time.After(80 * time.Millisecond) 58 | 59 | for i := 0; i < 3; i++ { 60 | select { 61 | case result := <-c: 62 | results = append(results, result) 63 | case <-timeout: 64 | fmt.Println("timed out") 65 | return 66 | } 67 | } 68 | return 69 | } 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang Concurrency Patterns 2 | Common and useful golang concurrency patterns I implemented from Rob Pike's famous 2012 Google I/O talk. 3 | 4 | [Google I/O Talk](https://www.youtube.com/watch?v=f6kdp27TYZs&t=1021s) 5 | 6 | ### Common Patterns 7 | - Generator: function that runs goroutine and returns channel 8 | - Multiplexing (fan-in): function that takes multiple channels and pipes to one channel, so that the returned channel receives both outputs 9 | - Daisychaining: functions whose I/O are daisy-chained with channels together 10 | --------------------------------------------------------------------------------