Golang Go语言新手求助

发布于 1周前 作者 yibo5220 来自 Go语言

Golang Go语言新手求助

帮朋友做 excel 的简单处理。原本是 python 栈,心想使用省事就像打包成 exe 文件,python 打包的 exe 文件又太大,而且会有各种奇奇怪怪的问题。刚好最近在学 golang,就用 golang 在写代码,不过遇到了 Goroutine 和 Channel 配合使用的问题。

问题的大致是这样的,先找出所有的符合条件的 Excel 文件,放入一个 chanfileChan中,然后通过读取这个 chan 中的数据,使用 Goroutine 调用utlis.ReadExcelFile的方法将传入的文件进行分析,只对需要的处理的行进行处理,并用一个 chanResultChan去接受,最后再将读取到的结果进行处理。但是对ResultChan进行处理之后就会报死锁的错误,我怎么样都不能定位到原因。请各位好哥哥帮帮我。 代码如下

main.go

package main

import ( utils “excelmaker/src/Utils” “fmt” “log” “os” “path/filepath” “strings” “sync” )

func main() { pwd, _ := os.Getwd() // 找到本地路径 filenamePaths, err := filepath.Glob(filepath.Join(pwd, “*”)) //获取本地目录下所有文件 if err != nil { log.Fatal(err) } fileChan := make(chan string, 5) for _, filePathName := range filenamePaths { fileExt := filepath.Ext(filePathName) fileName := filepath.Base(filePathName) if fileExt == “.xlsx” && strings.Contains(fileName, “工资表”) { fmt.Println(filePathName) fileChan <- filePathName } } totalFileNum := len(fileChan) wg := sync.WaitGroup{} wg.Add(totalFileNum) for filePathName := range fileChan { go utils.ReadExcelFile(filePathName, &wg) } go utils.MakeTotalExcel() //会出现死锁 wg.Wait()

}

Utils/utils.go

package utils

import ( “fmt” “strconv” “strings” “sync”

"github.com/360EntSecGroup-Skylar/excelize/v2"

)

type ExcelData struct { Name string Salar int Company string }

var ResultChan = make(chan *ExcelData, 10)

const SalaryTable = “工资表”

