前言
Golang很火啊,引用维基百科词条说明 Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。。并发对于我来说接触很少,几乎没有写过多线程并发之类的程序。之前折腾Python的时候使用过多线程,执行效率要高不少。PHP也有多线程,我真是一次都没有用到过。
oh my zsh(有兴趣可以了解下oh-my-zsh强大的zsh配置管理)会自动提示更新,但是自己安装的一些插件却不会一起更新,最近翻了一遍Go语言官方文档,写了个oh my zsh自定义插件更新程序。
开搞
oh my zsh的自定义插件都安装在了/root/.oh-my-zsh/custom/plugins
目录下,我随便找了个已安装的插件试着git pull
,果然没有更新。插件也不多,就两个。但是我看了Go语言文档有劲没处使啊,就要折腾下。
我不仅要写,我还要多线程,各种多线程。
第一步,找出需要更新的目录
上网查、看文档,折腾出这么个玩意:
func GetDir(path string) ([]string, error) {
var s []string
rd, err := ioutil.ReadDir(path)
if err != nil {
fmt.Println("read dir error:", err)
return s, err
}
for _, dir := range rd {
if dir.IsDir() {
s = append(s, path+"/"+dir.Name())
}
}
return s, err
}
ioutil.ReadDir
读取传入路径下所有文件和目录,然后找出所有目录返回,这样一个简单的获取oh my zsh自定义插件目录的函数就封装好了。
第二步,进入目录更新
其实写这篇文章的时候我已经修改了好几遍程序了,还是贴最终的吧。
更新函数,遍历目录然后执行pull
函数拉代码。
var wg sync.WaitGroup
...
func Update(path string) {
//不异步不需要执行
defer wg.Done()
paths, _ := GetDir(path)
if len(paths) != 0 {
for _, dir := range paths {
//添加进程数
wg.Add(1)
fmt.Println("get pull dir:", dir)
go Pull(dir)
}
}
}
最开始的版本没有使用sync.WaitGroup
,会出现问题。涉及到Channels,主线程也是用多线程,update
函数中也有多线程。Channels在使用<- ch
接收值时会堵塞,直到ch
接收到Channels的值后继续执行。那么不使用sync.WaitGroup
堵塞进程,还没等子线程/协程执行完主线程就已经执行完毕退出了。
func Pull(path string) {
//每执行一个进程都要标记完成
defer wg.Done()
//command := exec.Command("sh", "-c", "cd ", path, "&&", "git", "pull")
command := exec.Command("sh", "-c", "cd " + path + "&& git pull")
ch := make(chan int)
go Stdout(command, ch)
fmt.Println("ProcessState PID:", <- ch)
}
exec.Command
有点讲究,起初我直接把命令作为第一个参数传入是不行的,需要把命令拆分成参数才能执行。
后面我又把获取参数封装成了Stdout
函数,其实我想折腾下Channels。
第三步,返回命令行输出
折腾起来浑身是劲,编写命令行返回输出函数,我要看到所有git pull
的执行结果。
func Stdout(command *exec.Cmd, ch chan int) {
stdout, _ := command.StdoutPipe()
stderr, _ := command.StderrPipe()
if err := command.Start(); nil != err {
fmt.Println("command start err:", err)
ch <- 0
return
}
pid := command.Process.Pid
output(stdout, pid)
output(stderr, pid)
err := command.Wait() //等待执行完成
if nil != err {
fmt.Printf("Process PID %d command wait err: %s", pid, err)
}
ch <- command.ProcessState.Pid()
return
}
command.StdoutPipe
,command.StderrPipe
分别获取命令正确输出返回和错误输出返回,好打印出来。command.Wait()
等待命令结束。exec.Command需要好好看看文档。直接执行命令不需要这么麻烦,使用exec.Run()
就行,但是我要捕获命令输出,所以相对麻烦。
func outputLine(oReader *bufio.Reader, pid int) {
for {
line, err := oReader.ReadString('\n')
if nil != err{
if io.EOF != err {
log.Printf("Process PID %d: err %v", pid, err)
}
break
}
log.Printf("Process PID %d: %s", pid, line)
}
}
func output(stdout io.ReadCloser, pid int) {
oReader := bufio.NewReader(stdout)
outputLine(oReader, pid)
}
这两个函数主要是重复代码太多,封装了下,outputLine
函数主要是按行输出命令返回信息。
第四部,主函数
主函数我改进接收参数功能,在执行的时候可以接收多个目录参数,然后并发git pull
。
func main() {
// 接收多目录参数
args := os.Args[1:]
if len(args) != 0 {
for _, path := range args {
fmt.Println(path)
wg.Add(1)
go Update(path)
}
} else {
path := "/root/.oh-my-zsh/custom/plugins"
//添加进程数,相对于Update异步,如果Update不异步可以不需要add
wg.Add(1)
go Update(path)
//等待所有进程全部结束后退出主线程
}
wg.Wait()
}
sync.WaitGroup
就是一个计数器,使用Add()
添加,Done()
就释放,Wait()
会等待所有Add()
全部Done()
后向下执行,否则一直堵塞进程。如果这里我使用Channels无法控制,主线程传入Channels,那么Update()
函数何时返回呢?函数中又是多进程/协程,没有找到其他办法,因为Update()
函数中还要输出,全部用Channels会乱成一锅粥的。
贴一下完整代码吧,看著挺乱的:
package main
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"runtime"
"sync"
)
// 更新oh my zsh 自定义插件
//同步
var wg sync.WaitGroup
func GetDir(path string) ([]string, error) {
var s []string
rd, err := ioutil.ReadDir(path)
if err != nil {
fmt.Println("read dir error:", err)
return s, err
}
for _, dir := range rd {
if dir.IsDir() {
s = append(s, path+"/"+dir.Name())
}
}
return s, err
}
func Update(path string) {
//不异步不需要执行
defer wg.Done()
paths, _ := GetDir(path)
if len(paths) != 0 {
for _, dir := range paths {
//添加进程数
wg.Add(1)
fmt.Println("get pull dir:", dir)
go Pull(dir)
}
}
}
func Stdout(command *exec.Cmd, ch chan int) {
stdout, _ := command.StdoutPipe()
stderr, _ := command.StderrPipe()
if err := command.Start(); nil != err {
fmt.Println("command start err:", err)
ch <- 0
return
}
pid := command.Process.Pid
output(stdout, pid)
output(stderr, pid)
err := command.Wait() //等待执行完成
if nil != err {
fmt.Printf("Process PID %d command wait err: %s", pid, err)
}
ch <- command.ProcessState.Pid()
return
}
func outputLine(oReader *bufio.Reader, pid int) {
for {
line, err := oReader.ReadString('\n')
if nil != err{
if io.EOF != err {
log.Printf("Process PID %d: err %v", pid, err)
}
break
}
log.Printf("Process PID %d: %s", pid, line)
}
}
func output(stdout io.ReadCloser, pid int) {
oReader := bufio.NewReader(stdout)
outputLine(oReader, pid)
}
func Pull(path string) {
//每执行一个进程都要标记完成
defer wg.Done()
//command := exec.Command("sh", "-c", "cd ", path, "&&", "git", "pull")
command := exec.Command("sh", "-c", "cd " + path + "&& git pull")
ch := make(chan int)
go Stdout(command, ch)
fmt.Println("ProcessState PID:", <- ch)
}
func main() {
// 接收多目录参数
args := os.Args[1:]
if len(args) != 0 {
for _, path := range args {
fmt.Println(path)
wg.Add(1)
go Update(path)
}
} else {
path := "/root/.oh-my-zsh/custom/plugins"
//添加进程数,相对于Update异步,如果Update不异步可以不需要add
wg.Add(1)
go Update(path)
//等待所有进程全部结束后退出主线程
}
wg.Wait()
}
最后使用go build 文件名
编译就好了,跨平台。
结语
在看Golang文档的时候语法有点别扭,不过敲了几遍后居然特别顺手。敲这个程序真的试了很多只用Channels的方法,但是达不到想要的效果,还是总线程不等待所有子线程执行完毕的问题。因为这里线程/协程用得太杂乱了。不过这就是我的目的,让线程/协程极致的情况下怎么控制好。各种线程/协程满天飞是怎么样的体验。
评论 (0)