首页
留言
动态
归档
推荐
音乐
工具
Search
1
Emby公益服-上万部电影电视剧免费看
56,687 阅读
2
openwrt-docker部署lxk0301京东自动签到脚本
12,147 阅读
3
QuantumultX-京东签到撸京东豆
10,459 阅读
4
LXK0301京东签到脚本-自动提交互助码
9,014 阅读
5
微信-域名被封监测以及自动更换被封域名
8,747 阅读
随便写写
科学上网
Web开发
瞎折腾
登录
Search
标签搜索
quantumultx
laravel
openwrt
laravel nova
laradock
telegram
薅羊毛
google adsense
jd_scripts
京东签到
ubuntu
oh-my-zsh
web开发环境
nginx
工具
shadowsocks shadowsocksR
RBAC
权限管理
内网穿透
Python
orzlee
累计撰写
43
篇文章
累计收到
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,039 阅读
0 评论
0 点赞