Go 语言初学实践(2)
继续昨天的。
现在要实现一个特殊的 map ,支持 push 和 pop 两个操作。看起来是这样的:
type myMap interface { push(key string, e interface {}) interface{} pop(key string) interface{} }
当 push 的时候,如果 map 中 key 已存在,返回原来对应的 value ;若 key 不存在,则创建一个新的 key 把 value 放进去。
而 pop 操作,返回 key 对应的 value ,并把 key/value 对从 map 中删除。
鉴于 Go 程序中通常会使用大量 goroutine ,所以,这个 map 应该是线程安全的。那么用 Go 怎么实现它呢?最简单也就是最传统的方式是使用锁,即 sync.Mutex ,代码如下:
package main import ( "fmt" "sync" ) type myMap struct { m map[string] interface {} sync.Mutex } func (m *myMap) push(key string, e interface {}) interface {} { m.Lock() defer m.Unlock() if v,exist := m.m[key] ; exist { return v; } m.m[key] = e return nil } func (m *myMap) pop(key string) interface {} { m.Lock() defer m.Unlock() if v,exist := m.m[key] ; exist { m.m[key] = nil, false return v } return nil } func newMap() *myMap { return &myMap { m: make(map[string] interface {}) } } func main() { m := newMap() fmt.Println(m.push("hello","world")) fmt.Println(m.push("hello","world")) fmt.Println(m.pop("hello")) fmt.Println(m.pop("hello")) }
这里,newMap() 方法会返回一个 myMap 指针。其实按之前的定义,返回 muMap interface 也可以,它们在功能上是等价的。 但这里不可以返回 myMap 结构。因为,其中包含有一个其它包里的结构 sync.Mutex ,它是不可以被复制的。Go 里面没有 C++ 中重载赋值运算那些污七八糟的语法糖,所以用指针就好了。反正有 gc 不用担心。
其实,这个东东也可以把所有对 map 的操作放到同一个 goroutine 里完成,就不需要显式的锁了。不过具体到这个需求上,这么实现的意义实在有限。下面列出代码来,只是说可以这么做而已。
package main import "fmt" type myMap interface { push(key string, e interface {}) interface{} pop(key string) interface{} } type myMapPair struct { key string value interface {} } type mapChan struct { push_req chan * myMapPair push_rep chan interface{} pop_req chan string pop_rep chan interface{} } func (c *mapChan) push(key string, e interface{}) interface{} { c.push_req <- & myMapPair {key,e} return <- c.push_rep } func (c *mapChan) pop(key string) interface {} { c.pop_req <- key return <- c.pop_rep } func newMap() myMap { c := mapChan { push_req : make (chan * myMapPair), push_rep : make (chan interface{}), pop_req : make (chan string), pop_rep : make (chan interface{}), } m := make(map[string] interface {}) go func() { for { select { case r := <- c.push_req : if v , exist := m[r.key] ; exist { c.push_rep <- v } else { m[r.key] = r.value c.push_rep <- nil } case r := <- c.pop_req: if v,exist := m[r] ; exist { m[r] = nil, false c.pop_rep <- v } else { c.pop_rep <- nil } } } } () return &c } func main() { m := newMap() fmt.Println(m.push("hello","world")) fmt.Println(m.push("hello","world")) fmt.Println(m.pop("hello")) fmt.Println(m.pop("hello")) }
这里建立了四个 chan ,也就是两对请求/回应通道,让单一的 goroutine 来处理两种对 map 操作的请求。这个处理的 goroutine 可以看成是对其它 goroutine 提供的服务。能这么方便的使用这种模式,在大多数编程语言里并不多见。Erlang 可以算一个。但 Go 的优势在于,允许你使用可能对你更为习惯的命令式编程方式,而不需要转换思维到函数式编程。
Comments
感觉四个chan,还是unbuffered chan,性能不高呀
Posted by: red.snapper | (19) March 24, 2023 12:03 PM
不用锁的代码无法保证一致性,只能在特定场所使用
Posted by: 拉拉拉 | (18) May 6, 2013 11:30 AM
关于第二段有个疑问:
如果有2个goroutine,分别为g1,g2,几乎同时操作,
假如请求输入chan的顺序是res1,res2,
g1,g2都在等待响应输出chan的消息,
那么能保证响应输出chan的rep1能回到g1;rep2能回到g2,而不至于混乱吗?
Posted by: damacheng009 | (17) October 31, 2012 11:43 AM
我中心的设备一流,人才济济
Posted by: 郑州脑残整治收容所 | (16) March 8, 2011 09:43 PM
郑州人民欢迎你
Posted by: 郑州人民英雄记念杯永垂不朽 | (15) March 8, 2011 09:23 PM
嗯,我回来看看
Posted by: 郑州医疗废弃物焚烧中心 | (14) March 8, 2011 09:12 PM
下次再来哈!
Posted by: 郑州儿科治疗中心 | (13) March 7, 2011 10:41 AM
@Kabie: 你说的挺有道理。返回量也行得通,但Cloud的方法似乎更简单。(另外,我看了一下Go Specification,似乎内置的Map type对于不存在的key会返回一个<nil>, 所以Cloud的方法更自然吧)
关于interface{},我猜不用多虑了,因为这里Cloud是把Map做成了一个封闭的功能块,不会出现另外一种interface{}。
Posted by: zhanxw | (12) March 7, 2011 01:30 AM
mark下,以后学习go语言后,再回头看看。
Posted by: kmplayer | (11) March 4, 2011 10:25 PM
来看看!
Posted by: 郑州第二中医院儿科 | (10) March 4, 2011 12:29 PM
@zhanxw: 如果要表示原先是否存在值……应当返回第二个值true or false……而不是跟C一样在原值上做手脚……设置值后返回nil导致不能将值设为nil(虽然可能不会这样做)……无论如何都返回值一定程度上能简化函数和客户代码……
另外万一未来要加入另一个interface{}……两个用在不同地方的interface{}就会混淆了……所以最好还是特别起一个名字……
Posted by: Kabie | (9) March 4, 2011 11:47 AM
@Kabie: 返回nil是为了说明map中没有该元素,返回其他值说明已经有了这个key。
其实把 interface {} 写成 type ValueT {} 也能work,我猜这里是为了更直接的表示任何一个类型。
@Cloud:云风对Go的性能怎么看?在数值运算方面,即使LuaJIT也会快出Go不少。这样来看Go用在哪些领域比较合适?
Posted by: zhanxw | (8) March 3, 2011 10:15 PM
@ray
谢谢,排版代码的时候整掉了,现在改过来了 :)
Posted by: Cloud | (7) March 3, 2011 12:05 PM
case r := <- c.pop_req:
c.pop_req <- r
虽然不懂go,但貌似这段代码不全,根本没去查询m,至少后面那句应该是c.pop_rep <- r
Posted by: ray | (6) March 3, 2011 09:34 AM
其实这种时候是应该为"interface {}"另起一个名字……看起来会清晰一些
push看起来有点像python里面dict的setdefault……不过设置值之后返回nil有些奇怪……
Posted by: Kabie | (5) March 3, 2011 01:30 AM
m map[string] interface {}这句看了好一会儿才反应过来是一个key为string,value为interface的map。又过了好一会儿才知道interface可以当作泛型来用,先入为主的c语言坑爹啊这是T_T
Posted by: xuruoji | (4) March 3, 2011 12:13 AM
Google为何要开发Simle和Go,基于哪些考虑?
求解。
Posted by: ShinyWang | (3) March 2, 2011 10:38 PM
感觉go在谷歌的引导下会很有开发前途
Posted by: FakeFish | (2) March 2, 2011 08:58 PM
习惯c了 对这个go语言不是很习惯
Posted by: FakeFish | (1) March 2, 2011 08:49 PM