(3) Without such a class, how can I implement a read/write mutex where I can “promote” a read to a write? For example, a getter method that caches a value would need to grab the read lock first, then promote it to a write lock if it needs to update the cache. Do I use two Mutex objects, one for reading and one for writing, and always order them so I grab the read lock first, then the write?
Maybe you could explain a bit more about the problem you are trying to
solve, rather than the solution you would like to use.
> This has been discussed a few times on the mailing list. I don't
> remember the details, but I think it is prone to deadlocks.
Hm. I don’t see why it would deadlock, as long as you always make sure to grab the read lock before the write one.
It sounds like we're talking about two different things
a reentrant mutex (also called recursive mutex), and a RWMutex with the ability to promote an RLock to a write Lock. This has been discussed before, and I believe it was determined that while demotion is trivial (turning a write Lock into an RLock), a blocking mutex implementation (as opposed to some locking setups that return an error if the lock couldn't be acquired) has too many problems when trying to promote, since at most one promotion can be guaranteed to work (all others must fail, since the intent is that you want assurances that the state hasn't changed since you held the RLock).
No! It absolutely will not deadlock!
If both try calling promote, you will have the same behavior you would
when attempting to have two writers: one will (atomically) acquire the
lock, the other will block trying to acquire it. It's not that
complicated:
func (rw *RWMutex) Promote() {
rw.RUnlock();
rw.Lock();
}
Done. The atomicity doesn't have to apply to the whole block here: the
semantics of RUnlock and Lock aren't vulnerable to deadlocks due to
pre-emption or some kind of race. RUnlock is non-blocking, and Lock
must wait for 0 readers and 0 writers.
No deadlocks, and no reason to get confused.
--
Assuming you don't care if someone else holds the promotion first, then yes, this is okay. But in that case, you just need two RWMutexes (untested):
Problem with reentrant locks:
Data structure has two locks, Alpha and Bravo. To prevent deadlocks, it is specified that, when both are needed, Alpha must be locked before Bravo. Method X only needs Bravo, and so only locks Bravo. Method Y needs both, and so locks Alpha and then Bravo. Adding a call to Y within X could succeed if Alpha and Bravo were reentrant mutexes, despite this breaking the lock policy in a way which is very difficult to spot.
Problem with write lock promotion:When you RLock a RWMutex, the data is guaranteed not to change until you release the RLock. So, when two processes both hold and RLock, neither can promote to a WLock until the other has released the RLock. Otherwise you could get into a situation where the data you accessed while RLocked is not the same as the data once the write promotion has succeeded.
On Feb 24, 2013, at 8:21 PM, Kyle Lemons <kev...@google.com> wrote:Problem with reentrant locks:Data structure has two locks, Alpha and Bravo. To prevent deadlocks, it is specified that, when both are needed, Alpha must be locked before Bravo. Method X only needs Bravo, and so only locks Bravo. Method Y needs both, and so locks Alpha and then Bravo. Adding a call to Y within X could succeed if Alpha and Bravo were reentrant mutexes, despite this breaking the lock policy in a way which is very difficult to spot.Agreed, and lock ordering is part of why I treat mutexes of any sort very gingerly. It is impossible to make mutexes not be dangerous in one way or another. Nonetheless, recursive locks are something a great many programmers have experience with from other languages, that avoid some of the dangers that non-recursive locks are prone to (while creating others, yes.) I don’t think it’s a bad idea to let people pick a dangerous tool they’re familiar with, over another dangerous tool they’re not.(If I sound a little testy, it’s because I’m getting a sense that the lack of recursive locks might be intentional, another of the many religious design decisions made by Go, like the one true formatting style and lack of inheritance and so forth. I’m getting a bit weary of running into more of these.)
Problem with write lock promotion:When you RLock a RWMutex, the data is guaranteed not to change until you release the RLock. So, when two processes both hold and RLock, neither can promote to a WLock until the other has released the RLock. Otherwise you could get into a situation where the data you accessed while RLocked is not the same as the data once the write promotion has succeeded.This is true. But lock promotion is successfully used for things like database transactions. I am trying and failing right now to remember the algorithm used by SQLite, for example; I may need to go look it up.—Jens
--
I'm not sure that this is an intentional design decision, so much as a decision that follows from other design decisions (and I think it is worth making such a distinction). When goroutines have no form of identity and there is no concept of being 'inside an object', then it is difficult to see how you could begin to implement a recursive lock in a way that makes sense.
Certainly sync.Mutex is not recursive, and I think that's a reasonable decision. It's a low-level mechanism for providing mutual exclusion. Most people would expect the following code to fail, considering almost 50 years of experience and research with working with monitors and semaphores.m.Lock()m.Lock()
Remember that sync.Mutex is just provided in a package. There's nothing to stop other forms of mutual exclusion from being provided as packages for those who want something a little different.
And such a program will immediately deadlock whenever you turn a for
loop into a parallel for idiom, or fork a goroutine for a stupid
reason (for example to break an annoying stack split).
On 2/25/2013 11:02 AM, Devon H. O'Dell wrote:
> 2013/2/25 John Nagle <na...@animats.com>:
>> On 2/24/2013 9:39 PM, Jens Alfke wrote:
>>
>>> (If I sound a little testy, it’s because I’m getting a sense that the
>>> lack of recursive locks might be intentional, another of the many
>>> religious design decisions made by Go, like the one true formatting
>>> style and lack of inheritance and so forth. I’m getting a bit weary
>>> of running into more of these.)
>>
>> It seems to be intentional. See
>>
>> https://groups.google.com/forum/?fromgroups=#!searchin/golang-nuts/reentrant$20mutex/golang-nuts/XqW1qcuZgKg/tYq8frpidd8J
>>
>> It's amusing to see the Go people talking about "invariants".
>> Go isn't a tight enough language to have enforceable invariants.
>> For a modern one that is, see "Spec#"
>> (http://research.microsoft.com/en-us/projects/specsharp/)
>
> I don't understand this statement. Invariants are a concept, an idea.
Ah. I see the problem.
By an "enforceable invariant", I mean one that can potentially
be machine checked, and for which passing the check provides
a guarantee that the invariant is true when it is supposed to be.
This can be done either with an automated theorem proving system,
as in Spec# and the Microsoft Driver Verifier, or at run time, as
in Eiffel.
Before you can even start thinking about doing that, you
have to have clarity on exactly what code can access which data,
and which data is locked by which locks. Go, like C, doesn't
really address this for shared data.
Sometimes one sees invariants written as comments in code.
That at least gives guidance to maintenance programmers.
Invariants which are not written down anywhere are just
hand-waving.
On Feb 24, 2013, at 10:06 PM, Jim Whitehead II <jnwh...@gmail.com> wrote:I'm not sure that this is an intentional design decision, so much as a decision that follows from other design decisions (and I think it is worth making such a distinction). When goroutines have no form of identity and there is no concept of being 'inside an object', then it is difficult to see how you could begin to implement a recursive lock in a way that makes sense.Good point. Internally of course a goroutine must have an identity, but if that's not exposed to Go code then it wouldn’t be possible to implement recursive mutexes in Go. They’d have to be provided as primitives by the runtime.
Certainly sync.Mutex is not recursive, and I think that's a reasonable decision. It's a low-level mechanism for providing mutual exclusion. Most people would expect the following code to fail, considering almost 50 years of experience and research with working with monitors and semaphores.m.Lock()m.Lock()I know this, but my _practical_ experience with multithreading has been in languages that provide recursive mutexes as built-in constructs, so it was surprising to me to run into a mutex that deadlocks this way just like in my old college textbooks. As I keep saying, given the popularity of Java, a lot of programmers coming to Go are going to make the naive assumption that sync.Mutex is a direct replacement for “synchronized”.
Remember that sync.Mutex is just provided in a package. There's nothing to stop other forms of mutual exclusion from being provided as packages for those who want something a little different.Unless their implementation needs to keep track of goroutines, as a recursive mutex would. Then you’re stuck, unless you want to make the API awkward by requiring some kind of token to keep track of who has the lock.
On Mon, Feb 25, 2013 at 7:18 AM, Jens Alfke <je...@mooseyard.com> wrote:
On Feb 24, 2013, at 10:06 PM, Jim Whitehead II <jnwh...@gmail.com> wrote:I'm not sure that this is an intentional design decision, so much as a decision that follows from other design decisions (and I think it is worth making such a distinction). When goroutines have no form of identity and there is no concept of being 'inside an object', then it is difficult to see how you could begin to implement a recursive lock in a way that makes sense.Good point. Internally of course a goroutine must have an identity, but if that's not exposed to Go code then it wouldn’t be possible to implement recursive mutexes in Go. They’d have to be provided as primitives by the runtime.I think that's very unlikely, but I'm far from an authority. The anonymity of processes is one of the key differences between CSP and other concurrency models, such as the actor model. Exposing (even through the runtime) a way to distinguish between processes for this sort of purpose flies against that quite a bit and (I personally think) massively complicates the mental concurrency model.
Certainly sync.Mutex is not recursive, and I think that's a reasonable decision. It's a low-level mechanism for providing mutual exclusion. Most people would expect the following code to fail, considering almost 50 years of experience and research with working with monitors and semaphores.m.Lock()m.Lock()I know this, but my _practical_ experience with multithreading has been in languages that provide recursive mutexes as built-in constructs, so it was surprising to me to run into a mutex that deadlocks this way just like in my old college textbooks. As I keep saying, given the popularity of Java, a lot of programmers coming to Go are going to make the naive assumption that sync.Mutex is a direct replacement for “synchronized”.
Hopefully the documentation and examples can help make the difference more distinct. The 'synchronized' keyword does all forms of magic to provide a lot of features that are fairly important in concurrent programming in a 100% object-oriented environment. They are very different beasts, although they both fall under 'providing mutual exclusion of some sort'. It's a big problem if people think they are the same.It might be worth something in 'Effective Go' that talks about the differences between 'synchronized' and sync.Mutex. I don't think there's much of a need to distinguish from other languages, since C/C++ concurrency mechanisms seem mostly in-line with what Go provides.Remember that sync.Mutex is just provided in a package. There's nothing to stop other forms of mutual exclusion from being provided as packages for those who want something a little different.Unless their implementation needs to keep track of goroutines, as a recursive mutex would. Then you’re stuck, unless you want to make the API awkward by requiring some kind of token to keep track of who has the lock.Yes, it's awkward, and the current runtime package doesn't expose goroutine identity. But we can implement it using channels in order to see how it might work. That's how I think about concurrency, so it's a good place to start.I've been thinking about this problem since seeing the thread this morning (HOW it might be done, not whether or not it SHOULD be done), so here's one take on it.
On Mon, Feb 25, 2013 at 1:42 PM, Jim Whitehead II <jnwh...@gmail.com> wrote:
On Mon, Feb 25, 2013 at 7:18 AM, Jens Alfke <je...@mooseyard.com> wrote:
On Feb 24, 2013, at 10:06 PM, Jim Whitehead II <jnwh...@gmail.com> wrote:I'm not sure that this is an intentional design decision, so much as a decision that follows from other design decisions (and I think it is worth making such a distinction). When goroutines have no form of identity and there is no concept of being 'inside an object', then it is difficult to see how you could begin to implement a recursive lock in a way that makes sense.Good point. Internally of course a goroutine must have an identity, but if that's not exposed to Go code then it wouldn’t be possible to implement recursive mutexes in Go. They’d have to be provided as primitives by the runtime.I think that's very unlikely, but I'm far from an authority. The anonymity of processes is one of the key differences between CSP and other concurrency models, such as the actor model. Exposing (even through the runtime) a way to distinguish between processes for this sort of purpose flies against that quite a bit and (I personally think) massively complicates the mental concurrency model.The runtime does have a notion of a goroutine ID, but this is an implementation detail and won't be exposed at a higher level.
Certainly sync.Mutex is not recursive, and I think that's a reasonable decision. It's a low-level mechanism for providing mutual exclusion. Most people would expect the following code to fail, considering almost 50 years of experience and research with working with monitors and semaphores.m.Lock()m.Lock()I know this, but my _practical_ experience with multithreading has been in languages that provide recursive mutexes as built-in constructs, so it was surprising to me to run into a mutex that deadlocks this way just like in my old college textbooks. As I keep saying, given the popularity of Java, a lot of programmers coming to Go are going to make the naive assumption that sync.Mutex is a direct replacement for “synchronized”.
Hopefully the documentation and examples can help make the difference more distinct. The 'synchronized' keyword does all forms of magic to provide a lot of features that are fairly important in concurrent programming in a 100% object-oriented environment. They are very different beasts, although they both fall under 'providing mutual exclusion of some sort'. It's a big problem if people think they are the same.It might be worth something in 'Effective Go' that talks about the differences between 'synchronized' and sync.Mutex. I don't think there's much of a need to distinguish from other languages, since C/C++ concurrency mechanisms seem mostly in-line with what Go provides.Remember that sync.Mutex is just provided in a package. There's nothing to stop other forms of mutual exclusion from being provided as packages for those who want something a little different.Unless their implementation needs to keep track of goroutines, as a recursive mutex would. Then you’re stuck, unless you want to make the API awkward by requiring some kind of token to keep track of who has the lock.Yes, it's awkward, and the current runtime package doesn't expose goroutine identity. But we can implement it using channels in order to see how it might work. That's how I think about concurrency, so it's a good place to start.I've been thinking about this problem since seeing the thread this morning (HOW it might be done, not whether or not it SHOULD be done), so here's one take on it.I think there's a simpler solution. Instead of using some sort of goroutine ID, you can pass around a "token" which is essentially a proxy for goroutine ID, and this can also store the lock depth of the mutex and know when to return it. Straw man here:Again, not saying it should be done, only that it takes less time to implement it than to complain about the fact that it doesn't exist :).
--
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.