文档章节

从一个WaitGroup的例子看Go语言的Upvalue的传递

LinkerLin
 LinkerLin
发布于 2017/01/02 00:08
字数 1088
阅读 552
收藏 4

Go语言的闭包捕获的外部变量,我还是习惯以Lua的叫法,称之为Upvalue,毕竟Go借鉴了很多Lua的特性。

让我们首先看五个几乎一样的代码片段。

package main

import (
	"log"
	"sync"
)

func main() {
	wg := sync.WaitGroup{}
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(wg sync.WaitGroup, i int) {
			log.Printf("i:%d", i)
			wg.Done()
		}(wg, i)
	}
	wg.Wait()
	log.Println("exit")
}

输出:

go run wgtest1.go 
2017/01/01 23:43:08 i:4
2017/01/01 23:43:08 i:2
2017/01/01 23:43:08 i:3
2017/01/01 23:43:08 i:1
2017/01/01 23:43:08 i:0
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc42000a2ac)
	/usr/local/Cellar/go/1.7.4_1/libexec/src/runtime/sema.go:47 +0x30
sync.(*WaitGroup).Wait(0xc42000a2a0)
	/usr/local/Cellar/go/1.7.4_1/libexec/src/sync/waitgroup.go:131 +0x97
main.main()
	/Users/linkerlin/gos/wgtest1.go:17 +0xba
exit status 2

这是因为Go语言中WaitGroup是一个不可以在第一次使用后复制的对象。而goroutine的主函数其实是传值的方法传递了WaitGroup。这里可以特别注意下i的输出是符合预期的。

好,让我们接下来看第二段代码:

package main

import (
	"log"
	"sync"
)

func main() {
	wg := sync.WaitGroup{}
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			log.Printf("i:%d", i)
			wg.Done()
		}()
	}
	wg.Wait()
	log.Println("exit")
}

输出:

go run wgtest2.go 
2017/01/01 23:48:10 i:5
2017/01/01 23:48:10 i:5
2017/01/01 23:48:10 i:5
2017/01/01 23:48:10 i:5
2017/01/01 23:48:10 i:5
2017/01/01 23:48:10 exit

没有死锁,但是i值的输出是错误的。因为,Go语言里面upvalue是引用的。Goroutine多次捕获的是同一个i。

再来,我们看第三段代码:

package main

import (
	"log"
	"sync"
)

func main() {
	wg := sync.WaitGroup{}
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			log.Printf("i:%d", i)
			wg.Done()
		}()
	}
	wg.Wait()
	log.Println("exit")
}

输出:

go run wgtest3.go 
2017/01/01 23:51:46 i:5
2017/01/01 23:51:46 i:5
2017/01/01 23:51:46 i:5
2017/01/01 23:51:46 i:4
2017/01/01 23:51:46 i:5
2017/01/01 23:51:46 exit

没死锁,i的数值还是不对。因为upvaule的i是byRef传递。注意,这里出现了4个5和一个4,最终输出什么其实是随机,取决于操作系统和硬件。goroutine调度的越快,就越可能出现比5小的输出。

再来,我们看第四段代码:

package main

import (
	"log"
	"sync"
)

func main() {
	wg := sync.WaitGroup{}
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(wg *sync.WaitGroup, i int) {
			log.Printf("i:%d", i)
			wg.Done()
		}(&wg, i)
	}
	wg.Wait()
	log.Println("exit")
}

输出:

go run wgtest4.go 
2017/01/01 23:56:51 i:1
2017/01/01 23:56:51 i:0
2017/01/01 23:56:51 i:4
2017/01/01 23:56:51 i:2
2017/01/01 23:56:51 i:3
2017/01/01 23:56:51 exit

一切正常,符合预期。但是,这种写法却比较累赘。首先,没有利用闭包的upvalue来构建一个高阶函数,而是恢复到传统的传值,同时这种写法对写代码的人的心智负担太重了,传值和传引用要手动指定,而且还要在goroutine的主函数入口一一指定。那么我们推荐的写法应该是什么样子的呢?

最后,来看第五段代码:

package main

import (
	"log"
	"sync"
)

func main() {
	wg := sync.WaitGroup{}
	for i := 0; i < 5; i++ {
		func(i int) {
			wg.Add(1)
			go func() {
				log.Printf("i:%d", i)
				wg.Done()
			}()
		}(i)
	}
	wg.Wait()
	log.Println("exit")
}

输出:

