我想在Go中编写一个小的内存数据库。 读取和写入请求将通过通道传递并由db引擎处理,这将确保访问正确完成。
第一个想法是模仿RWMutex的行为。只有它会使用更惯用的风格。
这是一个小玩具(虽然,相当长)的例子,我想做什么。
package main
import (
"log"
"math/rand"
"time"
)
var source *rand.Rand
type ReqType int
const (
READ = iota
WRITE
)
type DbRequest struct {
Type int // request type
RespC chan *DbResponse // channel for request response
// content here
}
type DbResponse struct {
// response here
}
type Db struct {
// DB here
}
func randomWait() {
time.Sleep(time.Duration(source.Intn(1000)) * time.Millisecond)
}
func (d *Db) readsHandler(in <-chan *DbRequest) {
for r := range in {
id := source.Intn(4000000)
log.Println("read ", id, " starts")
randomWait()
log.Println("read ", id, " ends")
r.RespC <- &DbResponse{}
}
}
func (d *Db) writesHandler(r *DbRequest) *DbResponse {
id := source.Intn(4000000)
log.Println("write ", id, " starts")
randomWait()
log.Println("write ", id, " ends")
return &DbResponse{}
}
func (d *Db) Start(nReaders int) chan *DbRequest {
in := make(chan *DbRequest, 100)
reads := make(chan *DbRequest, nReaders)
// launch readers
for k := 0; k < nReaders; k++ {
go d.readsHandler(reads)
}
go func() {
for r := range in {
switch r.Type {
case READ:
reads <- r
case WRITE:
// here we should wait for all reads to
// be over (how ??)
r.RespC <- d.writesHandler(r)
// here writesHandler is blocking,
// this ensures that no additional
// read is added in the reads channel
// before the write is finished
}
}
}()
return in
}
func main() {
seed := time.Now().Unix()
source = rand.New(rand.NewSource(seed))
blackhole := make(chan *DbResponse, 100)
d := Db{}
rc := d.Start(4)
wc := time.After(3 * time.Second)
go func() {
for {
<-blackhole
}
}()
for {
select {
case <-wc:
return
default:
if source.Intn(2) == 0 {
rc <- &DbRequest{READ, blackhole}
} else {
rc <- &DbRequest{WRITE, blackhole}
}
}
}
}
当然,这个例子显示了读/写冲突。
我觉得我正在尝试做一些有点邪恶的事情:使用旨在避免它的构造共享内存...... 在这一点上,一个明显的解决方案是在两种类型的请求处理中添加RWMutex锁,但也许只有goroutines和channel有一个聪明的解决方案。
答案 0 :(得分:6)
为什么不使用RWMutex?它经过优化,非常高效,而且概念上很简单。只需在Db对象中嵌入一个
type Db struct {
sync.RWMutex
// DB here
}
你可以称之为
db := &Db{}
...
db.Lock()
// do RW operations
db.Unlock()
...
db.RLock()
// do Read operations
db.RUnlock()
我不知道使用频道获得更好性能的方法。但可以使用无锁技术获得更好的性能,但我建议先运行RWMutex版本。
另一个并发问题是fmt包写入stdout不是线程安全的,你最终会看到乱码输出。请尝试使用日志包。您可以将其设置为写入 没有记录前缀的stdout,它将确保原子写入。
答案 1 :(得分:0)
另一种可能的解决方案是通过通道传递数据库本身,然后仅在保存数据库时更新它。这意味着您不需要锁定它,因为只有持有者可以写入它,并且内存模型保证写入数据库IIRC。