Atomically downgrade write lock to read lock?

756 views
Skip to first unread message

Josh Bleecher Snyder

unread,
Sep 30, 2013, 5:35:33 PM9/30/13
to golang-nuts
Hi,

I've got a situation where I want to update a value and give some
other goroutines a guaranteed chance to read it before it changes
again.

What I'd like to do is create a sync.RWMutex, Lock it, do the write,
and then atomically Unlock+RLock the mutex. I'd notify the interested
goroutines, and after they have all had a chance to read the new value
(taking RLocks themselves), I'd RUnlock the mutex, enabling future
writes.

I don't see an obvious way to do this with the sync package as it
stands. Am I missing something? Is there a good workaround short of
juggling channels? (Or should I go file an issue?)

Thanks,
Josh

Ian Lance Taylor

unread,
Sep 30, 2013, 5:41:21 PM9/30/13
to Josh Bleecher Snyder, golang-nuts
How do you plan to tell interested goroutines that there is a new
value available? If you are going to use a channel to tell them about
the new value, why not just send them the new value? Then they don't
have to read it, and you don't have to worry about this locking
behaviour.

To answer your question, you aren't missing anything: the
implementation of RWMutex does not support atomically downgrading a
write lock to a read lock. I don't know how hard it would be to add
that facilty.

Ian

Josh Bleecher Snyder

unread,
Sep 30, 2013, 5:53:34 PM9/30/13
to Ian Lance Taylor, golang-nuts
>> I don't see an obvious way to do this with the sync package as it
>> stands. Am I missing something? Is there a good workaround short of
>> juggling channels? (Or should I go file an issue?)
>
> How do you plan to tell interested goroutines that there is a new
> value available?

Via a method call; interested objects implement an interface and
register themselves to be notified. This allows the concurrency to be
hidden from the API.


> If you are going to use a channel to tell them about
> the new value, why not just send them the new value? Then they don't
> have to read it, and you don't have to worry about this locking
> behaviour.

There's already a call in place to read the value, with most of this
locking behavior; I was hoping to simply reuse it.

Passing along the new value is an option, although there are different
types of values in play, and I was hoping to spare the clients the
bother of converting interface{} to the expected type (or of having a
multiplicity of methods, one for each type).


> To answer your question, you aren't missing anything: the
> implementation of RWMutex does not support atomically downgrading a
> write lock to a read lock. I don't know how hard it would be to add
> that facilty.

Okie dokie. I guess I'll start with passing along the new value for
now, and entertain myself later by looking at the RWMutex
implementation.

Thanks!

-josh

Sugu Sougoumarane

unread,
Sep 30, 2013, 7:31:42 PM9/30/13
to golan...@googlegroups.com, Ian Lance Taylor
Have you looked at sync.Cond? It seems like you just want to broadcast, which guarantees that all existing waiters will get the signal before you can obtain the lock again..

Josh Bleecher Snyder

unread,
Sep 30, 2013, 7:59:33 PM9/30/13
to Sugu Sougoumarane, golang-nuts, Ian Lance Taylor
> Have you looked at sync.Cond? It seems like you just want to broadcast,
> which guarantees that all existing waiters will get the signal before you
> can obtain the lock again..

Hmm...sorry for being slow, but can you spell it out a tiny bit for
me? What is the sequence of events/protections? It is not obvious to
me how to use sync.Cond to achieve what I want.

-josh

Sugu Sougoumarane

unread,
Sep 30, 2013, 8:54:22 PM9/30/13
to Josh Bleecher Snyder, golang-nuts, Ian Lance Taylor
Actually, never mind. I don't think Cond guarantees lock synchronization. I think the broadcaster may still be able to race ahead of the waiters and re-obtain a lock, especially with GOMAXPROCS>1.
You'll need to use explicit synchronization with locks or channels.

Kevin Gillette

