FAQ
I'm making a type in go which exposes access to some immutable resources.
Fetching the resource can be expensive, so I want to cache the fetched
data. The basic logic is, the object gets a request, if it's in the cache,
returns it, else it fetches, caches, and returns it. Very simple in the
single-threaded case. Now I want to make it concurrent. In particular, I
want fetching requests to not block non-fetching requests, and if two
clients ask for an uncached resource at the same time, we don't launch two
fetches for the same thing.

A solution I've thought of is to have a mutex-guarded map from resource
identifier to a channel which sends the resource over and over. Each
resource would then have a single goroutine which is either in the process
of fetching the resource or will cheaply send it back. Is this a reasonable
idea? Would creating a goroutine for every entry in the cache have
excessive overhead? If so, any other suggestions? This is a use case I've
run into a few times.

Hunter

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Search Discussions

  • Russ Cox at Feb 19, 2013 at 6:44 pm
    The usual pattern is to have a lock around the cache and then have each
    entry be a pointer to a struct that can itself be locked. The lookup is
    then:

    lock(cache)
    if cache is missing entry:
    cache[key] = new entry
    entry := cache[key]
    unlock(cache)
    lock(entry)
    if entry is not populated:
    entry.val = compute()
    val := entry.val
    unlock(entry)
    return val

    It is true that you can create a goroutine and channel for every entry
    instead of putting a mutex in the entry. But putting the mutex in will be
    faster at run time and also have less overhead (a channel already contains
    a mutex, so sizeof(mutex) < sizeof(channel) + sizeof(goroutine) for sure).

    Russ

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/groups/opt_out.
  • Hjfreyer at Feb 19, 2013 at 7:00 pm

    On Tuesday, February 19, 2013 1:44:41 PM UTC-5, Russ Cox wrote:

    The usual pattern is to have a lock around the cache and then have each
    entry be a pointer to a struct that can itself be locked. The lookup is
    then:

    lock(cache)
    if cache is missing entry:
    cache[key] = new entry
    entry := cache[key]
    unlock(cache)
    lock(entry)
    if entry is not populated:
    entry.val = compute()
    val := entry.val
    unlock(entry)
    return val

    It is true that you can create a goroutine and channel for every entry
    instead of putting a mutex in the entry. But putting the mutex in will be
    faster at run time and also have less overhead (a channel already contains
    a mutex, so sizeof(mutex) < sizeof(channel) + sizeof(goroutine) for sure).
    Aww, this solution isn't as fancy, but it indeed seems like the right one.
    Thanks for the advice.

    Quick general concurrency follow-up: it seems tempting to use a RWLock in
    this situation to do the check in read-only mode, then release and grab the
    write-lock if you actually do need to modify the state. Of course, this
    doubles the number of critical sections so it's a lot trickier to get
    right, but it seems like it'd have better performance if the cache usually
    doesn't change. Is it at all worth it to do it this way? (I'm assuming the
    answer is "it depends" but I'm wondering if you have a general opinion on
    the matter.)

    Hunter

    >

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/groups/opt_out.
  • Michael Jones at Feb 19, 2013 at 9:05 pm
    Don't forget, if you do that, to again check the status once you get into
    the critical section. It may have changed between your initial probe and
    the first code the return from acquiring the lock.

    is missing? { lock; is missing? update; unlock }
    On Tue, Feb 19, 2013 at 11:00 AM, wrote:
    On Tuesday, February 19, 2013 1:44:41 PM UTC-5, Russ Cox wrote:

    The usual pattern is to have a lock around the cache and then have each
    entry be a pointer to a struct that can itself be locked. The lookup is
    then:

    lock(cache)
    if cache is missing entry:
    cache[key] = new entry
    entry := cache[key]
    unlock(cache)
    lock(entry)
    if entry is not populated:
    entry.val = compute()
    val := entry.val
    unlock(entry)
    return val

    It is true that you can create a goroutine and channel for every entry
    instead of putting a mutex in the entry. But putting the mutex in will be
    faster at run time and also have less overhead (a channel already contains
    a mutex, so sizeof(mutex) < sizeof(channel) + sizeof(goroutine) for sure).
    Aww, this solution isn't as fancy, but it indeed seems like the right one.
    Thanks for the advice.

    Quick general concurrency follow-up: it seems tempting to use a RWLock in
    this situation to do the check in read-only mode, then release and grab the
    write-lock if you actually do need to modify the state. Of course, this
    doubles the number of critical sections so it's a lot trickier to get
    right, but it seems like it'd have better performance if the cache usually
    doesn't change. Is it at all worth it to do it this way? (I'm assuming the
    answer is "it depends" but I'm wondering if you have a general opinion on
    the matter.)

    Hunter
    --
    You received this message because you are subscribed to the Google Groups
    "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an
    email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/groups/opt_out.



    --
    Michael T. Jones | Chief Technology Advocate | mtj@google.com | +1
    650-335-5765

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/groups/opt_out.
  • Tyler at Feb 26, 2013 at 3:01 am
    This is a problem I've encountered also. I've written a cache manager to
    handle exactly what you describe. It's open sourced
    here: https://github.com/stretchrcom/hoard

    I hope it's useful for you! :)

    -Tyler
    On Tuesday, February 19, 2013 11:30:44 AM UTC-7, hjfr...@google.com wrote:

    I'm making a type in go which exposes access to some immutable resources.
    Fetching the resource can be expensive, so I want to cache the fetched
    data. The basic logic is, the object gets a request, if it's in the cache,
    returns it, else it fetches, caches, and returns it. Very simple in the
    single-threaded case. Now I want to make it concurrent. In particular, I
    want fetching requests to not block non-fetching requests, and if two
    clients ask for an uncached resource at the same time, we don't launch two
    fetches for the same thing.

    A solution I've thought of is to have a mutex-guarded map from resource
    identifier to a channel which sends the resource over and over. Each
    resource would then have a single goroutine which is either in the process
    of fetching the resource or will cheaply send it back. Is this a reasonable
    idea? Would creating a goroutine for every entry in the cache have
    excessive overhead? If so, any other suggestions? This is a use case I've
    run into a few times.

    Hunter
    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/groups/opt_out.

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupgolang-nuts @
categoriesgo
postedFeb 19, '13 at 6:36p
activeFeb 26, '13 at 3:01a
posts5
users4
websitegolang.org

People

Translate

site design / logo © 2022 Grokbase