├── .travis.yml ├── README.md ├── coop.go └── coop_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 1.2 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # coop 2 | 3 | [![Build Status](https://travis-ci.org/rakyll/coop.png?branch=master)](https://travis-ci.org/rakyll/coop) 4 | 5 | Note: This package became obsolete. I started it when I was learning Go a couple of years ago. I see so many better ways to implement them all now, so don't keep using this package as an ultimate reference. 6 | 7 | coop contains some of the most common concurrent program flows I personally use in Go. I'm suggesting you to use this package as a snippets reference/cheat sheet instead of a library. The functionally provided in this package can be obtained in many different ways, and frankly with more performant implementations depending on the type of your problem. 8 | 9 | coop contains implementations for the following flows: 10 | 11 | ### coop.At(time, fn) 12 | 13 | Runs fn at t, returns a boolean channel that will receive a message after fn returns. The following example, prints "Hello World" in a minute and blocks the goroutine its running in until fn is completed. 14 | 15 | ~~~ go 16 | done := coop.At(time.Now().Add(time.Minute), func() { 17 | fmt.Println("Hello world") 18 | }) 19 | <-done // wait for fn to be done 20 | ~~~ 21 | 22 | ### coop.Until(time, duration, fn) 23 | 24 | Runs fn once in every provided duration until t, returns a boolean channel that will receive a message after fn returns. The following example prints "Hello world" every minute until tomorrow, and blocks the goroutine its running in until the job is completed. 25 | 26 | ~~~ go 27 | done := coop.Until(time.Now().Add(24*time.Hour), time.Minute, func() { 28 | fmt.Println("Hello world") 29 | }) 30 | <-done 31 | ~~~ 32 | 33 | ### coop.After(duration, fn) 34 | 35 | Runs fn after duration, returns a boolean channel that will receive a message after fn returns. The following example prints "Hello world" after a second and blocks until fn is completed. 36 | 37 | ~~~ go 38 | done := coop.After(time.Second, func() { 39 | fmt.Println("Hello world") 40 | }) 41 | <-done 42 | ~~~ 43 | 44 | ### coop.Every(duration, fn) 45 | 46 | Runs fn once in every duration, and never stops. The following example will print "Hello World" once in every second. 47 | 48 | ~~~ go 49 | coop.Every(time.Second, func() { 50 | fmt.Println("Hello world") 51 | }) 52 | ~~~ 53 | 54 | ### coop.Timeout(duration, fn) 55 | Runs fn, and cancels the running job if timeout is exceeded. The following example will timeout and fn will return immediately ("Hello world will not printed"), the value read from the done channel will be false if timeout occurs, true if fn is completed. 56 | 57 | ~~~ go 58 | done := coop.Timeout(time.Second, func() { 59 | time.Sleep(time.Hour) 60 | fmt.Println("Hello world") 61 | }) 62 | <-done // will return false, because timeout occurred 63 | ~~~ 64 | 65 | ### coop.All(fns...) 66 | Runs the list of fns concurrently, returns a boolean channel that will receive a message after all of the fns are completed. The following example will start 4 printing jobs concurrently and wait until all of them are completed. 67 | 68 | ~~~ go 69 | printFn := func() { 70 | fmt.Println("Hello world") 71 | } 72 | <-coop.All(printFn, printFn, printFn, printFn) 73 | ~~~ 74 | 75 | ### coop.AllWithThrottle(num, fns...) 76 | Similar to coop.All, but with limiting. Runs the list of fns concurrently, but at most num fns at a time. Returns a boolean channel that will receive a message after all of the fns are completed. The following example will start 3 printing jobs immediately, and run the left out one once the first 3 is completed. It will block the goroutine until all 4 are finished. 77 | 78 | ~~~ go 79 | printFn := func() { 80 | fmt.Println("Hello world") 81 | } 82 | <-coop.AllWithThrottle(3, printFn, printFn, printFn, printFn) 83 | ~~~ 84 | 85 | ### coop.Replicate(n, fn) 86 | 87 | Runs fn n time concurrently, returns a boolean channel that indicates all runs are completed. The following example prints "Hello world" 5 times, and waits for all printing jobs are finished. 88 | 89 | ~~~ go 90 | <-coop.Replicate(5, func() { 91 | fmt.Println("Hello world") 92 | }) 93 | ~~~ 94 | 95 | ## License 96 | 97 | Copyright 2014 Google Inc. All Rights Reserved. 98 | 99 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 100 | 101 | http://www.apache.org/licenses/LICENSE-2.0 102 | 103 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ![Analytics](https://ga-beacon.appspot.com/UA-46881978-1/coop?pixel) 104 | -------------------------------------------------------------------------------- /coop.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // coop contains some of the most commonly used concurrent 16 | // flows. This package is mostly around for reference. even though 17 | // you can use it as a library, I'd suggest you to implement your own 18 | // control mechanisms. 19 | package coop 20 | 21 | import ( 22 | "sync" 23 | "time" 24 | ) 25 | 26 | // Runs fn at the specified time. 27 | func At(t time.Time, fn func()) (done <-chan bool) { 28 | return After(t.Sub(time.Now()), fn) 29 | } 30 | 31 | // Runs until time in every dur. 32 | func Until(t time.Time, dur time.Duration, fn func()) (done <-chan bool) { 33 | ch := make(chan bool, 1) 34 | untilRecv(ch, t, dur, fn) 35 | return ch 36 | } 37 | 38 | func untilRecv(ch chan bool, t time.Time, dur time.Duration, fn func()) { 39 | if t.Sub(time.Now()) > 0 { 40 | time.AfterFunc(dur, func() { 41 | fn() 42 | untilRecv(ch, t, dur, fn) 43 | }) 44 | return 45 | } 46 | doneSig(ch, true) 47 | } 48 | 49 | // Runs fn after duration. Similar to time.AfterFunc 50 | func After(duration time.Duration, fn func()) (done <-chan bool) { 51 | ch := make(chan bool, 1) 52 | time.AfterFunc(duration, func() { 53 | fn() 54 | doneSig(ch, true) 55 | }) 56 | return ch 57 | } 58 | 59 | // Runs fn in every specified duration. 60 | func Every(dur time.Duration, fn func()) { 61 | time.AfterFunc(dur, func() { 62 | fn() 63 | Every(dur, fn) 64 | }) 65 | } 66 | 67 | // Runs fn and times out if it runs longer than the provided 68 | // duration. It will send false to the returning 69 | // channel if timeout occurs. 70 | // TODO: cancel if timeout occurs 71 | func Timeout(duration time.Duration, fn func()) (done <-chan bool) { 72 | ch := make(chan bool, 2) 73 | go func() { 74 | <-time.After(duration) 75 | doneSig(ch, false) 76 | }() 77 | go func() { 78 | fn() 79 | doneSig(ch, true) 80 | }() 81 | return ch 82 | } 83 | 84 | // Starts to run the given list of fns concurrently. 85 | func All(fns ...func()) (done <-chan bool) { 86 | var wg sync.WaitGroup 87 | wg.Add(len(fns)) 88 | 89 | ch := make(chan bool, 1) 90 | for _, fn := range fns { 91 | go func(f func()) { 92 | f() 93 | wg.Done() 94 | }(fn) 95 | } 96 | go func() { 97 | wg.Wait() 98 | doneSig(ch, true) 99 | }() 100 | return ch 101 | } 102 | 103 | // Starts to run the given list of fns concurrently, 104 | // at most n fns at a time. 105 | func AllWithThrottle(throttle int, fns ...func()) (done <-chan bool) { 106 | ch := make(chan bool, 1) 107 | go func() { 108 | for { 109 | num := throttle 110 | if throttle > len(fns) { 111 | num = len(fns) 112 | } 113 | next := fns[:num] 114 | fns = fns[num:] 115 | <-All(next...) 116 | if len(fns) == 0 { 117 | doneSig(ch, true) 118 | break 119 | } 120 | } 121 | }() 122 | return ch 123 | } 124 | 125 | // Run the same function with n copies. 126 | func Replicate(n int, fn func()) (done <-chan bool) { 127 | funcs := make([]func(), n) 128 | for i := 0; i < n; i++ { 129 | funcs[i] = fn 130 | } 131 | return All(funcs...) 132 | } 133 | 134 | func doneSig(ch chan bool, val bool) { 135 | ch <- val 136 | close(ch) 137 | } 138 | -------------------------------------------------------------------------------- /coop_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package coop 16 | 17 | import ( 18 | "testing" 19 | "time" 20 | ) 21 | 22 | func TestAt_Future(test *testing.T) { 23 | start := time.Now() 24 | done := At(start.Add(100*time.Millisecond), func() { 25 | diff := time.Now().Sub(start) 26 | if diff > 105*time.Millisecond { 27 | test.Errorf("Expected to run in 100 ms, it did in %v", diff) 28 | } 29 | }) 30 | <-done 31 | } 32 | 33 | func TestAt_Past(test *testing.T) { 34 | start := time.Now() 35 | done := At(start.Add(-100*time.Millisecond), func() {}) 36 | <-done 37 | diff := time.Now().Sub(start) 38 | if diff > time.Millisecond { 39 | test.Errorf("Expected to return immediately, but it took %v", diff) 40 | } 41 | } 42 | 43 | func TestAfter_Future(test *testing.T) { 44 | start := time.Now() 45 | done := After(100*time.Millisecond, func() { 46 | diff := time.Now().Sub(start) 47 | if diff > 105*time.Millisecond { 48 | test.Errorf("Expected to run in 100 ms, it did in %v", diff) 49 | } 50 | }) 51 | <-done 52 | } 53 | 54 | func TestEvery(test *testing.T) { 55 | dur := 10 * time.Millisecond 56 | count := 0 57 | Every(dur, func() { 58 | count++ 59 | }) 60 | <-time.After(100 * time.Millisecond) 61 | count++ 62 | if count < 9 { 63 | test.Errorf("Expected to run in at least 9 times, it did %v times", count) 64 | } 65 | } 66 | 67 | func TestUntil_Future(test *testing.T) { 68 | count := 0 69 | done := Until(time.Now().Add(100*time.Millisecond), 10*time.Millisecond, func() { 70 | count++ 71 | }) 72 | <-done 73 | if count < 9 { 74 | test.Errorf("Expected to run for at least for 9 times, but it ran for %v times", count) 75 | } 76 | } 77 | 78 | func TestUntil_Past(test *testing.T) { 79 | count := 0 80 | done := Until(time.Now().Add(-100*time.Millisecond), 10*time.Millisecond, func() { 81 | count++ 82 | }) 83 | <-done 84 | if count != 0 { 85 | test.Errorf("Expected to run for at least for 0 times, but it ran for %v times", count) 86 | } 87 | } 88 | 89 | func TestTimeout_TimedOut(test *testing.T) { 90 | done := Timeout(100*time.Millisecond, func() { 91 | time.Sleep(time.Minute) 92 | }) 93 | if <-done { 94 | test.Errorf("Expected to get timed out, but it has been completed") 95 | } 96 | } 97 | 98 | func TestTimeout_Completed(test *testing.T) { 99 | done := Timeout(time.Minute, func() { 100 | time.Sleep(100 * time.Millisecond) 101 | }) 102 | if !<-done { 103 | test.Errorf("Expected to get completed, but it has been timed out") 104 | } 105 | } 106 | 107 | func TestAll(test *testing.T) { 108 | start := time.Now() 109 | var val1, val2, val3 bool 110 | done := All(func() { 111 | val1 = true 112 | time.Sleep(100 * time.Millisecond) 113 | }, func() { 114 | val2 = true 115 | time.Sleep(100 * time.Millisecond) 116 | }, func() { 117 | val3 = true 118 | time.Sleep(100 * time.Millisecond) 119 | }) 120 | <-done 121 | diff := time.Now().Sub(start) 122 | if diff > 105*time.Millisecond { 123 | test.Errorf("All takes too long to complete") 124 | } 125 | if !(val1 && val2 && val3) { 126 | test.Errorf("Expected all to run, but at least one didn't") 127 | } 128 | } 129 | 130 | func TestAllWithThrottle(test *testing.T) { 131 | start := time.Now() 132 | fn := func() { 133 | time.Sleep(100 * time.Millisecond) 134 | } 135 | done := AllWithThrottle(3, fn, fn, fn, fn, fn) 136 | <-done 137 | diff := time.Now().Sub(start) 138 | if diff > 205*time.Millisecond { 139 | test.Errorf("All with throttle takes too long to complete") 140 | } 141 | if diff < 105*time.Millisecond { 142 | test.Errorf("All with throttle doesn't take long, throttling may not work") 143 | } 144 | } 145 | 146 | func TestReplicate(test *testing.T) { 147 | results := make(chan bool, 5) 148 | done := Replicate(5, func() { 149 | results <- true 150 | }) 151 | <-done 152 | close(results) 153 | count := 0 154 | for _ = range results { 155 | count++ 156 | } 157 | if count != 5 { 158 | test.Errorf("Expected 5 to run, but %v worked", count) 159 | } 160 | } 161 | --------------------------------------------------------------------------------