unread,
Sep 30, 2013, 9:09:08 PM9/30/13
to golan...@googlegroups.com, Josh Bleecher Snyder, Ian Lance Taylor
On Monday, September 30, 2013 6:54:22 PM UTC-6, Sugu Sougoumarane wrote:
Actually, never mind. I don't think Cond guarantees lock synchronization. I think the broadcaster may still be able to race ahead of the waiters and re-obtain a lock, especially with GOMAXPROCS>1.
You'll need to use explicit synchronization with locks or channels.

Agreed. The intent is that the goroutines that get signaled goroutines will attempt to acquire a lock immediately after the Broadcast or Signal call, and thus no additional synchronization needs to be provided.

Atomically downgradable RWLock's have been discussed before: as I recall, the conclusion was to wait and see if there was a great demand for it, before giving it serious consideration. Part of the issue is also that, while it's theoretically possible to atomically downgrade (though the feasibility of doing so with the current implementation was not thoroughly explored), it's impossible to do an atomic upgrade in the general case.

Steven Blenkinsop

unread,
Sep 30, 2013, 9:39:01 PM9/30/13
to Kevin Gillette, golang-nuts, Josh Bleecher Snyder, Ian Lance Taylor
That is unless you wanted to track all the readers on their way into the Cond using a WaitGroup, but that seems like unnecessary synchronization (though it does make it easier to reason about). You'd still need a separate Mutex for writers and probably another WaitGroup to track the readers on their way out of the Cond as well.

The cond basically doesn't do much for you if you want to let the readers stagger in while other readers are already getting to read the value.


--
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...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Aaron Blohowiak

unread,
Sep 30, 2013, 10:25:04 PM9/30/13
to golan...@googlegroups.com
I think this would work: A is your existing RWMutex, B is a vanilla Mutex

WLock A
Lock B
Modify value
Unlock A
Tell readers, which take a read lock on A. When they have all read,
Unlock B.

This will atomically prevent writes while the reads are happening.

I don't know why you want this particular functionality and it smells kind of funny.

Josh Bleecher Snyder

unread,
Sep 30, 2013, 11:47:24 PM9/30/13
to Aaron Blohowiak, golan...@googlegroups.com
Lock A, Lock B, Unlock A, Unlock B sounds like asking for deadlock to me...but I'll think about it harder tomorrow.

Josh
Sent from my iPhone
--

Sugu Sougoumarane

unread,
Oct 1, 2013, 12:33:58 AM10/1/13
to Josh Bleecher Snyder, Aaron Blohowiak, golan...@googlegroups.com
You can probably use RWMutex to rely on the guarantee that you cannot obtain a write lock until all existing read lock waiters have been granted.
Sorry, it's very old code (so no comments, etc).

Create: block any new goroutines that want to read (achieved through a write lock)
Wait: wait for a broadcast (achieved through a read lock)
Broadcast: inform all waiters of a value change (achieved through a write unlock)

In my use case, I don't have to release the read lock because it's for one-time use. In your case, you'll have to release the read lock after reading your value.

In my case, I always create a new lock (result) and write lock immediately. In your case, you'll just have to re-obtain a write lock, which will not be granted until all existing readers have released their read locks.




--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/MmIDUzl8HA0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.

Kevin Gillette

