Best equivalent for EnterCriticalSection on Mac OS X?
Asked Answered
A

1

2

What's the best equivalent? I didn't find any reasonable solution for such a simple function. Choices I'm aware of:

1) MPEnterCriticalRegion - this is unfortunately extremely ineffective, probably because despite it's name it enters kernel mode, so for repeating locks it just takes way too much time...

2) OSSpinLockLock - unusable, because apparently not recursive. If it would be recursive, it would be the correct equivalent.

3) pthread_mutex_lock - didn't try, but I don't expect much, because it will probably be just emulated using the critical region or another system resource.

Anticlastic answered 2/7, 2014 at 14:36 Comment(4)
You can easily generate a recursive wrapper around a non recursive lock, so I guess #2 if your summary is correct.Cocke
How? I was thinking about it, but at the end I'd have to start comparing thread IDs etc... seems more complex than I initially thought.Danyelldanyelle
Yeah you have to compare thread ids and store the owner TID if a thread acquires the lock, that's it. 10 lines extra code tops.Cocke
Well, I'm not sure it's that simple: 1) If the thread ID matches -> ok, nothing to go wrong. 2) If the thread ID doesn't match -> problem, because multiple threads may thing the same thing, hell the stored thread ID may even be in partly unconfirmed state, so that in extreme situation it may match even if it doesn't actually match. -> so I would need one more lock to protect the testing code. Now the question is if there won't be additional problems...Danyelldanyelle
C
0

Assuming you have a correctly working non-recursive lock, it's rather easy to get an efficient recursive lock (no idea about Mac APIs, so this is pseudo code):

class RecursiveLock {
public:
    void acquire() {
        auto tid = get_thread_id();
        if (owner == tid) { 
            lockCnt++;
        } else {
            AcquireLock(lock);
            owner = tid;
            lockCnt = 1;
        }
    }

    void release() {
        assert(owner == get_thread_id());
        lockCnt--;
        if (lockCnt == 0) {
            owner = 0;  // some illegal value for thread id
            ReleaseLock(lock);
        }
    }

private:
    int lockCnt;
    std::atomic<void*> owner;  
    void *lock;   // use whatever lock you like here
};

It's simple to reason:

  • if tid == owner it's guaranteed that we have already acquired the lock.
  • if tid != owner either someone else holds the lock or it's free, in both cases we try to acquire the lock and block until we get it. After we acquire the lock we set the owner to tid. So there's some time where we have acquired the lock but owner is still illegal. But no problem since the illegal tid won't compare equal to any real thread's tid either, so they'll go into the else branch as well and have to wait to get it.

Notice the std::atomic part - we do need ordering guarantees for the owner field to make this legal. If you don't have c++11 use some compiler intrinsic for that.

Cocke answered 3/7, 2014 at 10:17 Comment(4)
Aaaaah, you are right! I overcomplicated things! Thank you!Danyelldanyelle
@Vojtěch also if there's no illegal thread id value, you need an additional bool. In that case the bool has to be atomic (acquire release semantics to be exact) and set after the owner tid for acquire, not a big deal either.Cocke
why does owner need to be atomic if it is only modified a) by the owner thread and b) while the owner thread owns the lock, and, the only way it can be set to equal owner is by the owner itself after the lock is acquired?Aorta
@Aorta because we are reading and writing to the variable at the same time. And the reading threads do not acquire the lock before reading so the memory ordering guarantees of the lock are useless, so we need those in any case.Cocke

© 2022 - 2024 — McMap. All rights reserved.