Go-缓冲信道

创建一个有缓冲(Buffer)的信道。只在缓冲已满的情况,才会阻塞向缓冲信道(Buffered Channel)发送数据。同样,只有在缓冲为空的时候,才会阻塞从缓冲信道接收数据。

通过向 make 函数再传递一个表示容量的参数(指定缓冲的大小),可以创建缓冲信道。

ch := make(chan type, capacity)

上面语法中的 capacity 应该大于 0。无缓冲信道的容量默认为 0

import (
	"fmt"
)


func main() {
    // 写入两次,就不能再写入了,否则报错。
	ch := make(chan string, 2)
	ch <- "naveen"
	ch <- "paul"
	fmt.Println(<- ch)
	ch <- "paul2"
	fmt.Println(<- ch)
	fmt.Println(<- ch)
}
结果:
naveen
paul
paul2

一个协程写入数据,一个协程读取数据

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 string, 2)
	ch <- "naveen"
	ch <- "paul"
	//ch <- "paul2"   // 打开会造成死锁,信道写满,没有其他协程读取,再次写入发生死锁
	fmt.Println(<- ch)
	ch <- "paul2"
	fmt.Println(<- ch)
	fmt.Println(<- ch)

	ch2 := make(chan int,2)
	go write(ch2)
	// 尝试把这句话注释掉,看下结果,进行分析
	time.Sleep(2 * time.Second)
	for v := range ch2 {
		fmt.Println("read value", v, "from ch")
		time.Sleep(2 * time.Second)
	}
}
结果:
successfully wrote 0 to ch
successfully wrote 1 to ch
read value 0 from ch
successfully wrote 2 to ch
read value 1 from ch
successfully wrote 3 to ch
read value 2 from ch
successfully wrote 4 to ch
read value 3 from ch
read value 4 from ch

write协程先写入0 1,然后阻塞,主协程进行读取,然后主协程休眠2秒,开始读取数据
读取一次,休眠两秒,知道结束

所以先打印

successfully wrote 0 to ch
successfully wrote 1 to ch

然后

read value 0 from ch
successfully wrote 2 to ch

依次类推,直到结束

长度 vs 容量

缓冲信道的容量是指信道可以存储的值的数量。我们在使用 make 函数创建缓冲信道的时候会指定容量大小

缓冲信道的长度是指信道中当前排队的元素个数

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) // 读取一次,容量3
fmt.Println("new length is", len(ch)) // 长度1

WaitGroup

工作池(Worker Pools),而 WaitGroup 用于实现工作池。

WaitGroup 用于等待一批 Go 协程执行结束。程序控制会一直阻塞,直到这些协程全部执行完毕。假设我们有 3 个并发执行的 Go 协程(由 Go 主协程生成)。Go 主协程需要等待这 3 个协程执行结束后,才会终止。这就可以用 WaitGroup 来实现。

package main

import (
	"fmt"
	"time"
	"sync"
)

func process(index int,wait *sync.WaitGroup) {
	fmt.Printf("goroutine start %d \n",index)
	time.Sleep(1*time.Second)
	fmt.Printf("Goroutine %d ended\n", index)
	wait.Done()
}

func main() {
	var wait sync.WaitGroup
	// 等待三个协程运行完,再走主线程
	for i := 0; i < 3; i++ {
		wait.Add(1)
		//time.Sleep(1*time.Second)
		go process(i,&wait)
	}
	fmt.Println("=================")
	wait.Wait()
	fmt.Printf("main goroutine")
}
结果:
goroutine start 0 
goroutine start 1 
=================
goroutine start 2 
Goroutine 1 ended
Goroutine 2 ended
Goroutine 0 ended
main goroutine

当我们调用 WaitGroupAdd 并传递一个 int 时,WaitGroup 的计数器会加上 Add 的传参。要减少计数器,可以调用 WaitGroupDone() 方法。Wait() 方法会阻塞调用它的 Go 协程,直到计数器变为 0 后才会停止阻塞

传递 wg 的地址是很重要的。如果没有传递 wg 的地址,那么每个 Go 协程将会得到一个 WaitGroup 值的拷贝,因而当它们执行结束时,main 函数并不会知道。

工作池的实现

缓冲信道的重要应用之一就是实现工作池。

一般而言,工作池就是一组等待任务分配的线程。一旦完成了所分配的任务,这些线程可继续等待任务的分配

我们会使用缓冲信道来实现工作池。我们工作池的任务是计算所输入数字的每一位的和。例如,如果输入 234,结果会是 9

我们工作池的核心功能如下:

创建一个 Go 协程池,监听一个等待作业分配的输入型缓冲信道。
将作业添加到该输入型缓冲信道中。
作业完成后,再将结果写入一个输出型缓冲信道。
从输出型缓冲信道读取并打印结果。
package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

func process(index int,wait *sync.WaitGroup) {
	fmt.Printf("goroutine start %d \n",index)
	time.Sleep(1*time.Second)
	fmt.Printf("Goroutine %d ended\n", index)
	wait.Done()
}

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() {
	var wait sync.WaitGroup
	// 等待三个协程运行完,再走主线程
	for i := 0; i < 3; i++ {
		wait.Add(1)
		//time.Sleep(1*time.Second)
		go process(i,&wait)
	}
	fmt.Println("=================")
	wait.Wait()
	fmt.Printf("main goroutine")


	startTime := time.Now()

	// 数据写入工作信道
	noOfJobs := 100
	go allocate(noOfJobs)

	// 结果协程,结果信道中读取数据
	done := make(chan bool)
	go result(done)

	// 工作池,里面创建工作协程,循环读取工作信道数据,写入到结果信道,完成后关闭工作池,关闭结果信道
	noOfWorkers := 100
	createWorkerPool(noOfWorkers)
	// 读取结果信道
	<-done
	endTime := time.Now()
	diff := endTime.Sub(startTime)
	fmt.Println("total time taken ", diff.Seconds(), "seconds")
}
结果:
Job id 30, input random no 562 , sum of digits 13
Job id 86, input random no 236 , sum of digits 11
Job id 85, input random no 421 , sum of digits 7
。。。。
total time taken  20.0043116 seconds

在 main 函数里修改 noOfJobsnoOfWorkers 的值,并试着去分析一下结果

noOfWorkers := 100

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值