Pertanyaan Pengecualian baca tulis dengan saluran


Saya ingin menulis database kecil di dalam memori di Go. Permintaan baca dan tulis akan dilewatkan melalui saluran dan diproses oleh mesin db yang akan memastikan akses dilakukan dengan benar.

Ide pertama adalah meniru perilaku RWMutex. Hanya itu akan menggunakan gaya go yang lebih idiomatis.

Berikut ini adalah contoh mainan kecil (meskipun agak panjang) tentang apa yang ingin saya lakukan.

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

Tentu saja, contoh ini menunjukkan konflik baca / tulis.

Saya merasa seperti saya sedang mencoba melakukan sesuatu yang agak jahat: berbagi memori menggunakan konstruksi yang dirancang untuk menghindarinya ... Pada titik ini, solusi yang jelas adalah dengan menambahkan kunci RWMutex di sekitar dua jenis penanganan permintaan, tetapi mungkin ada solusi cerdas hanya menggunakan goroutines dan saluran.


5
2017-12-27 07:22


asal


Jawaban:


Kenapa tidak menggunakan RWMutex saja? Ini telah dioptimalkan menjadi sangat efisien dan secara konseptual sederhana. Hanya menanamkan satu di objek Db Anda

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

dan Anda bisa menyebutnya seperti itu

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

Saya tidak tahu cara untuk mendapatkan kinerja yang lebih baik menggunakan saluran. Kamu bisa Namun, dapatkan kinerja yang lebih baik dengan teknik bebas-lock, tetapi saya sarankan Anda menjalankan versi RWMutex terlebih dahulu.

Masalah konkurensi lain adalah bahwa paket fmt menulis ke stdout tidak aman dan Anda akhirnya akan melihat output yang kacau. Coba paket log sebagai gantinya. Anda dapat mengaturnya untuk menulis untuk stdout tanpa awalan pencatatan dan ini akan memastikan penulisan atom.


6
2017-12-28 01:16



Solusi lain yang mungkin, adalah melewatkan database itu sendiri melalui saluran dan kemudian memperbaruinya hanya ketika Anda memegang database. Ini berarti Anda tidak memerlukan kunci karena hanya pemegang yang dapat menulisnya, dan model memori menjamin menulis ke database, IIRC.


0
2017-12-29 22:35