前段在玩协程时跟着libtask的作者Russ Cox转到Golang了,一堆重量级大牛们想出来的东西就是不同,思路非常特别,和常用的差别有点大,
但非常注重实用,减少了一堆的语法糖后,让人重新思考,开发语言本来应当是怎么样?如果用来做服务端开发的话,真的是很不错的东西。
在一个练习中,我设计了个发送Oracle数据库备份任务的简单场景。通过 发送备份任务到各个客户端,来练习Golang中的sync.WaitGroup.
sync.WaitGroup是Golang提供的一种简单的同步方法集合。它有三个方法.
Add() 添加计数,数目可以为一个,也可以为多个。
Done() 减掉一个计数,如果计数不为0,则Wait()会阻塞在那,直到全部为0
Wait() 等待计数为0.
弄个例子直观的见识下:
package main import ( "fmt" "sync" ) var waitGrp sync.WaitGroup func main() { bakJobs := []string{"Full Database", "Archive Log", "Control File"} for i := 0; i < len(bakJobs); i++ { waitGrp.Add(1) //add job go func(jobName string) { fmt.Println(jobName) waitGrp.Done() //sub job }(bakJobs[i]) } waitGrp.Wait() } /* E:\GitHub\Golang\XCLTools\src\test>wg Full Database Archive Log Control File */可以看到会依次打印bakJobs中的数据。但如果把waitGrp.Wait()注释掉。啥都不会输出。即,在for后,wait会等待所有的job执行完后,
才会执行后面的。 这个例子说明,除了熟悉了下waitGroup的使用外,还说明waitGroup能保证作业的执行顺序,及会等待所有子作业全部完成再继续的特性。
就Oracle数据库备份本身而言,在这使用go func()...其实意义不大,所以我想了个更复杂点的场景,给多个服务器上的数据库批量发送备份指令,并依数据库
的重要性顺序来执行。
下面是实现的代码(纯演示,忽略掉其它通信和逻辑处理方面的东西):
package main import ( "fmt" "sync" ) var waitGrp sync.WaitGroup func main() { waitGrp.Add(2) for _, ip := range []string{"ip1", "ip2"} { go doJobs(ip) fmt.Println("IP = ", ip) } waitGrp.Wait() fmt.Println("main() end.") } func doJobs(ip string) { bakJobs := []string{"Full Database", "Archive Log", "Control File"} for i := 0; i < len(bakJobs); i++ { doBak(ip, i, bakJobs[i]) } fmt.Println("The backup is complete!") fmt.Println("......") defer waitGrp.Done() } func doBak(ip string, id int, jobName string) (bool, error) { fmt.Println("doBak() :", ip, " - ", id, " - ", jobName) return true, nil } /* IP = ip1 IP = ip2 doBak() : ip1 - 0 - Full Database doBak() : ip1 - 1 - Archive Log doBak() : ip1 - 2 - Control File The backup is complete! ...... doBak() : ip2 - 0 - Full Database doBak() : ip2 - 1 - Archive Log doBak() : ip2 - 2 - Control File The backup is complete! ...... main() end. */
代码中,会首先向各个IP发送执行jobs的指令。各个指令执行的时间有长有短,用go func()...能保证是在同一时间向所有相关IP发出命令让各IP依次执行相关备份。
主goroutine则等待这个总作业全部结束后再执行其它。同时由于waitGroup的顺序性,能保证重要点的ip,可以优先执行。
我设计场景的目的是想在例子中加入更多的Golang新鲜元素来增强感觉,所以针对上一个例子,我又设想了一个情况,因为数据库备份是很耗时的,且要等待
所有IP上的作业都执行完毕要花费很长时间,如果这时,用户要求取消这次作业怎么办?
于是这就有了下面的例子:
package main import ( "bufio" "fmt" "os" "sync" "time" ) var waitGrp sync.WaitGroup func main() { ch := make(chan bool) go schedule(ch) r := bufio.NewReader(os.Stdin) for { time.Sleep(time.Second) fmt.Print("Command:> ") ln, _, _ := r.ReadLine() cmd := string(ln) if "q" == cmd || "quit" == cmd { close(ch) break } else { fmt.Println(" = cmd = ", cmd, "\n") } } waitGrp.Wait() fmt.Println("main() end.") } func schedule(ch chan bool) { for _, ip := range []string{"ip1", "ip2"} { waitGrp.Add(1) go doJobs(ip, ch) fmt.Println("schedule() IP = ", ip) } fmt.Println("schedule() end.") return } func doJobs(ip string, ch chan bool) { defer waitGrp.Done() for i := 0; i < 10; i++ { select { case <-ch: fmt.Println("doJobs() ", ip, "=>Job Cancel......") return default: } fmt.Println("doJobs()...... ", ip, " for:", i) time.Sleep(time.Second) } } /* E:\GitHub\Golang\XCLTools\src\test>wg5 schedule() IP = ip1 schedule() IP = ip2 schedule() end. doJobs()...... ip1 for: 0 doJobs()...... ip2 for: 0 Command:> doJobs()...... ip1 for: 1 doJobs()...... ip2 for: 1 doJobs()...... ip1 for: 2 doJobs()...... ip2 for: 2 doJobs()...... ip1 for: 3 doJobs()...... ip2 for: 3 doJobs()...... ip1 for: 4 doJobs()...... ip2 for: 4 doJobs()...... ip1 for: 5 doJobs()...... ip2 for: 5 doJobs()...... ip1 for: 6 doJobs()...... ip2 for: 6 doJobs()...... ip1 for: 7 doJobs()...... ip2 for: 7 doJobs()...... ip1 for: 8 doJobs()...... ip2 for: 8 doJobs()...... ip1 for: 9 doJobs()...... ip2 for: 9 q main() end. E:\GitHub\Golang\XCLTools\src\test>wg5 schedule() IP = ip1 schedule() IP = ip2 schedule() end. doJobs()...... ip1 for: 0 doJobs()...... ip2 for: 0 Command:> doJobs()...... ip2 for: 1 doJobs()...... ip1 for: 1 doJobs()...... ip2 for: 2 doJobs()...... ip1 for: 2 doJobs()...... ip2 for: 3 doJobs()...... ip1 for: 3 q doJobs() ip2 =>Job Cancel...... doJobs() ip1 =>Job Cancel...... main() end. */我用了个for{},让用户在作业执行过程中,可以输入指令退出执行。并且,在退出过程中,各个IP也会作相关的取消处理。以保证不会因
强制中断而出现一些不必要的麻烦。
这个例子到这,已经引入了足够多的东西,我觉得已达到练习的目的,场景就先到此了。
在此整理下,以备后查。
MAIL: xcl_168@aliyun.com
BLOG: http://blog.csdn.net/xcl168
有疑问加站长微信联系(非本文作者)