首页
留言
动态
归档
推荐
音乐
工具
Search
1
Emby公益服-上万部电影电视剧免费看
66,771 阅读
2
openwrt-docker部署lxk0301京东自动签到脚本
12,893 阅读
3
QuantumultX-京东签到撸京东豆
11,188 阅读
4
LXK0301京东签到脚本-自动提交互助码
9,634 阅读
5
微信-域名被封监测以及自动更换被封域名
9,172 阅读
随便写写
科学上网
Web开发
瞎折腾
Search
标签搜索
quantumultx
laravel
openwrt
laravel nova
laradock
telegram
DDC/CL
薅羊毛
google adsense
jd_scripts
京东签到
ubuntu
oh-my-zsh
web开发环境
nginx
工具
shadowsocks shadowsocksR
RBAC
权限管理
内网穿透
orzlee
累计撰写
46
篇文章
累计收到
595
条评论
首页
栏目
随便写写
科学上网
Web开发
瞎折腾
页面
留言
动态
归档
推荐
音乐
工具
搜索到
1
篇与
golang
的结果
2020-03-23
Golang oh-my-zsh 自定义插件批量更新程序
前言 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的方法,但是达不到想要的效果,还是总线程不等待所有子线程执行完毕的问题。因为这里线程/协程用得太杂乱了。不过这就是我的目的,让线程/协程极致的情况下怎么控制好。各种线程/协程满天飞是怎么样的体验。
2020年03月23日
1,131 阅读
0 评论
0 点赞