unread,
Oct 1, 2013, 1:05:19 AM10/1/13
to golan...@googlegroups.com, Aaron Blohowiak
As long as all lock accessors have consistent ordering, this should be fine (it shouldn't matter between two locks if the lock/unlock sequence is in fifo or filo order).

In this case, nobody can hold B lock until after A is acquired, and presumably they'll only attempt to acquire by if they hold a write lock on A.


On Monday, September 30, 2013 9:47:24 PM UTC-6, Joshua Bleecher Snyder wrote:
Lock A, Lock B, Unlock A, Unlock B sounds like asking for deadlock to me...but I'll think about it harder tomorrow.

Josh
Sent from my iPhone


On Monday, September 30, 2013, Aaron Blohowiak wrote:
I think this would work: A is your existing RWMutex, B is a vanilla Mutex

WLock A
Lock B
Modify value
Unlock A
Tell readers, which take a read lock on A. When they have all read,
Unlock B.

This will atomically prevent writes while the reads are happening.

I don't know why you want this particular functionality and it smells kind of funny.

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

Dmitry Vyukov

unread,
Oct 1, 2013, 2:46:09 AM10/1/13
to Sugu Sougoumarane, Josh Bleecher Snyder, Aaron Blohowiak, golan...@googlegroups.com
On Tue, Oct 1, 2013 at 8:33 AM, Sugu Sougoumarane <sou...@google.com> wrote:
> You can probably use RWMutex to rely on the guarantee that you cannot obtain
> a write lock until all existing read lock waiters have been granted.
> I have something close here:

I would not rely on this guarantee. Moreover you can not know when a
goroutine has actually blocked on mutex.

Sugu Sougoumarane

unread,
Oct 1, 2013, 3:08:03 AM10/1/13
to Dmitry Vyukov, Josh Bleecher Snyder, Aaron Blohowiak, golan...@googlegroups.com
On Mon, Sep 30, 2013 at 11:46 PM, Dmitry Vyukov <dvy...@google.com> wrote:
On Tue, Oct 1, 2013 at 8:33 AM, Sugu Sougoumarane <sou...@google.com> wrote:
> You can probably use RWMutex to rely on the guarantee that you cannot obtain
> a write lock until all existing read lock waiters have been granted.
> I have something close here:

I would not rely on this guarantee. Moreover you can not know when a
goroutine has actually blocked on mutex.


I see. If there are hundreds of goroutines wanting to wait on that read lock, some may not even get scheduled. So, there's no guaranteed delivery.

Steven Blenkinsop

unread,
Oct 1, 2013, 4:11:22 AM10/1/13
to Sugu Sougoumarane, Dmitry Vyukov, Josh Bleecher Snyder, Aaron Blohowiak, golan...@googlegroups.com
(Was going to say untested)

On Tuesday, October 1, 2013, Steven Blenkinsop wrote:
Would something like this work? http://play.golang.org/p/882fssFAYZ
--

Steven Blenkinsop

unread,
Oct 1, 2013, 4:10:58 AM10/1/13
to Sugu Sougoumarane, Dmitry Vyukov, Josh Bleecher Snyder, Aaron Blohowiak, golan...@googlegroups.com
Would something like this work? http://play.golang.org/p/882fssFAYZ

On Tuesday, October 1, 2013, Sugu Sougoumarane wrote:
--

Kevin Gillette

unread,
Oct 1, 2013, 8:07:30 AM10/1/13
to golan...@googlegroups.com, Dmitry Vyukov, Josh Bleecher Snyder, Aaron Blohowiak
Don't you mean "no guarantee of timely delivery"? As I understand it, the semantics of Cond are such that callers of Wait will be woken if called before (in terms of Go "happens before" semantics) a Broadcast call on the same lock. Depending on what the application is using the lock for, at worst it means that a given goroutine may have access to the state it needs, or it may need to wait again (if the lock protects alternate states), or at best it means that the required event has occurred (if the lock protects or signals a progression of events).

Neither locks, Cond, nor channels should be used by goroutines to do something "right now" when some kind of synchronization occurs. Not only would you need an infinite GOMAXPROCS (and an infinite number of processors) to make that occur in the general theoretical case, but in practice would also need the OS and/or certain portions of the Go runtime to provide realtime scheduling guarantees. Generally speaking, a goroutine that can end up in an unrunnable state (when blocking on a channel, for example, though with the eventual addition of full scheduler preemption, this applies to never-unrunnable goroutines too) should account for the possibility that an indefinite amount of clock-time may pass between becoming runnable (the channel operation succeeds, the sleep duration has passed, the net event completes, etc) and actually getting scheduled to run. Just because you sleep for 1 millisecond doesn't mean you won't wake up one millennium later (if at all, though this last sentiment applies to any program, not just those written in Go).
Reply all
Reply to author
Forward
0 new messages