为了账号安全,请及时绑定邮箱和手机立即绑定

有多个通道与单个共享结构进行通信是否线程安全?

有多个通道与单个共享结构进行通信是否线程安全?

Go
Smart猫小萌 2021-09-13 19:58:35
考虑以下代码:type Cache struct{    cache map[string]*http.Response    AddChannel chan *http.Response    RemoveChannel chan *http.Response    FindChannel chan string}func (self *Cache) Run(){    select{        case resp := <-self.AddChannel:        //..code        case resp := <- self.RemoveChannel:        //..code        case find := <- self.FindChannel:        //..code    }}在这段代码中,创建了一个缓存,并在一个单独的 goroutine 上调用了 Run 函数。如果要缓存响应,则通过缓存的AddChannel;如果要删除响应,则通过 RemoveChannel如果需要找到响应,则通过FindChannel.这是一种保护缓存免受竞争条件影响的线程安全方式,还是有可能,例如,相同的响应可以发送到AddChannel和RemoveChannel导致缓存损坏。我已经阅读了 Go 的内存模型文档,并了解到通过通道发送变量肯定会在接收之前发生,但我有点困惑,如果有多个通道与单个实例进行通信,这是否仍然成立。对不起,如果我的问题措辞不好,感谢您的帮助。
查看完整描述

2 回答

?
小怪兽爱吃肉

TA贡献1852条经验 获得超1个赞

原则上,通道的使用是确保同步访问结构数据的有效方法。我在您的方法中看到的问题是您的Run函数只执行一次读取然后返回。只要您Run每次都从同一个 goroutine调用,它可能会起作用,但有一种更简单的方法。


只有将所有 struct 访问限制在一个且仅一个 goroutine 中才能保证内存安全。我通常这样做的方法是创建一个在通道上循环的轮询例程。无限期地,或直到它被明确停止。


这是一个例子。我为每个支持的操作创建了单独的频道,主要是为了更清楚地说明正在发生的事情。您可以轻松地使用单个通道,如chan interface{},并打开接收到的消息类型以查看您应该执行哪种操作。这种设置非常松散地基于 Erlang 的消息传递概念。它需要大量的样板来设置,但不需要互斥锁。它是否高效和可扩展,只有通过测试才能发现。还要注意,它包含了大量的分配开销。


package main


import "fmt"


func main() {

    t := NewT()

    defer t.Close()


    t.Set("foo", 123)

    fmt.Println(t.Get("foo"))


    t.Set("foo", 321)

    fmt.Println(t.Get("foo"))


    t.Set("bar", 456)

    fmt.Println(t.Get("bar"))

}


type T struct {

    get  chan getRequest

    set  chan setRequest

    quit chan struct{}


    data map[string]int

}


func NewT() *T {

    t := &T{

        data: make(map[string]int),

        get:  make(chan getRequest),

        set:  make(chan setRequest),

        quit: make(chan struct{}, 1),

    }


    // Fire up the poll routine.

    go t.poll()

    return t

}


func (t *T) Get(key string) int {

    ret := make(chan int, 1)

    t.get <- getRequest{

        Key:   key,

        Value: ret,

    }

    return <-ret

}


func (t *T) Set(key string, value int) {

    t.set <- setRequest{

        Key:   key,

        Value: value,

    }

}


func (t *T) Close() { t.quit <- struct{}{} }


// poll loops indefinitely and reads from T's channels to do

// whatever is necessary. Keeping it all in this single routine,

// ensures all struct modifications are preformed atomically.

func (t *T) poll() {

    for {

        select {

        case <-t.quit:

            return


        case req := <-t.get:

            req.Value <- t.data[req.Key]


        case req := <-t.set:

            t.data[req.Key] = req.Value

        }

    }

}


type getRequest struct {

    Key   string

    Value chan int

}


type setRequest struct {

    Key   string

    Value int

}



查看完整回答
反对 回复 2021-09-13
  • 2 回答
  • 0 关注
  • 177 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信