猿问

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

考虑以下代码:


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 的内存模型文档,并了解到通过通道发送变量肯定会在接收之前发生,但我有点困惑,如果有多个通道与单个实例进行通信,这是否仍然成立。


对不起,如果我的问题措辞不好,感谢您的帮助。


Smart猫小萌
浏览 178回答 2
2回答

小怪兽爱吃肉

原则上,通道的使用是确保同步访问结构数据的有效方法。我在您的方法中看到的问题是您的Run函数只执行一次读取然后返回。只要您Run每次都从同一个 goroutine调用,它可能会起作用,但有一种更简单的方法。只有将所有 struct 访问限制在一个且仅一个 goroutine 中才能保证内存安全。我通常这样做的方法是创建一个在通道上循环的轮询例程。无限期地,或直到它被明确停止。这是一个例子。我为每个支持的操作创建了单独的频道,主要是为了更清楚地说明正在发生的事情。您可以轻松地使用单个通道,如chan interface{},并打开接收到的消息类型以查看您应该执行哪种操作。这种设置非常松散地基于 Erlang 的消息传递概念。它需要大量的样板来设置,但不需要互斥锁。它是否高效和可扩展,只有通过测试才能发现。还要注意,它包含了大量的分配开销。package mainimport "fmt"func main() {&nbsp; &nbsp; t := NewT()&nbsp; &nbsp; defer t.Close()&nbsp; &nbsp; t.Set("foo", 123)&nbsp; &nbsp; fmt.Println(t.Get("foo"))&nbsp; &nbsp; t.Set("foo", 321)&nbsp; &nbsp; fmt.Println(t.Get("foo"))&nbsp; &nbsp; t.Set("bar", 456)&nbsp; &nbsp; fmt.Println(t.Get("bar"))}type T struct {&nbsp; &nbsp; get&nbsp; chan getRequest&nbsp; &nbsp; set&nbsp; chan setRequest&nbsp; &nbsp; quit chan struct{}&nbsp; &nbsp; data map[string]int}func NewT() *T {&nbsp; &nbsp; t := &T{&nbsp; &nbsp; &nbsp; &nbsp; data: make(map[string]int),&nbsp; &nbsp; &nbsp; &nbsp; get:&nbsp; make(chan getRequest),&nbsp; &nbsp; &nbsp; &nbsp; set:&nbsp; make(chan setRequest),&nbsp; &nbsp; &nbsp; &nbsp; quit: make(chan struct{}, 1),&nbsp; &nbsp; }&nbsp; &nbsp; // Fire up the poll routine.&nbsp; &nbsp; go t.poll()&nbsp; &nbsp; return t}func (t *T) Get(key string) int {&nbsp; &nbsp; ret := make(chan int, 1)&nbsp; &nbsp; t.get <- getRequest{&nbsp; &nbsp; &nbsp; &nbsp; Key:&nbsp; &nbsp;key,&nbsp; &nbsp; &nbsp; &nbsp; Value: ret,&nbsp; &nbsp; }&nbsp; &nbsp; return <-ret}func (t *T) Set(key string, value int) {&nbsp; &nbsp; t.set <- setRequest{&nbsp; &nbsp; &nbsp; &nbsp; Key:&nbsp; &nbsp;key,&nbsp; &nbsp; &nbsp; &nbsp; Value: value,&nbsp; &nbsp; }}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() {&nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case <-t.quit:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; case req := <-t.get:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; req.Value <- t.data[req.Key]&nbsp; &nbsp; &nbsp; &nbsp; case req := <-t.set:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; t.data[req.Key] = req.Value&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}type getRequest struct {&nbsp; &nbsp; Key&nbsp; &nbsp;string&nbsp; &nbsp; Value chan int}type setRequest struct {&nbsp; &nbsp; Key&nbsp; &nbsp;string&nbsp; &nbsp; Value int}
随时随地看视频慕课网APP

相关分类

Go
我要回答