Make empth_rwlock_t prefer writers

LWP and Windows implementations already did that.  Rewrite the
pthreads implementation.

The write-bias makes the stupid play_wrlock_wanted busy wait in
dispatch() unnecessary.  Remove it.
This commit is contained in:
Markus Armbruster 2009-04-26 23:48:05 +02:00
parent 079fb286c5
commit d052239a7a
3 changed files with 47 additions and 28 deletions

View file

@ -56,10 +56,10 @@
/* Abstract data types */ /* Abstract data types */
/* empth_t * represents a thread. */ /* A thread. */
typedef struct lwpProc empth_t; typedef struct lwpProc empth_t;
/* empth_rwlock_t * represents a read-write lock */ /* A read-write lock, perferring writers */
typedef struct lwp_rwlock empth_rwlock_t; typedef struct lwp_rwlock empth_rwlock_t;
/* Flags for empth_select(): whether to sleep on input or output */ /* Flags for empth_select(): whether to sleep on input or output */
@ -189,7 +189,7 @@ int empth_wait_for_signal(void);
/* /*
* Create a read-write lock. * Create a read-write lock.
* NAME is its name, it is used for debugging. * NAME is its name, it is used for debugging.
* Return the reade-write lock, or NULL on error. * Return the read-write lock, or NULL on error.
*/ */
empth_rwlock_t *empth_rwlock_create(char *name); empth_rwlock_t *empth_rwlock_create(char *name);
@ -209,9 +209,9 @@ void empth_rwlock_wrlock(empth_rwlock_t *rwlock);
/* /*
* Lock RWLOCK for reading. * Lock RWLOCK for reading.
* A read-write lock can be locked for reading only when it is not * A read-write lock can be locked for reading only when it is not
* locked for writing. If this is not the case, put the current * locked for writing, and no other thread is attempting to lock it
* thread to sleep until it is. Must not starve writers, and may * for writing. If this is not the case, put the current thread to
* sleep to avoid that. * sleep until it is.
*/ */
void empth_rwlock_rdlock(empth_rwlock_t *rwlock); void empth_rwlock_rdlock(empth_rwlock_t *rwlock);

View file

@ -62,8 +62,12 @@ struct empth_t {
}; };
struct empth_rwlock_t { struct empth_rwlock_t {
/* Can't use pthread_rwlock_t, because it needn't prefer writers */
char *name; char *name;
pthread_rwlock_t lock; int nread; /* #active readers */
int nwrite; /* total #writers (active and waiting) */
pthread_cond_t can_read;
pthread_cond_t can_write;
}; };
/* Thread-specific data key */ /* Thread-specific data key */
@ -397,19 +401,22 @@ empth_rwlock_create(char *name)
if (!rwlock) if (!rwlock)
return NULL; return NULL;
if (pthread_rwlock_init(&rwlock->lock, NULL) != 0) { if (pthread_cond_init(&rwlock->can_read, NULL) != 0
|| pthread_cond_init(&rwlock->can_write, NULL) != 0) {
free(rwlock); free(rwlock);
return NULL; return NULL;
} }
rwlock->name = strdup(name); rwlock->name = strdup(name);
rwlock->nread = rwlock->nwrite = 0;
return rwlock; return rwlock;
} }
void void
empth_rwlock_destroy(empth_rwlock_t *rwlock) empth_rwlock_destroy(empth_rwlock_t *rwlock)
{ {
pthread_rwlock_destroy(&rwlock->lock); pthread_cond_destroy(&rwlock->can_read);
pthread_cond_destroy(&rwlock->can_write);
free(rwlock->name); free(rwlock->name);
free(rwlock); free(rwlock);
} }
@ -417,23 +424,45 @@ empth_rwlock_destroy(empth_rwlock_t *rwlock)
void void
empth_rwlock_wrlock(empth_rwlock_t *rwlock) empth_rwlock_wrlock(empth_rwlock_t *rwlock)
{ {
pthread_mutex_unlock(&mtx_ctxsw); empth_status("wrlock %s %d %d",
pthread_rwlock_wrlock(&rwlock->lock); rwlock->name, rwlock->nread, rwlock->nwrite);
pthread_mutex_lock(&mtx_ctxsw); rwlock->nwrite++;
empth_restorectx(); while (rwlock->nread != 0 || rwlock->nwrite != 1) {
empth_status("waiting for wrlock %s", rwlock->name);
pthread_cond_wait(&rwlock->can_write, &mtx_ctxsw);
empth_status("got wrlock %s %d %d",
rwlock->name, rwlock->nread, rwlock->nwrite);
empth_restorectx();
}
} }
void void
empth_rwlock_rdlock(empth_rwlock_t *rwlock) empth_rwlock_rdlock(empth_rwlock_t *rwlock)
{ {
pthread_mutex_unlock(&mtx_ctxsw); empth_status("rdlock %s %d %d",
pthread_rwlock_rdlock(&rwlock->lock); rwlock->name, rwlock->nread, rwlock->nwrite);
pthread_mutex_lock(&mtx_ctxsw); while (rwlock->nwrite) {
empth_restorectx(); empth_status("waiting for rdlock %s", rwlock->name);
pthread_cond_wait(&rwlock->can_read, &mtx_ctxsw);
empth_status("got rdlock %s %d %d",
rwlock->name, rwlock->nread, rwlock->nwrite);
empth_restorectx();
}
rwlock->nread++;
} }
void void
empth_rwlock_unlock(empth_rwlock_t *rwlock) empth_rwlock_unlock(empth_rwlock_t *rwlock)
{ {
pthread_rwlock_unlock(&rwlock->lock); if (CANT_HAPPEN(!rwlock->nread && !rwlock->nwrite))
return;
if (rwlock->nread) { /* holding read lock */
if (!--rwlock->nread)
pthread_cond_signal(&rwlock->can_write);
} else {
rwlock->nwrite--;
pthread_cond_signal(&rwlock->can_write);
}
if (rwlock->nwrite == 0)
pthread_cond_broadcast(&rwlock->can_read);
} }

View file

@ -85,16 +85,6 @@ dispatch(char *buf, char *redir)
pr("Command not implemented\n"); pr("Command not implemented\n");
return 0; return 0;
} }
/*
* Rwlocks can hand out read locks while a write lock is wanted.
* Unfair to writers, but possible. This lets commands run with
* !player->aborted, which can then block on input and thus delay
* the update indefinitely. We could avoid that by setting
* player->aborted here, but spinning until the update is done is
* nicer to players.
*/
while (play_wrlock_wanted)
empth_yield();
player->command = command; player->command = command;
empth_rwlock_rdlock(play_lock); empth_rwlock_rdlock(play_lock);
if (redir) { if (redir) {