我正在尝试减少我的不和谐机器人发出的 http 请求数量。
它是从 API 读取的。
使用获取的数据,它会更新内部数据库并输出更改。
问题是:该数据库对于机器人所在的每个服务器都是不同的,这就是我使用 go 例程的地方。但是,有些服务器需要获取相同的数据,这里是我想减少 http 请求的地方。现在我正在发出请求,无论我是否已经获取了一个字符。我想创建某种可以在 go 例程之间共享的数据,然后在这些数据中进行请求搜索。
有人建议我使用互斥锁。我正在努力。原问题:Working with unbuffered channels in golang
我制作了我尝试过的真实代码的骨架:https://play.golang.org/p/mt229ns1R8m
在此示例中 master := make([][]map[string]interface{}, 0)
正在模拟不和谐服务器。Chars
和 Chars2
将是每个单独服务器的跟踪字符。
char "Test"对它们都是相互的,所以它应该只从 API 中获取一次。
它输出这个:
[[map[Level:15 Name:Test] map[Level:150 Name:Test2]] [map[Level:1500 Name:Test3] map[Level:15 Name:Test]]]
------
A call would be made
A call would be made
A call would be made
A call would be made
Cache: [map[Level:150 Name:Test2] map[Level:15 Name:Test]]Cache: [map[Level:15 Name:Test] map[Level:1500 Name:Test3]]Done
我期望输出是:[[map[Level:15 Name:Test] map[Level:150 Name:Test2]] [map[Level:1500 Name:Test3] map[Level:15 Name:Test]]]
------
A call would be made
A call would be made
A call would be made
Cache: [map[Level:150 Name:Test2] map[Level:15 Name:Test] map[Level:1500 Name:Test3]]Done
但是每个 goroutine 都会生成一个新的缓存。我怎样才能解决这个问题?谢谢。
最佳答案
这里有太多未知数,我无法真正写出合适的设计,但让我们做一些说明:
interface{}
完全,如果可能的话。在这种情况下,似乎它必须是可能的,尽管我不确定实际的类型是什么。这可能会给你一个像这样的功能界面:
var cacheServer *CacheServer
func InitCacheServer() error {
cacheServer = ... // whatever it takes to initialize the cache server
}
(我假设缓存服务器的延迟初始化。如果您可以进行预先初始化,则可以放弃下面的下一个测试。将 ValueType
替换为名称的缓存查找结果的类型。)func (DiscordServer ds) Get(name string) (ValueType, error) {
if cacheserver == nil {
if err := InitCacheServer(); err != nil {
return nil, err
}
}
// Do a cache lookup. Tell the cache server that if there
// is no entry, it should return a NoEntry error and we will
// fill the cache ourselves, so it should hold this slot as
// "will be filled, so wait for it".
slot, v, err := cacheServer.Lookup(name, ds.identity, CacheServer.IntentToFill)
if err == CacheServer.NoEntry {
// We have the slot held. Try to look up the right info
// directly in the Discord server, then cache it.
v, err = ds.UncachedGet(name)
// Tell cache server that this is the value, or that it should
// produce this error instead of NoCache.
cacheServer.FillSlot(slot, v, err)
}
}
您可能只想缓存一些错误类型,而不是全部;这是另一个需要我无法在此处提供的答案的设计问题。还有其他方法可以做到这一点,不一定需要 slot
指针返回值;我刚刚为这个例子选择了这个。请注意,大部分“艰苦的工作”现在都在缓存服务器中,这肯定需要一些花哨的步法。特别是你会想要锁定整个数据结构一段时间,用它来找到正确的槽,然后保持槽本身,以便槽的其他用户必须等待,同时释放整体锁,以便其他用户的其他用户条目无需等待。这引入了锁定顺序约束:小心避免死锁。一种应该有效的方法是:
type CacheServer struct {
lock sync.Mutex
data map[string]map[string]*Entry
// more fields
}
type Entry {
lock sync.Mutex
cachedValue ValueType
cachedError error
}
(你需要更多的类型,比如 Intent
——现在只有两个枚举整数——在下面,可能还有更多的字段;这只是一个骨架。)func (cs *CacheServer) Lookup(name, srv string, flags Intent) (*Entry, ValueType, error) {
cs.lock.Lock()
defer cs.lock.Unlock()
// first, look up the server - if it does not exist, create one
smap := cs.data[srv]
if smap == nil {
cs.data[server] = make(map[string]*Entry)
}
entry := smap[name]
if entry == nil {
// no cached entry - if this is a pure lookup, just error,
// but if not, make a locked entry
if flags == CacheServer.IntentToFill {
// make a new entry and return with it locked
entry = &Entry{}
smap[name] = entry
entry.lock.Lock() // and do not unlock
}
return entry, nil, NoEntry
}
entry.lock.Lock() // wait for someone to fill it, if needed
defer entry.lock.Unlock()
return nil, entry.cachedValue, entry.cachedError
}
您还需要一个例程来填充和释放条目,但这很简单。如果您愿意,您可以在 Entry
上将此作为方法。键入而不是在 CacheServer
上类型,至少在这个特定的原型(prototype)中,不需要直接使用缓存服务器数据结构。但是,如果您开始对缓存失效有更多兴趣,那么访问 CacheServer
可能会很好。目的。注意:我设计了这个,以便您可以在没有意图填充的情况下进行缓存查找,如果这有用的话。如果没有,没有理由打扰
Intent
争论。
关于go - 跨多个 goroutine 使用单个互斥锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62962702/