之前在处理unbuffered的channel时会有阻塞的的场景。那么使用buffered的channel场景是怎样的呢?
- Sends to a buffered channel are blocked only when the buffer is full.
- Similarly receives from a buffered channel are blocked only when the buffer is empty.
Buffered channels can be created by passing an additional capacity parameter to the make function which specifies the size of the buffer.
ch := make(chan type, capacity)
capacity in the above syntax should be greater than 0 for a channel to have a buffer.
The capacity for an unbuffered channel is 0 by default.hence we omitted the capacity parameter while creating channels in the previous tutorial.
package main
import (
"fmt"
)
func main() {
ch := make(chan string, 2)
ch <- "naveen"
ch <- "paul"
fmt.Println(<- ch)
fmt.Println(<- ch)
}
Tip 我们现在的channel容量是2. 所以写了两次。 如果容量是1那么第二次写的逻辑是不会被执行的。记着我们之前的阻塞逻辑。 此时需要有消费者去消费这个值,代码才会往下执行。
package main
import (
"fmt"
"time"
)
func write(ch chan int) {
for i := 0; i < 5; i++ {
ch <- i
fmt.Println("successfully wrote", i, "to ch")
}
close(ch)
}
func main() {
ch := make(chan int, 2)
go write(ch)
time.Sleep(2 * time.Second)
for v := range ch {
fmt.Println("read value", v,"from ch")
time.Sleep(2 * time.Second)
}
}
// write values `0` and `1` to the `ch` channel immediately. and then it blocks until at least one value is read from `ch` channel
successfully wrote 0 to ch
successfully wrote 1 to ch
read value 0 from ch // read value from ch
successfully wrote 2 to ch // write immediately
// read
read value 1 from ch
// write immediately
successfully wrote 3 to ch
read value 2 from ch
successfully wrote 4 to ch //write over. close channel
read value 3 from ch //after 2 sec. Read 4
read value 4 from ch
Deadlock
package main
import (
"fmt"
)
func main() {
ch := make(chan string, 2)
ch <- "naveen"
ch <- "paul"
ch <- "steve"
fmt.Println(<-ch)
fmt.Println(<-ch)
}
// all goroutines are asleep - deadlock!
Closing buffered channels
It’s possible to read data from a already closed buffered channel The channel will return the data that is already written to the channel and once all the data has been read. it will return the zero value of the channel.
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 5)
ch <- 5
ch <- 6
close(ch)
n, open := <-ch
fmt.Printf("Received: %d, open: %t\n", n, open) //5 true
n, open = <-ch
fmt.Printf("Received: %d, open: %t\n", n, open) // 6 true
n, open = <-ch
fmt.Printf("Received: %d, open: %t\n", n, open) // 0 false
}
The same program can be written using for range loop too.
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 5)
ch <- 5
ch <- 6
close(ch)
for n := range ch {
fmt.Println("Received:", n)
}
}
Length vs Capacity
- The length of the buffered channel is the number of elements currently queued in it.
- The capacity of a buffered channel is the number of values that the channel can hold. This is the value we specify when creating the buffered channel using the
makefunction.
package main
import (
"fmt"
)
func main() {
ch := make(chan string, 3)
ch <- "naveen"
ch <- "paul"
fmt.Println("capacity is", cap(ch)) //3
fmt.Println("length is", len(ch)) //2
fmt.Println("read value", <-ch)
fmt.Println("new length is", len(ch)) //1
}
WaitGroup
WaitGroup is a struct type
- A WaitGroup is used to wait for a collection of Goroutines to finish executing. The control is blocked until all Goroutines finish executing
- WaitGroup 是用来等待Goroutines的集合结束执行. 控制权会被阻塞直到所有的Goroutines执行完
Let’s say we have 3 concurrently executing Goroutines spawned from the main Goroutine. The main Goroutines needs to wait for the 3 other Goroutines to finish before terminating.
package main
import (
"fmt"
"sync"
"time"
)
func process(i int, wg *sync.WaitGroup) {
fmt.Println("started Goroutine ", i)
time.Sleep(2 * time.Second)
fmt.Printf("Goroutine %d ended\n", i)
wg.Done()
}
func main() {
no := 3
var wg sync.WaitGroup
for i := 0; i < no; i++ {
wg.Add(1)
go process(i, &wg)
}
wg.Wait()
fmt.Println("All go routines finished executing")
}
started Goroutine 2
started Goroutine 0
started Goroutine 1
Goroutine 0 ended
Goroutine 2 ended
Goroutine 1 ended
All go routines finished executing
TIP
It is important to pass the pointer of
wgin line no. 21. If the pointer is not passed, then each Goroutine will have its own copy of theWaitGroupandmainwill not be notified when they finish executing
Your output might be different from mine since the order of execution of Goroutines can vary :).
Awsome situations
Worker Pool Implementation
One of the important uses of buffered channel is the implementation of worker pool.
In general, a worker pool is a collection of threads that are waiting for tasks to be assigned to them. Once they finish the task assigned, they make themselves available again for the next task.
Our worker pool will carry out the task of finding the sum of a digits of the input number. For example if 234 is passed, the output would be 9 (2 + 3 + 4)
The following are the core functionalities of our worker pool
- Creation of a pool of Goroutines which listen on an input buffered channel waiting for jobs to be assigned
- Addition of jobs to the input buffered channel
- Writing results to an output buffered channel after job completion
- Read and print results from the output buffered channel
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
type Job struct {
id int
randomno int
}
type Result struct {
job Job
sumofdigits int
}
var jobs = make(chan Job, 10)
var results = make(chan Result, 10)
func digits(number int) int {
sum := 0
no := number
for no != 0 {
digit := no % 10
sum += digit
no /= 10
}
time.Sleep(2 * time.Second)
return sum
}
func worker(wg *sync.WaitGroup) {
for job := range jobs {
output := Result{job, digits(job.randomno)}
results <- output
}
wg.Done()
}
func createWorkerPool(noOfWorkers int) {
var wg sync.WaitGroup
for i := 0; i < noOfWorkers; i++ {
wg.Add(1)
go worker(&wg)
}
wg.Wait()
close(results)
}
func allocate(noOfJobs int) {
for i := 0; i < noOfJobs; i++ {
randomno := rand.Intn(999)
job := Job{i, randomno}
jobs <- job
}
close(jobs)
}
func result(done chan bool) {
for result := range results {
fmt.Printf("Job id %d, input random no %d , sum of digits %d\n", result.job.id, result.job.randomno, result.sumofdigits)
}
done <- true
}
func main() {
startTime := time.Now()
noOfJobs := 100
go allocate(noOfJobs)
done := make(chan bool)
go result(done)
noOfWorkers := 10
createWorkerPool(noOfWorkers)
<-done
endTime := time.Now()
diff := endTime.Sub(startTime)
fmt.Println("total time taken ", diff.Seconds(), "seconds")
}