【发布时间】:2015-04-05 00:50:27
【问题描述】:
我正在用 Go 编写一个搜索引擎,其中我对每个单词的相应结果都有一个单词的倒排索引。有一组单词字典,因此单词已经转换为 StemID,它是从 0 开始的整数。这允许我使用指针切片(即 sparse array)来映射每个 StemID到包含该查询结果的结构。例如。 var StemID_to_Index []*resultStruct。如果aardvark 是0,则指向aardvark 的resultStruct 的指针位于StemID_to_Index[0],如果当前未加载该单词的结果,则该指针将是nil。
服务器上没有足够的内存将所有这些存储在内存中,因此每个StemID 的结构将保存为单独的文件,并且可以将这些文件加载到StemID_to_Index 切片中。如果StemID_to_Index对于这个StemID当前是nil,那么结果没有缓存,需要加载,否则已经加载(缓存)了,所以可以直接使用。每次加载新结果时,都会检查内存使用情况,如果超过阈值,则丢弃 2/3 的加载结果(对于这些 StemID,StemID_to_Index 设置为 nil,并且强制进行垃圾收集。)
我的问题是并发性。我可以让多个线程同时搜索而不会遇到不同线程同时尝试读取和写入同一个地方的问题的最快和最有效的方法是什么?我试图避免在所有内容上使用互斥锁,因为这会减慢每次访问尝试。
你认为我会在工作线程中从磁盘加载结果,然后使用通道将指向该结构的指针传递给“更新程序”线程,然后更新StemID_to_Index 中的nil 值吗?切片到加载结果的指针?这意味着两个线程永远不会尝试同时写入,但是如果另一个线程尝试从 StemID_to_Index 的确切索引中读取,而“更新程序”线程正在更新指针,会发生什么?对于当前正在加载的结果,是否为线程提供nil 指针并不重要,因为它只会被加载两次,虽然这会浪费资源,但它仍然会提供相同的结果,因为那是不太可能经常发生,这是可以原谅的。
另外,将要更新的指针发送到“updater”线程的工作线程如何知道“updater”线程何时完成了对切片中指针的更新?它应该只是休眠并继续检查,还是有一种简单的方法让更新程序将消息发送回推送到频道的特定线程?
更新
我制作了一个小测试脚本,看看如果在修改指针的同时尝试访问指针会发生什么......它似乎总是可以的。没有错误。我错过了什么吗?
package main
import (
"fmt"
"sync"
)
type tester struct {
a uint
}
var things *tester
func updater() {
var a uint
for {
what := new(tester)
what.a = a
things = what
a++
}
}
func test() {
var t *tester
for {
t = things
if t != nil {
if t.a < 0 {
fmt.Println(`Error1`)
}
} else {
fmt.Println(`Error2`)
}
}
}
func main() {
var wg sync.WaitGroup
things = new(tester)
go test()
go test()
go test()
go test()
go test()
go test()
go updater()
go test()
go test()
go test()
go test()
go test()
wg.Add(1)
wg.Wait()
}
更新 2
更进一步,即使我同时从多个线程读取和写入同一个变量......它没有区别,仍然没有错误:
从上面:
func test() {
var a uint
var t *tester
for {
t = things
if t != nil {
if t.a < 0 {
fmt.Println(`Error1`)
}
} else {
fmt.Println(`Error2`)
}
what := new(tester)
what.a = a
things = what
a++
}
}
这意味着我根本不必担心并发性......再次:我在这里遗漏了什么吗?
【问题讨论】:
-
There are no benign data races!使用竞赛检测器运行您的最后一个示例。仅仅因为您无法引发错误或观察到未定义的行为,并不意味着它不会发生。你要么有比赛,要么没有。
-
感谢您的链接,这很有趣!但问题是,可能只有两个结果,一个指向结构的指针,它总是包含相同的数据……或者为零。就我而言,它是哪一个没有区别。因此,指针是否过时并不重要,只有在可能发生实际运行时错误的情况下,例如“nil 指针取消引用”,仅当指针实际损坏时才会发生。只要它始终是一个有效的指针(或 nil),我就没有问题,即使它已经过时了。
标签: multithreading go