6

Go で小さなメモリ内データベースを書きたいと思います。読み取りおよび書き込み要求はチャネルを介して渡され、アクセスが適切に行われることを保証する db エンジンによって処理されます。

最初のアイデアは、RWMutexの動作を模倣することです。より慣用的な go スタイルを使用するだけです。

これは、私がやりたいことの小さなおもちゃの (ただし、かなり長い) 例です。

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}
            }
        }
    }
}

もちろん、この例は読み取り/書き込みの競合を示しています。

少し悪いことをしようとしているように感じます。それを回避するように設計された構造を使用してメモリを共有しています...この時点で、明らかな解決策は、2種類のリクエスト処理にRWMutexロックを追加することですが、おそらくゴルーチンとチャネルのみを使用した巧妙なソリューション。

4

2 に答える 2

7

なぜ RWMutex を使用しないのですか? 非常に効率的になるように最適化されており、概念的に単純です。Dbオブジェクトに1つ埋め込むだけです

type Db struct {
    sync.RWMutex
    // DB here
}

そして、あなたはそれを次のように呼び出すことができます

db := &Db{}
...
db.Lock()
// do RW operations
db.Unlock()
...
db.RLock()
// do Read operations
db.RUnlock()

チャネルを使用してパフォーマンスを向上させる方法がわかりません。ただし、ロックフリーの手法を使用するとパフォーマンスを向上させることができますが、RWMutex のバージョンを最初に実行することをお勧めします。

並行性に関するもう 1 つの問題は、fmt パッケージの stdout への書き込みがスレッド セーフでなく、最終的に出力が文字化けすることです。代わりにログ パッケージを試してください。ログ接頭辞なしで stdout に書き込むように設定すると、アトミックな書き込みが保証されます。

于 2012-12-28T01:16:49.597 に答える
0

もう1つの可能な解決策は、データベース自体をチャネルを介して渡し、データベースを保持している場合にのみデータベースを更新することです。これは、所有者のみが書き込みを行うことができ、メモリモデルがデータベースIIRCへの書き込みを保証するため、ロックする必要がないことを意味します。

于 2012-12-29T22:35:07.587 に答える