分享
  1. 首页
  2. 文章

Golang In Action练习之路(1)-chan

大雄good · · 1183 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

最近项目全部转golang,内心既激动又害怕,因为其实自己code经验不足,更何况用一门不熟悉的语言,写代码的时候老是会去Google语法,而打断设计的思路。归其原因还是对golang的系统练习太少,所以我觉得有必要过一遍Golang In Action,多练习掌握最常用的特性,然后再深度研究Kubernetes的源码(非常漂亮),用到项目中。

所以这一系列文章就是,用Golang In Action里面的例子练习Golang,顺序就是看一章,关掉书,自己敲一遍代码,再对例子代码简单升级和总结。菜鸡共勉...

1.chan例子

package main
import (
 "fmt"
 "sync"
)
var wg sync.WaitGroup
func printer(ch <-chan int) {
 for i := range ch {
 fmt.Println("Input: ", i)
 }
 wg.Done()
}
func main() {
 c := make(chan int)
 go printer(c)
 wg.Add(1)
 for i := 0; i < 10; i++ {
 c <- i
 }
 close(c)
 wg.Wait()
}

不得不说Golang In Action第一个例子就这么凶猛,不仅上了chan,还使用了sync。其实从这个例子里面我们可以学到下面几点:

  1. range chan是一个阻塞(Blocked)的操作,如果不是阻塞的,此处goroutine应该马上就退出了
  2. range chanchan被关闭之后,会将chan中的数据全部读取,再退出循环(当然也有人怀疑是不是c<-i这个操作是一个阻塞的,导致的呢?我们后面可以再做一个实验证明这点)
  3. sync.WaitGroup是可以用来确保任务完成的利器,嗯,这个我们也可以改装一把代码让他变成一个worker pool刷点经验。

2.带buffer的chan

如果有阅读过其他关于chan的文章,其实知道c<-i这个操作是一个blocked的。那么为了证明上面的第二点,这里我们:

  1. 直接换成带10个单位buffer的chan
  2. 在goroutine里面sleep 1 second
package main
import (
 "fmt"
 "sync"
 "time"
)
var wg sync.WaitGroup
func printer(ch <-chan int) {
 for i := range ch {
 time.Sleep(time.Second)
 fmt.Println("Input: ", i)
 }
 wg.Done()
}
func main() {
 c := make(chan int, 10)
 go printer(c)
 wg.Add(1)
 for i := 0; i < 10; i++ {
 c <- i
 }
 close(c)
 wg.Wait()
}

结果和之前一样。

3.简单的worker pool

恩,之前谈到sync.WaitGroup可以用来确保任务的完成,这里我觉得书里的例子用的不是很好,并没有体会到sync.WaitGroup的价值,所以这里我们再改造一下,写个简单的worker pool。

package main
import (
 "fmt"
 "sync"
)
type Task int
type Pool interface {
 New(int)
 Run(<-chan Task)
 WaitClose()
}
type WorkPool struct {
 workers int
 wg sync.WaitGroup
}
func (p *WorkPool) New(workerCnt int) {
 var wg sync.WaitGroup
 p.wg = wg
 p.workers = workerCnt
}
func (p *WorkPool) Run(taskChan <-chan Task) {
 for id := 0; id < p.workers; id++ {
 p.wg.Add(1)
 go worker(taskChan, id, &(p.wg))
 }
}
func worker(taskChan <-chan Task, id int, wg *sync.WaitGroup) {
 for task := range taskChan {
 fmt.Printf("Task: %v, Worker ID@%v \n", task, id)
 }
 wg.Done()
}
func (p *WorkPool) WaitClose() {
 p.wg.Wait()
}
func main() {
 // 1. Init a taskChan with 10 buffersize
 taskChan := make(chan Task)
 // 2. Init a 3 worker pool
 var pool WorkPool
 pool.New(3)
 // 3. Listen to the taskChan and start to Run
 pool.Run(taskChan)
 // 4. Start send data from taskChan
 for i := 0; i < 10; i++ {
 taskChan <- Task(i)
 }
 close(taskChan)
 // 5. Wait for worker finish job and close
 pool.WaitClose()
}

这里我实现了一个简单workpool,用一个chan TaskChan来传递Task,WaitGroup来控制和等待所有的Task完成。(代码可能长得不太好看)运行结果如下:

➜ channels git:(master) ✗ go build
➜ channels git:(master) ✗ ./channels
Task: 1, Worker ID@0
Task: 3, Worker ID@0
Task: 4, Worker ID@0
Task: 0, Worker ID@2
Task: 2, Worker ID@1
Task: 6, Worker ID@2
Task: 8, Worker ID@2
Task: 9, Worker ID@2
Task: 7, Worker ID@1
Task: 5, Worker ID@0

这里发现Goroutine的负载大致均衡,所以我想如果我们把chan改成一个带buffer的,是不是第一个goroutine就会一直work,直到buffer里面数据被消耗完。

func main() {
 // 1. Init a taskChan
 taskChan := make(chan Task, 1000)
 // 2. Init a 3 worker pool
 var pool WorkPool
 pool.New(3)
 // 3. Listen to the taskChan and start to Run
 pool.Run(taskChan)
 // 4. Start send data from taskChan
 for i := 0; i < 1000; i++ {
 taskChan <- Task(i)
 }
 close(taskChan)
 // 5. Wait for worker finish job and close
 pool.WaitClose()
}

这里我用了一个buffer 1000的chan,并且传了1000个数据,运行结果:

Task: 1, Worker ID@2
Task: 2, Worker ID@1
Task: 4, Worker ID@1
Task: 0, Worker ID@0
Task: 6, Worker ID@0
Task: 7, Worker ID@0
Task: 3, Worker ID@2
Task: 9, Worker ID@2
...Always Worker ID@2
Task: 996, Worker ID@2
Task: 997, Worker ID@2
Task: 998, Worker ID@2
Task: 999, Worker ID@2
Task: 5, Worker ID@1
Task: 822, Worker ID@0

所以实验发现,正常情况下这个goroutine会消耗大部分数据,说明Golang的调度器会尽量让一个Goroutine工作完,但是为了保证公平性,还是会给他一个时间片,不会让它一直独占. 暂时猜测是这样,需要看源码验证一下。另外如果,我们在Goroutine里面加个time.Sleep,或者执行一个Blocked操作,Goroutine也会释放时间片,达到负载均衡。

小结

这一章简单的学到:

  1. chan是一个blocked的操作
  2. 可以加buffer让chan变成Non-blocked
  3. sync.WaitGroup可以用来写workpool来确保任务的完成
  4. Goroutine通常会尽量独占调度,但是为了公平性会设定时间片(此处为猜测,需要后期验证)

另外写的时候还是有一些坑,比如不熟悉interface的写法,另外我发现go build会生成package名字一样的可执行文件。看书总是会觉得很简单,都懂了,关书写一个程序调通,并且修改加强才有真实的理解。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:大雄good

查看原文:Golang In Action练习之路(1)-chan

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1183 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