请教 Go 并发上传多个文件问题

2019-10-16 17:27:26 +08:00
 raywong

基于 Gin 框架,在前端上传多文件到后台时(写入磁盘)使用了 goroutines,奇怪的是虽然并发执行了,但是上传消耗的时间却跟同步上传(没有使用 goroutines )差不多,难道是我使用的姿势不对?还是说多文件上传不能使用协程?

代码:

func UploadFileHandler(ctx *gin.Context) {
	formData, _ := ctx.MultipartForm()
    files := formData.File["fileList"]
	
    start := time.Now()
	var wg sync.WaitGroup
	wg.Add(len(files))

    for _, file := range files {
    	go func(file *multipart.FileHeader) {
    		
			fmt.Printf("(%s) upload...\n", file.Filename)
			
			// 文件上传
			filePath := filepath.Join(dirPath, file.Filename)
			errors = ctx.SaveUploadedFile(file, filePath)
			if errors != nil {
				ctx.JSON( http.StatusBadRequest, gin.H{
					"code": 400,
			        "error" : errors.Error(),
			    })
			}
			
			fmt.Printf("(%s) upload end...\n", file.Filename)
            wg.Done()

    	}(file)
    }

    wg.Wait()

    end := time.Since(start)
	fmt.Printf("it takes %s\n", end)

    ctx.JSON( http.StatusOK, gin.H{
    	"code": 200,
    	"msg": "上传成功",
    })
}

执行结果:

(文件 4.zip) upload...
(文件 2.zip) upload...
(文件 3.zip) upload...
(文件 1.zip) upload...
(文件 4.zip) upload end...
(文件 2.zip) upload end...
(文件 1.zip) upload end...
(文件 3.zip) upload end...
it takes 713.0408ms

下面是没有使用协程的方式的执行结果:

(文件 4.zip) upload...
(文件 4.zip) upload end...
(文件 3.zip) upload...
(文件 3.zip) upload end...
(文件 2.zip) upload...
(文件 2.zip) upload end...
(文件 1.zip) upload...
(文件 1.zip) upload end...
it takes 730.0474ms

请问各位大佬这是什么原因...

5586 次点击
所在节点    Go 编程语言
29 条回复
forcecharlie
2019-10-16 17:33:33 +08:00
据我所知,带宽和 I/O 是有限的。
TypeErrorNone
2019-10-16 17:55:37 +08:00
消耗时间在上传的网络和磁盘的 io 上,这并不是开启几个协程解决的,你在代码开多个协程处理文件,是已经上传到服务器的资源。
lbp0200
2019-10-16 18:19:34 +08:00
改成传 2 个,看看是不是带宽导致的
raywong
2019-10-16 19:20:42 +08:00
@lbp0200 在本地测试的,一样结果
zhshch
2019-10-16 19:23:32 +08:00
在下载任务里,很多时间耽误在传输和等待,所以开并发有提升。

但是在上传任务里,带宽已然被挤满了(瓶颈在客户端或者服务器),开多个线程也不会改变网络固有的传输能力。
raywong
2019-10-16 19:24:01 +08:00
tiedan
2019-10-16 19:24:11 +08:00
你多加几块磁盘,然后并发在不同磁盘同时写,你就会发现比同步写快了
reus
2019-10-16 23:19:07 +08:00
这样做没有意义,因为一个请求是必然顺序传输到服务器,你开多少线程,都不会影响传输速度。
反而可能造成问题,如果有人构造一个几百个文件的请求,你也开几百个 goroutine 吗…
这里按顺序处理就行。
encro
2019-10-17 09:19:58 +08:00
我的理解是:
正确的测试方法是开启多个 client 同时上传到 server,
单个 server function 里面没有必要再 goroutine,因为 gin 本身执行 server function 就是采用了 goroutine 吧。这里面的瓶颈很可能在于 disk I/O。
Reficul
2019-10-17 09:22:50 +08:00
看一下 HTTP 报文,你就知道一个请求里,文件都是一个个排队发过来的。
raywong
2019-10-17 09:46:57 +08:00
@Reficul 是一个个排队发送的原因吗,后台不是拿到全部文件后再写入磁盘的?
raywong
2019-10-17 09:51:15 +08:00
@encro 这里 disk I/O 的影响,也就是说还是要一个个排队写入 disk 么?
raywong
2019-10-17 09:53:48 +08:00
@reus 可能会限制一下一次性能上传多少个文件。虽然是顺序传输到服务器,服务器不是拿到全部文件后再写入磁盘的?
encro
2019-10-17 10:03:41 +08:00
磁盘速度只有这么快,你用多少个线程,已经不重要了。

“一个妈妈怀胎 10 个月,10 个妈妈还是 10 个月”。
encro
2019-10-17 10:04:58 +08:00
如果再 unix/linux 上,试试直接复制到 /dev/null,应该快很多。
raywong
2019-10-17 10:15:21 +08:00
@encro 明白了 谢谢。
reus
2019-10-17 10:16:09 +08:00
@raywong 是打包成一个 multipart form 发送,不是排队发送。进入这个处理函数的时候,应该是已经 parse 完了的,所以你开不开 goroutine,前面的都没影响。开不开 goroutine,区别是并行写入磁盘与否,这里没有区别,就说明磁盘 io 不是瓶颈。

用快递比喻,就是几个订单(文件)都用一个包裹(请求)发送给你,你有多少个人拆,都影响不了物流过程。而拆箱过程很短,你一个人拆和几个人拆时间都差不多。
raywong
2019-10-17 11:14:41 +08:00
@reus 所以意思是说时间大部分都是消耗在传输上,文件越多传输得也自然就越慢了。还有一个问题就是这里确实是并发将文件写入磁盘了吗(忽略传输时间)?换句话说开不开 goroutine 对磁盘写入有没有影响。
reus
2019-10-17 11:31:51 +08:00
@raywong 看看这一段代码运行了多久就知道了
lazyfighter
2019-10-17 11:33:29 +08:00
那是不是说明,瓶颈不再磁盘上,而是在上传本身上

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

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

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

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

© 2021 V2EX