Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 16 Aug 2000 12:19:51 -0400 (EDT)
From:      Peter Dufault <dufault@hda.com>
To:        hackers@FreeBSD.ORG
Subject:   Re: IPC, shared memory, syncronization AND threads...
Message-ID:  <200008161619.MAA43175@hda.hda.com>
In-Reply-To: <200008161223.IAA42181@hda.hda.com> from Peter Dufault at "Aug 16, 2000 08:23:49 am"

next in thread | previous in thread | raw e-mail | index | archive | help
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 majordomo@FreeBSD.org
with "unsubscribe freebsd-hackers" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200008161619.MAA43175>