Golang 新手求助

2021-02-24 16:29:10 +08:00
 hsczy

帮朋友做 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")
}

3955 次点击
所在节点    Go 编程语言
37 条回复
luguhu
2021-02-24 16:41:30 +08:00
因为 channel 没有 Close 吧
hsczy
2021-02-24 16:44:21 +08:00
@luguhu 使用 channel 需要 close 么?我看教程上面没有显式关闭的模式。如果要关闭的话 是在 wg.wait()之后关闭么?
darksword21
2021-02-24 16:45:50 +08:00
我觉得还是 python 好写一些。。
hsczy
2021-02-24 16:47:08 +08:00
@darksword21 Python 的 exe 包要根据目标环境的 windows 版本和位数一样才行,我只有 Mac0.0,太难了。
ahsjs
2021-02-24 16:48:54 +08:00
😓
// 加上这个
close(fileChan)

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

go func() {
ReadExcelFile(filePathName)
wg.Done()
}()
}
ahsjs
2021-02-24 16:49:25 +08:00
@ahsjs wg.Done 放外面
luguhu
2021-02-24 16:52:28 +08:00
for filePathName := range fileChan {
go utils.ReadExcelFile(filePathName, &wg)
}
luguhu
2021-02-24 16:52:53 +08:00
这里会 hang 住
# 5 正解
hsczy
2021-02-24 16:57:13 +08:00
@ahsjs 谢谢大佬。这个地方为什么要显式的关闭 chan ? chan 不是会在没有数据的时候自己关闭么?
mybyons
2021-02-24 16:57:51 +08:00
看的好乱 还是重构一下代码吧

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

....
```

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

暂时就这些吧 不要滥用 goroutine,
ahsjs
2021-02-24 17:36:31 +08:00
@hsczy 同在学习中的新手😓。channel 一般在几个协程中通信使用,你这里都可以字符串数组代替。
可以参考下官网文档 //golang.org/ref/spec#Channel_types
如果通道就 5 条数据,range 的时候到第六条就阻塞了。还有如果你这个发送方发送超过 5 条数据也会 deadlock(不是协程)。
katsusan
2021-02-24 17:39:18 +08:00
golang 里对 channel 的 range 要等到该 channel 被关闭才会终止。
考虑到 channel 的收发特性一般由 sender 端关闭。

而且我看了下 main 函数里 fileChan 容量只有 5,在工资表 xlsx 文件多于 5 个时
下面的 fileChan <- filePathName 操作像是会一直阻塞。
littlewey
2021-02-24 19:25:20 +08:00
windows 虚拟机里封一个 venv,配上 batch/powershell 调用,打包 zip,得了,不搞 exe 。
supersu
2021-02-24 19:42:59 +08:00
虚拟机装 win 呀,pyinstaller 打包 EXE 很成熟
hsczy
2021-02-24 20:06:52 +08:00
@mybyons 学艺不精,想着用 go 和 chan 来试试没想到自己差把火,还是老老实实 for 循环单线程把事情做完了。多谢大佬指点。
hsczy
2021-02-24 20:09:06 +08:00
@ahsjs 一起探讨,不知道我这个对不对。我的 chan 设置的缓存为 5,生产者发送第六条数据的时候不应该是在阻塞中,等到消费者消费了一条数据之后再向 chan 中发送一条数据,为什么回 deadlock 呢?
hsczy
2021-02-24 20:10:48 +08:00
@katsusan 多谢指点。所以 for range 来操作 chan 不是应该到 chan 关闭的时候才正常退出么?如果是多个 sender 对一个 chan 进行操作的话那应该怎么来控制关闭呢?
jworg
2021-02-24 21:22:14 +08:00
如果是我的话,这个 fileChan := make(chan string, 5) 绝不会只设置 5,至少 32 我才放心。然后也不会有 for filePathName := range fileChan 这个操作,直接起 CPU 个数的循环的 consumer 协程, 线程里 for 循环不停从 fileChan 取值,加个 select 检测 1s 超时,超时就 wg.Done,fileChan 里没值就应该 5 个 consumer 协程都 done 然后退出了。
jworg
2021-02-24 22:05:03 +08:00
hysys32
2021-02-25 07:47:45 +08:00
简单 excel 处理 vba 不是更好的选择…

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/755876

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX