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

Go:稀疏数组读写的线程安全并发问题

Go:稀疏数组读写的线程安全并发问题

Go
白衣染霜花 2021-09-27 16:43:37
我正在用 Go 编写一个搜索引擎,其中我有一个单词倒排索引到每个单词的相应结果。有一组单词字典,因此单词已经转换为 a StemID,它是一个从 0 开始的整数。这允许我使用一片指针(即 a sparse array)将每个指针映射StemID到包含结果的结构询问。例如var StemID_to_Index []*resultStruct。如果aardvark是,0则指向 resultStruct 的指针aardvark位于StemID_to_Index[0],nil如果当前未加载该单词的结果,则该指针将位于。服务器上没有足够的内存来将所有这些存储在内存中,因此每个结构StemID将保存为单独的文件,并且可以将这些文件加载到StemID_to_Index切片中。如果StemID_to_Index当前nil为此,StemID则结果未缓存并需要加载,否则它已经加载(缓存),因此可以直接使用。每次加载新结果时,都会检查内存使用情况,如果超过阈值,则丢弃 2/3 的加载结果(这些 StemIDStemID_to_Index设置nil为 并强制进行垃圾收集。)我的问题是并发。什么是最快和最有效的方法,我可以同时搜索多个线程,而不会出现不同线程尝试同时读取和写入同一位置的问题?我试图避免在所有内容上使用互斥锁,因为这会减慢每次访问尝试的速度。您认为我会在工作线程中从磁盘加载结果,然后使用通道将指向该结构的指针传递给“更新程序”线程,然后nil将StemID_to_Index切片中的值更新为加载结果的指针吗?这意味着两个线程永远不会尝试同时写入,但是如果另一个线程尝试从StemID_to_Index“更新程序”线程更新指针的确切索引中读取会发生什么?如果给一个线程一个nil当前正在加载的结果的指针并不重要,因为它只会被加载两次,虽然这是一种资源浪费,但它仍然会提供相同的结果,因为这不太可能发生很多时候,这是可以原谅的。此外,将要更新的指针发送到“更新程序”线程的工作线程如何知道“更新程序”线程何时完成更新切片中的指针?它应该只是休眠并继续检查,还是有一种简单的方法让更新程序将消息发送回推送到通道的特定线程?
查看完整描述

2 回答

?
慕丝7291255

TA贡献1859条经验 获得超6个赞

我的最佳答案是将elasticsearch与像elastigo这样的客户端一起使用。

如果这不是一种选择,那么了解您对种族行为的关心程度真的会有所帮助。如果您不关心,读取完成后可能会立即发生写入,完成读取的用户将获得陈旧数据。您可以只拥有一个写入和读取操作队列,并让多个线程进入该队列,并且一个调度程序在它们到来时一次一个地向映射发出操作。在所有其他场景中,如果有多个读者和作者,您将需要一个互斥锁。地图在 go中不是线程安全的

老实说,我只想添加一个互斥锁来让事情变得简单,并通过分析瓶颈实际所在的位置来优化。看起来您检查阈值然后清除 2/3 的缓存有点武断,如果您通过这样做来降低性能,我不会感到惊讶。这是会崩溃的情况:

请求者 1、2、3 和 4 经常访问文件 A 和 B 上的许多相同词。请求者 5、6、7 和 8 经常访问存储在文件 C 和 D 中的许多相同词。

现在,当这些请求者和文件之间交错的请求快速连续发生时,您最终可能会一遍又一遍地清除 2/3 的缓存,这些结果可能很快就会被请求。还有其他几种方法:

  1. 缓存在同一个盒子上同时被频繁访问的词,并且有多个缓存盒子。

  2. 对每个单词进行缓存,并对该单词的流行程度进行某种排序。如果在缓存已满的情况下从文件中访问了一个新词,请查看该文件中是否还有其他更流行的词,并清除缓存中不太流行的条目,希望这些词具有更高的命中率。

  3. 两种方法 1 和 2。


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

添加回答

举报

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