请考虑以下代码:
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的内存模型文档,并了解保证通过通道发送变量可以保证在接收之前发生,但是如果有多个通道与a通信,我是否仍然有些困惑单个实例。
很抱歉,如果我措辞严厉,谢谢你的帮助。
答案 0 :(得分:3)
原则上,使用通道是确保同步访问结构数据的有效方法。我用你的方法看到的问题是你的Run
函数只做一次读取然后返回。只要您每次都从同一个goroutine拨打Run
,它就可以运行,但这样会更容易。
只有所有结构访问仅限于一个,而且只有一个goroutine,才能保证内存安全。我通常这样做的方法是创建一个循环在通道上的轮询例程。无限期,或直到明确停止。
Here is an example。我为每个支持的操作创建了单独的通道,主要是为了让它更清晰。您可以轻松使用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
}
答案 1 :(得分:3)
是的,select只会等待或执行一个case块。 因此,如果您在任何时候只有一个Run函数,并且您知道没有其他goroutine会改变缓存,那么它将无竞争。
我假设你想要一个围绕选择的无限循环。
这是一个例子,你可以看到当一个人正在执行时,选择不会进入另一个区块...... https://play.golang.org/p/zFeRPK1h8c
btw,'self' is frowned upon作为接收者名称。