带CRUD操作的RW锁

I'm not sure if I'm approaching this right, the idea is to have concurrency while reading but lock only when it's writing or updating or deleting.

type Data struct {
    sync.RWMutex
    fooMap map[string]Foo
}

func (d *Data) AddFoo(foo Foo) {
    d.Lock()
    defer d.Unlock()

    d.fooMap[foo.getName()] = foo
}

func (d *Data) GetFoo(name string) Foo {
    return d.fooMap[name]
}

func (d *Data) RemoveFoo(name string) Foo {
    d.Lock()
    defer d.Unlock()

    var foo = self.fooMap[name]
    if foo != nil {
        delete(d.fooMap, name)
    }
    return foo
}

func (d *Data) HasFoo(name string) bool {
    return d.fooMap[name] != nil
}

Looking for an efficient approach where I don't block when I'm reading from the dictionary but block only in AddFoo and RemoveFoo methods.

Edit The context is slightly different, the code will not be executed by goroutines but will be used on a server where I've tons of concurrent requests going on.

Edit 2 In case of two maps

type Data struct {
    sync.RWMutex
    BarMutex sync.RWMutex{}
    fooMap map[string]Foo
    barMap map[string]Bar
}

// for Foo
func (d *Data) AddFoo(foo Foo) {
    d.Lock()
    defer d.Unlock()

    d.fooMap[foo.getName()] = foo
}

func (d *Data) GetFoo(name string) Foo {
    return d.fooMap[name]
}

// for Bar
func (d *Data) AddBar(bar Bar) {
    d.BarMutex.Lock()
    defer d.BarMutex.Unlock()

    d.barMap[bar.getName()] = bar
}

func (d *Data) GetBar(name string) Bar {
    d.BarMutex.RLock()
    defer d.BarMutex.RUnlock();
    return d.barMap[name]
}

Adding some more methods.

the code will not be executed by goroutines but will be used on a server where I've tons of concurrent requests

If they're concurrent, they're in goroutines - for example, if you're using the net/http server, every request is in its own goroutine. There are no other concurrency mechanisms, so if you've got concurrency, you're using goroutines, even if your code doesn't explicitly start them.

You can't "block only in AddFoo and RemoveFoo methods"; that's not how synchronization works. If you share data, you will have to acquire a lock for reads, and you will block on some reads if they happen concurrently to writes. If you don't acquire a lock for reads, you have a race condition.

func (d *Data) GetFoo(name string) Foo {
    d.RLock()
    d.RUnlock()
    return d.fooMap[name]
}

The alternative would be to share memory by communicating. This is a more complex refactor and there are many ways of doing it depending on your specific needs; it's not something I could offer a solution for based on a "Foo" type generic example.