2

I am writing an Objective-C class that I want to be thread safe. To do this I am using pthreads and a pthread_rwlock (using @synchronized is overkill and I want to learn a bit more about pthreads). The lock is inited in the objects designated init method and destroyed in dealloc. I have three methods for manipulating the lock; readLock, writeLock, unlock. These three methods simply invoke the related pthread functions and currently nothing else.

Here are two of the objects methods, both of which require a writeLock:

-(void)addValue:(const void *)buffer
{
    [self writeLock];

    NSUInteger lastIndex = self.lastIndex;
    [self setValue:buffer atIndex:(lastIndex == NSNotFound) ? 0 : lastIndex+1];

    [self unlock];
}


-(void)setValue:(const void *)buffer atIndex:(NSUInteger)index
{
    [self writeLock];
    //do work here
    [self unlock];
}

Invoking setAddValue: will first obtain a write lock and then invoke setValue:atIndex: which will also attempt to obtain a write lock. The documentation states that the behaviour is undefined when this occurs. Therefore, how do I check if a thread has a lock before attempting to obtain a lock?

(I could ensure that critical section make no invocation that trigger another lock request, but that would mean code repetition and I want to keep my code DRY).

Benedict Cohen
  • 11,912
  • 7
  • 55
  • 67

3 Answers3

2

Not entirely clear what kind of lock you're using. You indicate you're using pthreads, and read/write lock, so I'm concluding that you're using a pthread_rwlock.

If that's true, then you should be able to use pthread_rwlock_trywrlock on the lock. From the man page,

If successful, the pthread_rwlock_wrlock() and pthread_rwlock_trywrlock() functions will return zero. Otherwise, an error number will be returned to indicate the error.

And, one of the errors is:

[EDEADLK] The calling thread already owns the read/write lock (for reading or writing).

Therefore, I believe you should be able to call pthread_rwlock_trywrlock() and you will either be successful, it will return EBUSY if another thread has the lock, or you will get EDEADLK if the current thread has the lock.

Chris Cleeland
  • 4,760
  • 3
  • 26
  • 28
  • This information is slightly correct. The `EDEADLK` error is only specified for `pthread_rwlock_(rd|wr)lock`, not the trylock functions, which would return `EBUSY` in this case and not distinguish whether the lock is held by the calling thread or another thread. Also, a lock being held by the same thread is only detectable when it's a write lock. Since there can be an unlimited number of readers, the identity of the readers holding locks cannot be checked. However in OP's case it look like a double write lock is the issue that needs to be avoided, so it's probably ok. – R.. GitHub STOP HELPING ICE Apr 23 '11 at 21:25
  • Also, the `EDEADLK` error is only a "may", not a "shall", i.e. the implementation is not required to detect and report it – R.. GitHub STOP HELPING ICE Apr 23 '11 at 21:26
  • On further consideration I don't think your solution can work. I've posted an alternative as an answer. – R.. GitHub STOP HELPING ICE Apr 23 '11 at 21:32
  • R., I'm reading the man page on OS X, and it states the following: – Chris Cleeland Apr 24 '11 at 01:51
  • (grrr...hit return and ran out of editing time collecting references) R., I'm reading the man page on OS X (http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man3/pthread_rwlock_trywrlock.3.html), and it states the following: The pthread_rwlock_wrlock() and pthread_rwlock_trywrlock() functions may fail if: [EDEADLK] The calling thread already owns the read/write lock (for reading or writing). The OP would do well to empirically test the behavior. – Chris Cleeland Apr 24 '11 at 01:59
0

First, a critical section containing only one operation is useless. The point is to synchronize different things relative to each other. (You do effectively make the integer atomic, but that is probably not the full intent.)

Second, you already know you have the write lock inside the latter critical section, so there is no need to check that it exists or not. Simply do not attempt a read lock while writing.

The solution is probably to move the readLock and writeLock calls up into the calling functions, but without knowing more it's impossible to say.

(This will also likely reduce the performance cost of locking by reducing the number of total operations, as you will not be locking and then immediately unlocking. Probably you do not need to work directly at the pthreads level.)

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • First sentence is dangerously wrong. There is no such thing as a single atomic operation in C. If you were writing x86 assembly and using memory-operand instructions with the `lock` prefix you would be correct, but otherwise you need locking. – R.. GitHub STOP HELPING ICE Apr 23 '11 at 21:34
  • @R..: I'm saying that the effect of the locking he *is* doing is to make the integer atomic. Or at least, that he *was* doing. The question has changed since I wrote my answer (and, oddly, since he accepted another answer). – Potatoswatter Apr 23 '11 at 21:56
0

A portable program cannot rely on the implementation to tell the caller it already holds the write lock. Instead, you need to do something like this to wrap rwlocks with a recursive write lock:

int wrlock_wrap(pthread_rwlock_t *l, int *cnt)
{
    int r = *cnt ? 0 : pthread_rwlock_wrlocK(l);
    if (!r) ++*cnt;
    return r;
}

int wrunlock_wrap(pthread_rwlock_t *l, int *cnt)
{
    --*cnt;
    return pthread_rwlock_unlock(l);
}

You can keep the count beside the pthread_rwlock_t wherever it's stored, e.g. as a member of your struct/class/whatever.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • your note on portability is correct, but the OP is clearly on OS X and, with a choice of Objective C and Cocoa frameworks, has given up portability considerations. I originally considered an implementation similar to what you suggest (though sticking it directly inside the implementation the OP shows), but abandoned it after checking the OS X man page (see reference above). – Chris Cleeland Apr 24 '11 at 02:01