« Go 语言初学实践(1) | 返回首页 | Go 语言初学实践(3) »

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,性能不高呀
不用锁的代码无法保证一致性,只能在特定场所使用
关于第二段有个疑问: 如果有2个goroutine,分别为g1,g2,几乎同时操作, 假如请求输入chan的顺序是res1,res2, g1,g2都在等待响应输出chan的消息, 那么能保证响应输出chan的rep1能回到g1;rep2能回到g2,而不至于混乱吗?
我中心的设备一流,人才济济
郑州人民欢迎你
嗯,我回来看看
下次再来哈!
@Kabie: 你说的挺有道理。返回量也行得通,但Cloud的方法似乎更简单。(另外,我看了一下Go Specification,似乎内置的Map type对于不存在的key会返回一个, 所以Cloud的方法更自然吧) 关于interface{},我猜不用多虑了,因为这里Cloud是把Map做成了一个封闭的功能块,不会出现另外一种interface{}。
mark下,以后学习go语言后,再回头看看。
来看看!
@zhanxw: 如果要表示原先是否存在值……应当返回第二个值true or false……而不是跟C一样在原值上做手脚……设置值后返回nil导致不能将值设为nil(虽然可能不会这样做)……无论如何都返回值一定程度上能简化函数和客户代码…… 另外万一未来要加入另一个interface{}……两个用在不同地方的interface{}就会混淆了……所以最好还是特别起一个名字……
@Kabie: 返回nil是为了说明map中没有该元素,返回其他值说明已经有了这个key。 其实把 interface {} 写成 type ValueT {} 也能work,我猜这里是为了更直接的表示任何一个类型。 @Cloud:云风对Go的性能怎么看?在数值运算方面,即使LuaJIT也会快出Go不少。这样来看Go用在哪些领域比较合适?
@ray 谢谢,排版代码的时候整掉了,现在改过来了 :)
case r := case r := <- c.pop_req: c.pop_req <- r 虽然不懂go,但貌似这段代码不全,根本没去查询m,至少后面那句应该是c.pop_rep <- r
其实这种时候是应该为"interface {}"另起一个名字……看起来会清晰一些 push看起来有点像python里面dict的setdefault……不过设置值之后返回nil有些奇怪……
m map[string] interface {}这句看了好一会儿才反应过来是一个key为string,value为interface的map。又过了好一会儿才知道interface可以当作泛型来用,先入为主的c语言坑爹啊这是T_T
Google为何要开发Simle和Go,基于哪些考虑? 求解。
感觉go在谷歌的引导下会很有开发前途
习惯c了 对这个go语言不是很习惯

Post a comment

非这个主题相关的留言请到:留言本