func ReadExcelFile(filePathName string, wg *sync.WaitGroup) { // func ReadExcelFile(filePathName string) { f, err := excelize.OpenFile(filePathName) if err != nil { fmt.Println(err) return } var company string if strings.Contains(filePathName, “头疗”) { company = “头疗店” } else if strings.Contains(filePathName, “城东”) { company = “城东店” } else if strings.Contains(filePathName, “熹 SPA”) { company = “熹 SPA 店” } else if strings.Contains(filePathName, “置地”) { company = “置地店” } else { company = “东方丽景店” } result, _ := f.SearchSheet(SalaryTable, “实发工资”, false) salaryColIndex, _, _ := excelize.CellNameToCoordinates(result[0]) rows, err := f.Rows(SalaryTable) if err != nil { fmt.Println(err) return } for rows.Next() { row, err := rows.Columns() if err != nil { fmt.Println(err) return } if len(row) != 0 { var stringNotAnalysisList = []string{"", “姓名”, “合计”} if ok, _ := Contain(row[0], stringNotAnalysisList); ok { continue } else { salary, _ := strconv.Atoi(row[salaryColIndex-1]) tempData := ExcelData{ Name: row[0], Salar: salary, Company: company, } ResultChan <- &tempData } } } wg.Done() }

func MakeTotalExcel() { resultMap := make(map[string]map[string]int) companyList := []string{“姓名”} for v := range ResultChan { // fmt.Println(v.Name) if ok, _ := Contain(v.Name, resultMap); !ok { resultMap[v.Name] = make(map[string]int) } resultMap[v.Name][v.Company] = v.Salar if ok, _ := Contain(v.Company, companyList); !ok { companyList = append(companyList, v.Company) } } fmt.Println(companyList) fmt.Println(resultMap) }

Utils/commont_utils.go

package utils

import ( “errors” “reflect” )

// 判断 obj 是否在 target 中,target 支持的类型 arrary,slice,map func Contain(obj interface{}, target interface{}) (bool, error) { targetValue := reflect.ValueOf(target) switch reflect.TypeOf(target).Kind() { case reflect.Slice, reflect.Array: for i := 0; i < targetValue.Len(); i++ { if targetValue.Index(i).Interface() == obj { return true, nil } } case reflect.Map: if targetValue.MapIndex(reflect.ValueOf(obj)).IsValid() { return true, nil } }

return false, errors.New("not in array")

}


更多关于Golang Go语言新手求助的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

38 回复

因为 channel 没有 Close 吧

更多关于Golang Go语言新手求助的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


使用 channel 需要 close 么?我看教程上面没有显式关闭的模式。如果要关闭的话 是在 wg.wait()之后关闭么?

我觉得还是 python 好写一些。。

Python 的 exe 包要根据目标环境的 windows 版本和位数一样才行,我只有 Mac0.0,太难了。

😓
// 加上这个
close(fileChan)

wg := sync.WaitGroup{}
wg.Add(totalFileNum)
for filePathName := range fileChan {

go func() {
ReadExcelFile(filePathName)
wg.Done()
}()
}

wg.Done 放外面

for filePathName := range fileChan {
go utils.ReadExcelFile(filePathName, &wg)
}

这里会 hang 住
# 5 正解

谢谢大佬。这个地方为什么要显式的关闭 chan ? chan 不是会在没有数据的时候自己关闭么?

看的好乱 还是重构一下代码吧

go<br>err := filepath.Walk(path, func() {<br> // skip <br> ...<br> // do<br> result := doSomethig(path)<br> results = append(results, merge(result))<br>})<br><br>....<br>

- Use channel when you know how to close it.
- Don’t pass wg across the boundary.
- …

暂时就这些吧 不要滥用 goroutine,

同在学习中的新手😓。channel 一般在几个协程中通信使用,你这里都可以字符串数组代替。
可以参考下官网文档 //golang.org/ref/spec#Channel_types
如果通道就 5 条数据,range 的时候到第六条就阻塞了。还有如果你这个发送方发送超过 5 条数据也会 deadlock(不是协程)。

golang 里对 channel 的 range 要等到该 channel 被关闭才会终止。
考虑到 channel 的收发特性一般由 sender 端关闭。

而且我看了下 main 函数里 fileChan 容量只有 5,在工资表 xlsx 文件多于 5 个时
下面的 fileChan <- filePathName 操作像是会一直阻塞。

windows 虚拟机里封一个 venv,配上 batch/powershell 调用,打包 zip,得了,不搞 exe 。

虚拟机装 win 呀,pyinstaller 打包 EXE 很成熟

学艺不精,想着用 go 和 chan 来试试没想到自己差把火,还是老老实实 for 循环单线程把事情做完了。多谢大佬指点。

一起探讨,不知道我这个对不对。我的 chan 设置的缓存为 5,生产者发送第六条数据的时候不应该是在阻塞中,等到消费者消费了一条数据之后再向 chan 中发送一条数据,为什么回 deadlock 呢?

多谢指点。所以 for range 来操作 chan 不是应该到 chan 关闭的时候才正常退出么?如果是多个 sender 对一个 chan 进行操作的话那应该怎么来控制关闭呢?

如果是我的话,这个 fileChan := make(chan string, 5) 绝不会只设置 5,至少 32 我才放心。然后也不会有 for filePathName := range fileChan 这个操作,直接起 CPU 个数的循环的 consumer 协程, 线程里 for 循环不停从 fileChan 取值,加个 select 检测 1s 超时,超时就 wg.Done,fileChan 里没值就应该 5 个 consumer 协程都 done 然后退出了。

简单 excel 处理 vba 不是更好的选择…

感谢 还是要多学学多练练啊

最终的方案就是去掉 go runtinue 了?

golang 的并发很简单,但合起来就难了

  1. 在 main 函数里的 fileChan 同时发送并接收就会导致 deadlock, channel 是不同 goroutine 之间进行通信的, 同一个 goroutine 里面就没必要使用 channel 了, append 到一个 slice 就行
    2. ReadExcelFile 函数里面的 wg.Done() 最好放在函数第一行并用 defer 来执行, 要不然中间函数因为错误返回, wg.Done()就执行不到了, 会导致 main 函数里面 wg.Wait()会一直等待, 无法结束
    3. ResultChan 要在发送端进行 close, 否则 MakeTotalExcel 这里的 range 会一直阻塞

你这个代码里面, fileChan 没有关闭, 于是同一个 goroutine 里面同时使用 channel 发送和接收最终会导致发送端和接收端都阻塞, 导致死锁. 可以在发送完之后, 把 fileChan close()掉, 就不会 deadlock 了, 不过还是推荐一个 slice 完事

还是会死锁, 还是需要把 main 函数里面的 fileChan 去掉

我之前也遇到过,要记得把 channel 关闭掉

看到 chan 就先猜测滥用 chan
果然没错
还是 slice 吧

go 的 chan 设计有坑,能不用就不用,反正需要的时候转 chan 比较方便,但 chan 转成其他不方便。

#16 因为你阻塞了以后消费者还一个都没起来呢。你看一下代码,到死锁前没有一个 go 关键词

#17 再 go 个聚合器。这个其实是 chan 的设计缺陷之一,没有设计 “无生产者” 的状态,否则的话生产者各自退出,消费者直接捕获 “无生产者” 状态就行了 —— go 调度器拥有充分的信息来断定这个 chan 已经没生产者了、消费者不可能从这个 chan 得到更多消息了。这点被好像是提出 channel 概念的论文作者亲自说了(他表示 go 确实很不错,但 chan 实在坑,缺乏他当初的论文里提到的几个基础要求)

楼上都说的比较全了,再补充一个代码里容易被忽视的点吧。
utils.go 中通过 ResultChan 在 ReadExcelFile()和 MakeTotalExcel()之间传递信息,但在 ReadExcelFile()中,信息发送至通道后就执行了 wg.Done()。就算代码中没有任何错误,能够正常执行,这样的调用方式也很容易使得主程序中的 wg.Wait()等不到最后几个 MakeTotalExcel()执行完毕而提前退出。

对的 0.0 直接用 map 获取数据在集中处理了

可以找时间看看 Concurrency in Go 这本书的第三章(尤其是 WaitGroup 和 Channels 小节),里面讲得很详细。

是的 最后还是 slice 解决了问题。非常耐心感谢指导,我来消化一下

多谢,现在就啃

是的 第一次用就乱用一气 没有学扎实。最后还是用 slice 和 map 解决了

一个函数超过 20 行。就不想想怎么重构吗?

有道理。学无止境啊

头大,多线程好复杂

新手你好!很高兴你对Go语言感兴趣。作为IT领域的专家,我很乐意为你提供一些入门建议。

首先,Go语言是一门非常简洁且强大的编程语言,特别适合并发编程。如果你是编程新手,建议从Go语言的官方文档和教程入手,这些资源可以在Go语言的官方网站上找到。它们非常系统且易于理解,能够帮助你快速掌握Go语言的基础知识。

在学习过程中,建议你多动手实践,通过编写一些小程序来加深理解。比如,你可以尝试编写一个简单的HTTP服务器,或者实现一个基本的并发任务调度器。这些实践不仅能让你更好地理解Go语言的特性,还能提升你的编程能力。

此外,加入一些Go语言的社区或论坛也是很有帮助的。在这些社区中,你可以遇到很多同样热爱Go语言的朋友,大家可以一起交流学习心得,互相帮助解决问题。

最后,我想强调的是,学习编程是一个长期且持续的过程,不要急于求成。遇到问题时,要保持耐心和好奇心,多思考、多尝试,相信你一定能够掌握Go语言,并在编程领域取得更大的进步!

希望这些建议能对你有所帮助,祝你学习愉快!如果还有其他问题,随时欢迎向我提问。

回到顶部