Here's the kind of thing I have in mind, wrapped around the pthreads
mutexes.  This replaces default pthread mutexes (those with no special
attributes) with possibly fast ones.  I haven't done any real timing but
I've verified that a program I have works and runs a lot faster with
these wrappers.  Obviously you have to use -include mutex.h and various
-D flags to try this out.

Header:

#ifndef _MUTEX_H_
#define _MUTEX_H_

#include <pthread.h>

struct _pthread_mutex;
typedef struct _pthread_mutex *_pthread_mutex_t;

int _pthread_mutex_init(_pthread_mutex_t *, const pthread_mutexattr_t *);
int _pthread_mutex_lock(_pthread_mutex_t *);
int _pthread_mutex_unlock(_pthread_mutex_t *);

#endif /* _MUTEX_H_ */

Wrappers:

#include <stdio.h>
#include <stdlib.h>

#include <pthread.h>

typedef unsigned long castype;  /* Data type you can CAS */
extern int cas(volatile castype *, const castype, const castype);

struct _pthread_mutex {
        castype lock;
        struct pthread_mutex *strong_lock;
        struct pthread_cond *done_unlocking;
        struct pthread_mutex *unlock_lock;
        int unlocking;
};

#include "mutex.h"

/*
 * _PTHREAD_MUTEX_INITIALIZER would have to be something like:
 */
#define _PTHREAD_MUTEX_INITIALIZER { \
        0,\
        PTHREAD_MUTEX_INITIALIZER,\
        PTHREAD_COND_INITIALIZER,\
        PTHREAD_MUTEX_INITIALIZER,\
        0}

/* cas: Compare and swap.  Return 1 if it succeeds, zero if it
 * doesn't.
 */
int
cas(volatile castype *ptr, const castype o, const castype n)
{
        volatile int result = 0;

        __asm   __volatile(
                    "cmpxchg%L3 %3,%1; jne 0f; inc%L0 %0; 0:"
            :       "=m"(result)
            :       "m"(*ptr), "a"(o), "r"(n)
        );

        return result;
}

static void
oops(const char *s)
{
        fprintf(stderr, "_pthread_mutex: %s.\n", s);
}

/* Here's the init.  If there are any non-standard attributes use a default.
 */
int
_pthread_mutex_init(_pthread_mutex_t *mp, const pthread_mutexattr_t *attr)
{
        struct _pthread_mutex *m = (struct _pthread_mutex *)calloc(1, sizeof(*m));

        if (attr) {
                m->lock = 4;
                return pthread_mutex_init(&m->strong_lock, attr);
        }

        *mp = m;

        return 0;
}

/* mutex_lock: 
 * First try to go from 0 to 1.  If that works, we have the lock.
 *
 * Then see if it is 4.  That means it is a non-default lock,
 * just call the standard locker.  These two steps could be published
 * in the header along with the structure to make things inlineable.
 *
 * Finally go into a loop, doing the first step again, then:
 *
 * If it fails, set it from 1 to 2 to let the unlock know someone
 * is waiting and then call the existing lock.
 * If it is already at 2 just call the existing lock.
 *
 * The only thing left is that it is being unlocked, that is, it must be
 * 3.  Wait for the unlocker to do its thing then repeat.
 */

int _pthread_mutex_lock(_pthread_mutex_t *mp)
{
        struct _pthread_mutex *m = *mp;

        if (cas(&m->lock, 0, 1))        /* Try for the fast lock. */
                return 0;

        if (m->lock == 4)
                return pthread_mutex_lock(&m->strong_lock);     /* Not default lock */

        while (1) {
                if (cas(&m->lock, 0, 1))        /* Try for the fast lock. */
                        break;

                if (cas(&m->lock, 1, 2) || cas(&m->lock, 2, 2))
                        return pthread_mutex_lock(&m->strong_lock);

                pthread_mutex_lock(&m->unlock_lock);

                /* It is being unlocked, which can take a long time.
                 * Wait for the unlocker 
                 */

                while (m->unlocking) {
                        pthread_cond_wait(&m->done_unlocking, &m->unlock_lock);
                }

                pthread_mutex_unlock(&m->unlock_lock);
        }

        return 0;
}

/* unlock: First try to do a fast unlock and also handle the special
 * attributes case.
 * Then we must be the unlocker (there should only be one), so set the
 * flag so people know we're unlocking.
 *
 * Do the unlock and then signal anyone waiting.
 */
int _pthread_mutex_unlock(_pthread_mutex_t *mp)
{
        struct _pthread_mutex *m = *mp;
        if (cas(&m->lock, 1, 0))        /* Try for the fast unlock */
                return 0;

        if (m->lock == 4)
                return pthread_mutex_unlock(&m->strong_lock);   /* Not default lock */

        /* It has to be 2 or there are multiple unlocks going on.
         */
        if (!cas(&m->lock, 2, 3))
                oops("multiple unlocks?");

        pthread_mutex_lock(&m->unlock_lock);
        m->unlocking = 1;
        pthread_mutex_unlock(&m->strong_lock);
        m->unlocking = 0;
        pthread_mutex_unlock(&m->unlock_lock);

        pthread_cond_signal(&m->done_unlocking);

        return 0;
}


To Unsubscribe: send mail to [EMAIL PROTECTED]
with "unsubscribe freebsd-hackers" in the body of the message

Reply via email to