go run wgtest5.go 
2017/01/02 00:03:32 i:4
2017/01/02 00:03:32 i:0
2017/01/02 00:03:32 i:1
2017/01/02 00:03:32 i:2
2017/01/02 00:03:32 i:3
2017/01/02 00:03:32 exit

一样的一切正常。但是在第五段代码中,Goroutine的主函数是没有参数的。传引用的情况利用了upvalue,而需要传值的i变量用了一个外包函数的参数来复制。因为每次循环都会调用这个外包函数,从而复制了一次i的数值,虽然里层的Goroutine主函数还是 通过 upvalue来捕获i,不过每次捕获的都是外包函数的i副本而已。

综上所述,处于降低开发人员心智负担的考虑,我建议:

    1. Go语言里面的goroutine的入口函数不要传递参数。

    2. 所有的传ref参数都通过upvalue来捕获。  

    3. 如果要传值,可以在goroutine外面包一个函数,把要传value的参数用传值的方法传给这个外包的函数。参数名保持同名。

 

 

 

 

© 著作权归作者所有

共有 人打赏支持
LinkerLin

LinkerLin

粉丝 71
博文 63
码字总数 14161
作品 1
长宁
程序员
私信 提问
闭包的概念 [翻译]

解释函数式编程中的闭包概念,非完全翻译自 wiki: closure 文章中使用英文名词,以防止翻译不当和歧义 原文: wiki: closure 译文: 闭包的概念 译者: Breaker 译文 closure 概念 closure 亦称...

晨曦之光
2012/05/23
280
0
编译原理之学习 lua 3.1 (七) Closure 闭包支持

lua 3.1 与其前一个版本 3.0 比, 有了很大的变化, 可参见历史文件 HISTORY. 我们关心的有: 1. 解析由 LR 的变成手写的 LL 递归下降解析器了, 文法变化,代码生成变化了; 2. 新概念: 闭包 (clo...

刘军兴
2013/12/28
0
0
Lua4.0 参考手册(四)4.6-4.8

(接上篇) ------------------- 4.6 可见性和 Upvalue ------------------- 一个函数体可以引用它自己的局部变量(包括它的参数)和全局变量,只要它们没有被函数中同名的局部变量所隐藏(s...

晓寒
2014/11/05
0
0
Lua4.0 参考手册(六)5.8-5.14

(接上篇) ------------------- 5.8 执行 Lua 代码 ------------------- 一个宿主程序可以执行写在文件中或在字符串中的 Lua 块,使用下面的函数: int luadofile (luaState L, const char ...

晓寒
2014/11/10
0
0
编写C函数的技术-《lua程序设计》 27章 学习

1.数组操作 void luarawgeti(luaState * L ,int index,int key) void luarewseti(luaState * L,int index,int key) index表示table在栈的位置,key表示元素在table中的位置 test.lua内容 tab ......

技术小阿哥
2017/11/26
0
0

没有更多内容

加载失败,请刷新页面

加载更多

centos 最小化安装后安装vmtools故障后解决方法

本文测试环境是centos7最小化安装方式。 安装vmtools时提示错误: What is the location of the "ifconfig" program on your machine?。 安装net-tools即可。 安装过程中可能还需要安装gcc,直...

白豆腐徐长卿
17分钟前
0
0
《人月神话》读后感

前两天看完了《人月神话》,最初是被这本书的名字吸引了,然后再在上搜了一下,人月确实是一个神话,那种不可能实现的神话……(!| ̄▽ ̄) 好久没有看过这种每章开始都引用一段名人名言的书了...

ninjaFrog
昨天
4
0
matlab-线性代数 施密特正交化

  matlab : R2018a 64bit     OS : Windows 10 x64 typesetting : Markdown    blog : my.oschina.net/zhichengjiu    gitee : gitee.com/zhichengjiu   code clearclc% 施密特正......

志成就
昨天
3
0
08 分支管理 —— 多人协作

08 分支管理 —— 多人协作 多人协作 本节内容: 查看远程库信息,使用git remote -v;本地新建的分支如果不推送到远程,对其他人就是不可见的;从本地推送分支,使用git push origin bra...

lwenhao
昨天
6
0
开始使用Filebeat

认识Beats Beats是用于单用途数据托运人的平台。它们以轻量级代理的形式安装,并将来自成百上千台机器的数据发送到Logstash或Elasticsearch。 (画外音:通俗地理解,就是采集数据,并上报到...

北极南哥
昨天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部