Walter is right about transitive readonly - here's the alternative

Steven Schveighoffer schveiguy at yahoo.com
Thu Sep 13 12:03:40 PDT 2007


"Janice Caron" wrote
> On 9/13/07, Steven Schveighoffer <schveiguy at yahoo.com> wrote:
>> Without remembering which threads have read locks, you can just use a 
>> simple
>> reference counter to tell if a object is read-locked.
>
> My C++ implementation used an atomic counter for read locks, an atomic
> counter for (pending) write locks, and an OS mutex.

Using atomic counters, how do you read the write counter, then increment the 
read counter atomically without using the mutex?  I'm assuming that's the 
benefit of having read/write locks.

>> When "upgrading" becomes possible, then you have to know whether the 
>> thread
>> you are in holds a read-lock or not.  This means you have to store 
>> (probably
>> in the mutex itself) a list of all threads that hold the read-lock.  Then
>> when you go to do the upgrade, it must check to see if the upgrading 
>> thread
>> is in the list.  This is at least a O(lg(n)) operation, whereas a 
>> reference
>> counter is a O(1).
>
> ...and it must do all that ATOMICALLY. Forget it. You'd have to mutex
> lock the mutex.
>
> Upgrading just isn't possible in any case - not even in /theory/, let
> alone in a given implementation.

My point is not to advocate upgrading.  I believe it is possible, even in 
practice, but I don't think I'll ever use it :)

>> and write locks could be reentrant (only
>> have to store one thread id, you could even use the same reference 
>> counter
>> as the read locks).
>
> My implementation wasn't re-entrant for write locks. I never found it
> necessary. That said, I know that it is possible to make re-entrant
> write locks, but probably not by the means that you suggest.

No idea how it is done, but it definitely works in C#.  I use it a lot.  For 
instance, if I have a shared object that has properties, the property access 
function locks the object every time I access the property.  However, 
outside the object, if I want to atomically read many properties, I lock the 
object first, then access the properties, which then re-lock the object. 
Works rather well.

>> So the only really "bad" situation is:
>>
>> readable(o);
>> writable(o);
>
> You cannot get a write lock if you already have a read lock on the
> same object. It is impossible. That is a deadlock.

I no longer care to argue this point :)

>> This really seems awfully scary.  This really could result in an easy
>> deadlock, where code that holds a read lock could call code that 
>> eventually
>> grabs a write-lock.
>
> The trick is not to lock things for longer than you actually need to
> access them.
>
> Even without fancy wrappers, that holds true. If you do mutexes
> manually, you are expected to release them as soon as you've finished
> with them.
>
>
>> I'm thinking now that the best thing is just to use read-write locks, 
>> unless
>> you really know what you are doing, and own all the code that accesses a
>> shared object.
>
> Ah, but that could lead to even more deadlocks. If a hundred threads
> all want to read a resource at the same time,  and we can guarantee
> that no one's writing to it at the time,
> why not let them?


The issue I see is if you pass the object to another function which has no 
idea whether you have it locked or not, so it might think it's ok to 
write-lock it.  It's something a new programmer could do easily by mistake. 
"oh, I want to call niftyFunction with my locked object, hm... it wants the 
original object, OK, here you go! (deadlocks)."

I'm not saying its a bad feature, or that it's not useful.  I'm just saying 
it's no less error prone than the current mutex system.  A user must use and 
understand the system properly to not get into trouble, just like he must 
use and understand the current read-write locking system.

>
>
>> I guess I'll cancel my vote for this as a language feature.  Better left 
>> as
>> a library feature.  Sorry Janice :(
>
> Hehehe.
>
> It cannot be a library feature, because it cannot be implemented in a
> library, because to implement it in a library, you'd need transitive
> const, or logical const.

I'm guessing that tango.core.sync.ReadWriteMutex successfully implements it 
using a library.  It'd probably be possible using templates to make wrappers 
as you have suggested.

-Steve 





More information about the Digitalmars-d mailing list