GO by example, part 4
This commit is contained in:
parent
c5983176c5
commit
5d7e654fa1
36
5-go-by-example/31-closing-channels.go
Normal file
36
5-go-by-example/31-closing-channels.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// closing a channel indicates that no more values will be sent on it
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// jobs channel to communicate work to be done
|
||||||
|
jobs := make(chan int, 5)
|
||||||
|
done := make(chan bool)
|
||||||
|
|
||||||
|
// repeatedly receives from jobs
|
||||||
|
// more value will be false if jobs has been closed and all values in the channel have already been received
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
j, more := <-jobs
|
||||||
|
if more {
|
||||||
|
fmt.Println("received job", j)
|
||||||
|
} else {
|
||||||
|
fmt.Println("received all jobs")
|
||||||
|
done <- true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// sends 3 jobs to the worker over the jobs channel, then closes it
|
||||||
|
for j := 1; j <= 3; j++ {
|
||||||
|
jobs <- j
|
||||||
|
fmt.Println("sent job", j)
|
||||||
|
}
|
||||||
|
close(jobs)
|
||||||
|
fmt.Println("sent all jobs")
|
||||||
|
|
||||||
|
// await the worker using the synchronization approach
|
||||||
|
<-done
|
||||||
|
}
|
19
5-go-by-example/32-range-over-channels.go
Normal file
19
5-go-by-example/32-range-over-channels.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// iterate over values received from a channel
|
||||||
|
|
||||||
|
// here iterate over 2 values in the queue channel
|
||||||
|
queue := make(chan string, 2)
|
||||||
|
queue <- "one"
|
||||||
|
queue <- "two"
|
||||||
|
close(queue)
|
||||||
|
|
||||||
|
// range iterates over each element as it’s received from queue
|
||||||
|
for elem := range queue {
|
||||||
|
fmt.Println(elem)
|
||||||
|
}
|
||||||
|
// also shows that it’s possible to close a non-empty channel but still have the remaining values be received
|
||||||
|
}
|
33
5-go-by-example/33-timers.go
Normal file
33
5-go-by-example/33-timers.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// built-in timer and ticker features
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// timers represent a single event in the future
|
||||||
|
// tell the timer how long to wait and it provides a channel that will be notified at that time
|
||||||
|
timer1 := time.NewTimer(2 * time.Second)
|
||||||
|
|
||||||
|
// <-timer1.C blocks on the timer’s channel C until it sends a value indicating that the timer fired
|
||||||
|
<-timer1.C
|
||||||
|
fmt.Println("Timer 1 fired")
|
||||||
|
|
||||||
|
// to wait, you could have used time.Sleep
|
||||||
|
timer2 := time.NewTimer(time.Second)
|
||||||
|
go func() {
|
||||||
|
<-timer2.C
|
||||||
|
fmt.Println("Timer 2 fired")
|
||||||
|
}()
|
||||||
|
// one can cancel the timer before it fires
|
||||||
|
stop2 := timer2.Stop()
|
||||||
|
if stop2 {
|
||||||
|
fmt.Println("Timer 2 stopped")
|
||||||
|
}
|
||||||
|
|
||||||
|
// give the timer2 enough time to fire, if it ever was going to, to show it is in fact stopped
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
31
5-go-by-example/34-tickers.go
Normal file
31
5-go-by-example/34-tickers.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// tickers are for when you want to do something repeatedly at regular intervals
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Tickers use a similar mechanism to timers: a channel that is sent values
|
||||||
|
ticker := time.NewTicker(500 * time.Millisecond)
|
||||||
|
done := make(chan bool)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
case t := <-ticker.C:
|
||||||
|
fmt.Println("Tick at", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Tickers can be stopped like timers
|
||||||
|
time.Sleep(1600 * time.Millisecond)
|
||||||
|
ticker.Stop()
|
||||||
|
done <- true
|
||||||
|
fmt.Println("Ticker stopped")
|
||||||
|
}
|
41
5-go-by-example/35-worker-pools.go
Normal file
41
5-go-by-example/35-worker-pools.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// worker, of which will run several concurrent instances
|
||||||
|
// those workers will receive work on the jobs channel and send the corresponding results on results
|
||||||
|
func worker(id int, jobs <-chan int, results chan<- int) {
|
||||||
|
for j := range jobs {
|
||||||
|
fmt.Println("worker", id, "started job", j)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
fmt.Println("worker", id, "finished job", j)
|
||||||
|
results <- j * 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// send workers work and collect their results
|
||||||
|
const numJobs = 5
|
||||||
|
jobs := make(chan int, numJobs)
|
||||||
|
results := make(chan int, numJobs)
|
||||||
|
|
||||||
|
// starts up 3 workers, initially blocked because there are no jobs
|
||||||
|
for w := 1; w <= 3; w++ {
|
||||||
|
go worker(w, jobs, results)
|
||||||
|
}
|
||||||
|
|
||||||
|
// send 5 jobs and then close that channel
|
||||||
|
for j := 1; j <= numJobs; j++ {
|
||||||
|
jobs <- j
|
||||||
|
}
|
||||||
|
close(jobs)
|
||||||
|
|
||||||
|
// collect all the results of the work
|
||||||
|
for a := 1; a <= numJobs; a++ {
|
||||||
|
<-results
|
||||||
|
}
|
||||||
|
}
|
37
5-go-by-example/36-wait-group.go
Normal file
37
5-go-by-example/36-wait-group.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// wait for multiple goroutines to finish
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// function to run in every goroutine
|
||||||
|
func worker(id int) {
|
||||||
|
fmt.Printf("Worker %d starting\n", id)
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
fmt.Printf("Worker %d done\n", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// WaitGroup is used to wait for all the goroutines launched here to finish
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Launch several goroutines and increment the WaitGroup counter for each
|
||||||
|
for i := 1; i <= 5; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
// Avoid re-use of the same i value in each goroutine closure
|
||||||
|
i := i
|
||||||
|
// Wrap the worker call in a closure that makes sure to tell the WaitGroup that this worker is done
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
worker(i)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
// Block until the WaitGroup counter goes back to 0
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
}
|
57
5-go-by-example/37-rate-limiting.go
Normal file
57
5-go-by-example/37-rate-limiting.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// controlling resource utilization and maintaining quality of service
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// basic rate limiting
|
||||||
|
// want to limit handling of incoming requests
|
||||||
|
requests := make(chan int, 5)
|
||||||
|
for i := 1; i <= 5; i++ {
|
||||||
|
requests <- i
|
||||||
|
}
|
||||||
|
close(requests)
|
||||||
|
|
||||||
|
// this limiter channel will receive a value every 200 milliseconds
|
||||||
|
// this is the regulator in the rate limiting scheme.
|
||||||
|
limiter := time.Tick(200 * time.Millisecond)
|
||||||
|
|
||||||
|
// by blocking on a receive from the limiter channel before serving each request,
|
||||||
|
// limit to 1 request every 200 milliseconds
|
||||||
|
for req := range requests {
|
||||||
|
<-limiter
|
||||||
|
fmt.Println("request", req, time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow short bursts of requests in rate limiting scheme while preserving the overall rate limit
|
||||||
|
// accomplish this by buffering the limiter channel
|
||||||
|
burstyLimiter := make(chan time.Time, 3)
|
||||||
|
|
||||||
|
// fill up the channel to represent allowed bursting
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
burstyLimiter <- time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// every 200 milliseconds try to add a new value to burstyLimiter, up to its limit of 3
|
||||||
|
go func() {
|
||||||
|
for t := range time.Tick(200 * time.Millisecond) {
|
||||||
|
burstyLimiter <- t
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// simulate 5 more incoming requests
|
||||||
|
// the first 3 of these will benefit from the burst capability of burstyLimiter
|
||||||
|
burstyRequests := make(chan int, 5)
|
||||||
|
for i := 1; i <= 5; i++ {
|
||||||
|
burstyRequests <- i
|
||||||
|
}
|
||||||
|
close(burstyRequests)
|
||||||
|
for req := range burstyRequests {
|
||||||
|
<-burstyLimiter
|
||||||
|
fmt.Println("request", req, time.Now())
|
||||||
|
}
|
||||||
|
}
|
35
5-go-by-example/38-atomic-counters.go
Normal file
35
5-go-by-example/38-atomic-counters.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// atomic counters accessed by multiple goroutines
|
||||||
|
func main() {
|
||||||
|
// unsigned integer to represent our (always-positive) counter
|
||||||
|
var ops uint64
|
||||||
|
|
||||||
|
// WaitGroup will help wait for all goroutines to finish their work
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// start 50 goroutines that each increment the counter exactly 1000 times
|
||||||
|
for i := 0; i < 50; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for c := 0; c < 1000; c++ {
|
||||||
|
// atomically increment the counter, giving it the memory address of ops counter with the & syntax
|
||||||
|
atomic.AddUint64(&ops, 1)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait until all the goroutines are done
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// it’s safe to access ops now because it's known no other goroutine is writing to it
|
||||||
|
fmt.Println("ops:", ops)
|
||||||
|
}
|
49
5-go-by-example/39-mutexes.go
Normal file
49
5-go-by-example/39-mutexes.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// holds a map of counters
|
||||||
|
// add a Mutex to synchronize access
|
||||||
|
type Container struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
counters map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) inc(name string) {
|
||||||
|
// lock the mutex before accessing counters
|
||||||
|
c.mu.Lock()
|
||||||
|
// unlock it at the end of the function using defer
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.counters[name]++
|
||||||
|
}
|
||||||
|
|
||||||
|
// mutex to safely access data across multiple goroutines
|
||||||
|
func main() {
|
||||||
|
c := Container{
|
||||||
|
// the zero value of a mutex is usable as-is
|
||||||
|
counters: map[string]int{"a": 0, "b": 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// increments a named counter in a loop
|
||||||
|
doIncrement := func(name string, n int) {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
c.inc(name)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
// run several goroutines concurrently
|
||||||
|
wg.Add(3)
|
||||||
|
go doIncrement("a", 10000)
|
||||||
|
go doIncrement("a", 10000)
|
||||||
|
go doIncrement("b", 10000)
|
||||||
|
|
||||||
|
// wait a for the goroutines to finish
|
||||||
|
wg.Wait()
|
||||||
|
fmt.Println(c.counters)
|
||||||
|
}
|
90
5-go-by-example/40-stateful-goroutines.go
Normal file
90
5-go-by-example/40-stateful-goroutines.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// built-in synchronization features of goroutines and channels
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// state will be owned by a single goroutine
|
||||||
|
// this will guarantee that the data is never corrupted with concurrent access
|
||||||
|
// in order to read or write that state, other goroutines will send messages to the owning goroutine and receive corresponding replies
|
||||||
|
// these readOp and writeOp structs encapsulate those requests and a way for the owning goroutine to respond
|
||||||
|
type readOp struct {
|
||||||
|
key int
|
||||||
|
resp chan int
|
||||||
|
}
|
||||||
|
type writeOp struct {
|
||||||
|
key int
|
||||||
|
val int
|
||||||
|
resp chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// count how many operations to perform
|
||||||
|
var readOps uint64
|
||||||
|
var writeOps uint64
|
||||||
|
|
||||||
|
// reads and writes channels will be used by other goroutines
|
||||||
|
reads := make(chan readOp)
|
||||||
|
writes := make(chan writeOp)
|
||||||
|
|
||||||
|
// goroutine that owns the state, which is a map as in the previous example but now private to the stateful goroutine
|
||||||
|
// this goroutine repeatedly selects on the reads and writes channels, responding to requests as they arrive
|
||||||
|
go func() {
|
||||||
|
var state = make(map[int]int)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case read := <-reads:
|
||||||
|
read.resp <- state[read.key]
|
||||||
|
case write := <-writes:
|
||||||
|
state[write.key] = write.val
|
||||||
|
write.resp <- true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// starts 100 goroutines to issue reads to the state-owning goroutine via the reads channel
|
||||||
|
for r := 0; r < 100; r++ {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
// each read requires constructing a readOp, sending it over the reads channel, and the receiving the result over the provided resp channel
|
||||||
|
read := readOp{
|
||||||
|
key: rand.Intn(5),
|
||||||
|
resp: make(chan int)}
|
||||||
|
reads <- read
|
||||||
|
<-read.resp
|
||||||
|
atomic.AddUint64(&readOps, 1)
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// start 10 writes as well, using a similar approach
|
||||||
|
for w := 0; w < 10; w++ {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
write := writeOp{
|
||||||
|
key: rand.Intn(5),
|
||||||
|
val: rand.Intn(100),
|
||||||
|
resp: make(chan bool)}
|
||||||
|
writes <- write
|
||||||
|
<-write.resp
|
||||||
|
atomic.AddUint64(&writeOps, 1)
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// let the goroutines work for a second
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
// capture and report the op counts
|
||||||
|
readOpsFinal := atomic.LoadUint64(&readOps)
|
||||||
|
fmt.Println("readOps:", readOpsFinal)
|
||||||
|
writeOpsFinal := atomic.LoadUint64(&writeOps)
|
||||||
|
fmt.Println("writeOps:", writeOpsFinal)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user