(loc_RunThread, empth_init, empth_threadMain, empth_yield)
(empth_select, empth_sleep, empth_wait_for_signal) (empth_sem_wait) [EMPTH_W32]: Add the ability to wait additional event in loc_RunThread() when acquiring the hThread mutex. (empth_rwlock_t, empth_rwlock_create, empth_rwlock_destroy) (empth_rwlock_wrlock, empth_rwlock_rdlock, empth_rwlock_unlock): New. (empth_rwlock_t, empth_rwlock_create, empth_rwlock_destroy) (empth_rwlock_wrlock, empth_rwlock_rdlock, empth_rwlock_unlock) [EMPTH_W32]: WIN32 implementation. (empth_rwlock_t, empth_rwlock_create, empth_rwlock_destroy) (empth_rwlock_wrlock, empth_rwlock_rdlock, empth_rwlock_unlock) (lwp_rwlock, lwp_rwlock_create, lwp_rwlock_destroy) (lwp_rwlock_wrlock, lwp_rwlock_rdlock, lwp_rwlock_unlock) [EMPTH_LWP]: LWP implementation. (empth_rwlock_t, empth_rwlock_create, empth_rwlock_destroy) (empth_rwlock_wrlock, empth_rwlock_rdlock, empth_rwlock_unlock) [EMPTH_POSIX]: pthread implementation.
This commit is contained in:
parent
8c65fbc2be
commit
71e0f98825
6 changed files with 409 additions and 14 deletions
|
@ -31,7 +31,7 @@
|
|||
* Sasha Mikheev
|
||||
* Doug Hay, 1998
|
||||
* Steve McClure, 1998
|
||||
* Markus Armbruster, 2005-2006
|
||||
* Markus Armbruster, 2005-2007
|
||||
*/
|
||||
|
||||
/*
|
||||
|
@ -74,6 +74,9 @@ typedef struct lwpProc empth_t;
|
|||
/* empth_sem_t * represents a semaphore */
|
||||
typedef struct lwpSem empth_sem_t;
|
||||
|
||||
/* empth_rwlock_t * represents a read-write lock */
|
||||
typedef struct lwp_rwlock empth_rwlock_t;
|
||||
|
||||
/* Flags for empth_select(): whether to sleep on input or output */
|
||||
#define EMPTH_FD_READ LWP_FD_READ
|
||||
#define EMPTH_FD_WRITE LWP_FD_WRITE
|
||||
|
@ -95,6 +98,7 @@ typedef struct lwpSem empth_sem_t;
|
|||
|
||||
typedef struct empth_t empth_t;
|
||||
typedef struct empth_sem_t empth_sem_t;
|
||||
typedef struct empth_rwlock_t empth_rwlock_t;
|
||||
|
||||
#endif /* EMPTH_POSIX */
|
||||
|
||||
|
@ -108,6 +112,7 @@ typedef struct empth_sem_t empth_sem_t;
|
|||
|
||||
typedef struct loc_Thread_t empth_t;
|
||||
typedef struct loc_Sem_t empth_sem_t;
|
||||
typedef struct loc_RWLock_t empth_rwlock_t;
|
||||
|
||||
void empth_request_shutdown(void);
|
||||
#endif /* EMPTH_W32 */
|
||||
|
@ -220,6 +225,42 @@ void empth_sem_signal(empth_sem_t *sem);
|
|||
*/
|
||||
void empth_sem_wait(empth_sem_t *sem);
|
||||
|
||||
/*
|
||||
* Create a read-write lock.
|
||||
* NAME is its name, it is used for debugging.
|
||||
* Return the reade-write lock, or NULL on error.
|
||||
*/
|
||||
empth_rwlock_t *empth_rwlock_create(char *name);
|
||||
|
||||
/*
|
||||
* Destroy RWLOCK.
|
||||
*/
|
||||
void empth_rwlock_destroy(empth_rwlock_t *rwlock);
|
||||
|
||||
/*
|
||||
* Lock RWLOCK for writing.
|
||||
* A read-write lock can be locked for writing only when it is
|
||||
* unlocked. If this is not the case, put the current thread to sleep
|
||||
* until it is.
|
||||
*/
|
||||
void empth_rwlock_wrlock(empth_rwlock_t *rwlock);
|
||||
|
||||
/*
|
||||
* Lock RWLOCK for reading.
|
||||
* 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
|
||||
* thread to sleep until it is. Must not starve writers, and may
|
||||
* sleep to avoid that.
|
||||
*/
|
||||
void empth_rwlock_rdlock(empth_rwlock_t *rwlock);
|
||||
|
||||
/*
|
||||
* Unlock read-write lock RWLOCK.
|
||||
* The current thread must hold RWLOCK.
|
||||
* Wake up threads that can now lock it.
|
||||
*/
|
||||
void empth_rwlock_unlock(empth_rwlock_t *rwlock);
|
||||
|
||||
|
||||
/*
|
||||
* Stuff for implementations, not for clients.
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
struct lwpProc;
|
||||
struct lwpSem;
|
||||
struct lwp_rwlock;
|
||||
|
||||
#define LWP_FD_READ 0x1
|
||||
#define LWP_FD_WRITE 0x2
|
||||
|
@ -57,6 +58,12 @@ struct lwpSem *lwpCreateSem(char *name, int count);
|
|||
void lwpSignal(struct lwpSem *);
|
||||
void lwpWait(struct lwpSem *);
|
||||
|
||||
struct lwp_rwlock *lwp_rwlock_create(char *);
|
||||
void lwp_rwlock_destroy(struct lwp_rwlock *);
|
||||
void lwp_rwlock_wrlock(struct lwp_rwlock *);
|
||||
void lwp_rwlock_rdlock(struct lwp_rwlock *);
|
||||
void lwp_rwlock_unlock(struct lwp_rwlock *);
|
||||
|
||||
extern struct lwpProc *LwpCurrent;
|
||||
|
||||
#endif
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
*
|
||||
* Known contributors to this file:
|
||||
* Sasha Mikheev
|
||||
* Markus Armbruster, 2006
|
||||
* Markus Armbruster, 2006-2007
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
@ -149,3 +149,33 @@ empth_sem_wait(empth_sem_t *sm)
|
|||
{
|
||||
lwpWait(sm);
|
||||
}
|
||||
|
||||
empth_rwlock_t *
|
||||
empth_rwlock_create(char *name)
|
||||
{
|
||||
return lwp_rwlock_create(name);
|
||||
}
|
||||
|
||||
void
|
||||
empth_rwlock_destroy(empth_rwlock_t *rwlock)
|
||||
{
|
||||
lwp_rwlock_destroy(rwlock);
|
||||
}
|
||||
|
||||
void
|
||||
empth_rwlock_wrlock(empth_rwlock_t *rwlock)
|
||||
{
|
||||
lwp_rwlock_wrlock(rwlock);
|
||||
}
|
||||
|
||||
void
|
||||
empth_rwlock_rdlock(empth_rwlock_t *rwlock)
|
||||
{
|
||||
lwp_rwlock_rdlock(rwlock);
|
||||
}
|
||||
|
||||
void
|
||||
empth_rwlock_unlock(empth_rwlock_t *rwlock)
|
||||
{
|
||||
lwp_rwlock_unlock(rwlock);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
* Known contributors to this file:
|
||||
* Doug Hay, 1998
|
||||
* Steve McClure, 1998
|
||||
* Ron Koenderink, 2004-2005
|
||||
* Ron Koenderink, 2004-2007
|
||||
*/
|
||||
|
||||
/*
|
||||
|
@ -108,6 +108,53 @@ struct loc_Sem_t {
|
|||
int count;
|
||||
};
|
||||
|
||||
/************************
|
||||
* loc_RWLock_t
|
||||
*
|
||||
* Invariants
|
||||
* must hold at function call, return, sleep
|
||||
* and resume from sleep.
|
||||
*
|
||||
* any state:
|
||||
* nwrite >= 0
|
||||
* nread >= 0
|
||||
|
||||
* if unlocked:
|
||||
* can_read set
|
||||
* can_write set
|
||||
* nwrite == 0
|
||||
* nread == 0
|
||||
*
|
||||
* if read-locked without writers contending:
|
||||
* can_read set
|
||||
* can_write clear
|
||||
* nwrite == 0
|
||||
* nread > 0
|
||||
*
|
||||
* if read-locked with writers contending:
|
||||
* can_read clear
|
||||
* can_write clear
|
||||
* nwrite > 0 #writers blocked
|
||||
* nread > 0
|
||||
*
|
||||
* if write-locked:
|
||||
* can_read clear
|
||||
* can_write clear
|
||||
* nwrite > 0 #writers blocked + 1
|
||||
* nread == 0
|
||||
*
|
||||
* To ensure consistency, state normally changes only while the
|
||||
* thread changing it holds hThreadMutex.
|
||||
*
|
||||
*/
|
||||
struct loc_RWLock_t {
|
||||
char name[17]; /* The thread name, passed in at create time. */
|
||||
HANDLE can_read; /* Manual event -- allows read locks */
|
||||
HANDLE can_write; /* Auto-reset event -- allows write locks */
|
||||
int nread; /* number of active readers */
|
||||
int nwrite; /* total number of writers (active and waiting) */
|
||||
};
|
||||
|
||||
/* This is the thread exclusion/non-premption mutex. */
|
||||
/* The running thread has this MUTEX, and all others are */
|
||||
/* either blocked on it, or waiting for some OS response. */
|
||||
|
@ -202,8 +249,10 @@ loc_FreeThreadInfo(empth_t *pThread)
|
|||
* info, and the thread owns the MUTEX sem.
|
||||
*/
|
||||
static void
|
||||
loc_RunThisThread(void)
|
||||
loc_RunThisThread(HANDLE hWaitObject)
|
||||
{
|
||||
HANDLE hWaitObjects[2];
|
||||
|
||||
empth_t *pThread = TlsGetValue(dwTLSIndex);
|
||||
|
||||
if (pThread->bKilled) {
|
||||
|
@ -214,8 +263,11 @@ loc_RunThisThread(void)
|
|||
}
|
||||
}
|
||||
|
||||
/* Get the MUTEX semaphore, wait forever. */
|
||||
WaitForSingleObject(hThreadMutex, INFINITE);
|
||||
hWaitObjects[0] = hThreadMutex;
|
||||
hWaitObjects[1] = hWaitObject;
|
||||
|
||||
WaitForMultipleObjects(hWaitObject ? 2 : 1, hWaitObjects,
|
||||
TRUE, INFINITE);
|
||||
|
||||
if (!pCurThread) {
|
||||
/* Set the globals to this thread. */
|
||||
|
@ -306,7 +358,7 @@ empth_threadMain(void *pvData)
|
|||
srand(now ^ (unsigned)pThread);
|
||||
|
||||
/* Switch to this thread context */
|
||||
loc_RunThisThread();
|
||||
loc_RunThisThread(NULL);
|
||||
|
||||
/* Run the thread. */
|
||||
if (pThread->pfnEntry)
|
||||
|
@ -375,7 +427,7 @@ empth_init(void **ctx_ptr, int flags)
|
|||
TlsSetValue(dwTLSIndex, pThread);
|
||||
|
||||
/* Make this the running thread. */
|
||||
loc_RunThisThread();
|
||||
loc_RunThisThread(NULL);
|
||||
|
||||
logerror("NT pthreads initialized");
|
||||
return 0;
|
||||
|
@ -481,7 +533,7 @@ void
|
|||
empth_yield(void)
|
||||
{
|
||||
loc_BlockThisThread();
|
||||
loc_RunThisThread();
|
||||
loc_RunThisThread(NULL);
|
||||
}
|
||||
|
||||
/************************
|
||||
|
@ -534,7 +586,7 @@ empth_select(int fd, int flags)
|
|||
|
||||
WSACloseEvent(hEventObject[0]);
|
||||
|
||||
loc_RunThisThread();
|
||||
loc_RunThisThread(NULL);
|
||||
}
|
||||
|
||||
/************************
|
||||
|
@ -570,7 +622,7 @@ empth_sleep(time_t until)
|
|||
|
||||
loc_debug("sleep done. Waiting to run.");
|
||||
|
||||
loc_RunThisThread();
|
||||
loc_RunThisThread(NULL);
|
||||
}
|
||||
|
||||
/************************
|
||||
|
@ -593,7 +645,7 @@ empth_wait_for_signal(void)
|
|||
/* Get the MUTEX semaphore, wait the number of MS */
|
||||
WaitForSingleObject(hShutdownEvent, INFINITE);
|
||||
|
||||
loc_RunThisThread();
|
||||
loc_RunThisThread(NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -670,5 +722,81 @@ empth_sem_wait(empth_sem_t *pSem)
|
|||
} else
|
||||
ReleaseMutex(pSem->hMutex);
|
||||
|
||||
loc_RunThisThread();
|
||||
loc_RunThisThread(NULL);
|
||||
}
|
||||
|
||||
empth_rwlock_t *
|
||||
empth_rwlock_create(char *name)
|
||||
{
|
||||
empth_rwlock_t *rwlock;
|
||||
|
||||
rwlock = malloc(sizeof(*rwlock));
|
||||
if (!rwlock)
|
||||
return NULL;
|
||||
|
||||
memset(rwlock, 0, sizeof(*rwlock));
|
||||
strncpy(rwlock->name, name, sizeof(rwlock->name) - 1);
|
||||
|
||||
if ((rwlock->can_read = CreateEvent(NULL, TRUE, TRUE, NULL)) == NULL) {
|
||||
logerror("rwlock_create: failed to create reader event %s at %s:%d",
|
||||
name, __FILE__, __LINE__);
|
||||
free(rwlock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((rwlock->can_write = CreateEvent(NULL, FALSE, TRUE, NULL)) == NULL) {
|
||||
logerror("rwlock_create: failed to create writer event %s at %s:%d",
|
||||
name, __FILE__, __LINE__);
|
||||
CloseHandle(rwlock->can_read);
|
||||
free(rwlock);
|
||||
return NULL;
|
||||
}
|
||||
return rwlock;
|
||||
}
|
||||
|
||||
void
|
||||
empth_rwlock_destroy(empth_rwlock_t *rwlock)
|
||||
{
|
||||
if (CANT_HAPPEN(rwlock->nread || rwlock->nwrite))
|
||||
return;
|
||||
CloseHandle(rwlock->can_read);
|
||||
CloseHandle(rwlock->can_write);
|
||||
free(rwlock);
|
||||
}
|
||||
|
||||
void
|
||||
empth_rwlock_wrlock(empth_rwlock_t *rwlock)
|
||||
{
|
||||
/* block any new readers */
|
||||
ResetEvent(rwlock->can_read);
|
||||
rwlock->nwrite++;
|
||||
loc_BlockThisThread();
|
||||
loc_RunThisThread(rwlock->can_write);
|
||||
CANT_HAPPEN(rwlock->nread != 0);
|
||||
}
|
||||
|
||||
void
|
||||
empth_rwlock_rdlock(empth_rwlock_t *rwlock)
|
||||
{
|
||||
loc_BlockThisThread();
|
||||
loc_RunThisThread(rwlock->can_read);
|
||||
ResetEvent(rwlock->can_write);
|
||||
rwlock->nread++;
|
||||
}
|
||||
|
||||
void
|
||||
empth_rwlock_unlock(empth_rwlock_t *rwlock)
|
||||
{
|
||||
if (CANT_HAPPEN(!rwlock->nread && !rwlock->nwrite))
|
||||
return;
|
||||
if (rwlock->nread) { /* holding read lock */
|
||||
rwlock->nread--;
|
||||
if (rwlock->nread == 0)
|
||||
SetEvent(rwlock->can_write);
|
||||
} else {
|
||||
rwlock->nwrite--;
|
||||
SetEvent(rwlock->can_write);
|
||||
}
|
||||
if (rwlock->nwrite == 0)
|
||||
SetEvent(rwlock->can_read);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,8 @@
|
|||
* Known contributors to this file:
|
||||
* Sasha Mikheev
|
||||
* Steve McClure, 1998
|
||||
* Markus Armbruster, 2005-2006
|
||||
* Markus Armbruster, 2005-2007
|
||||
* Ron Koenderink, 2007
|
||||
*/
|
||||
|
||||
/* Required for PTHREAD_STACK_MIN on some systems, e.g. Solaris: */
|
||||
|
@ -75,6 +76,11 @@ struct empth_sem_t {
|
|||
pthread_cond_t cnd_sem;
|
||||
};
|
||||
|
||||
struct empth_rwlock_t {
|
||||
char *name;
|
||||
pthread_rwlock_t lock;
|
||||
};
|
||||
|
||||
/* Thread-specific data key */
|
||||
static pthread_key_t ctx_key;
|
||||
|
||||
|
@ -455,3 +461,52 @@ empth_sem_wait(empth_sem_t *sm)
|
|||
} else
|
||||
pthread_mutex_unlock(&sm->mtx_update);
|
||||
}
|
||||
|
||||
empth_rwlock_t *
|
||||
empth_rwlock_create(char *name)
|
||||
{
|
||||
empth_rwlock_t *rwlock;
|
||||
|
||||
rwlock = malloc(sizeof(*rwlock));
|
||||
if (!rwlock)
|
||||
return NULL;
|
||||
|
||||
if (pthread_rwlock_init(&rwlock->lock, NULL) != 0) {
|
||||
free(rwlock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rwlock->name = strdup(name);
|
||||
return rwlock;
|
||||
}
|
||||
|
||||
void
|
||||
empth_rwlock_destroy(empth_rwlock_t *rwlock)
|
||||
{
|
||||
pthread_rwlock_destroy(&rwlock->lock);
|
||||
free(rwlock);
|
||||
}
|
||||
|
||||
void
|
||||
empth_rwlock_wrlock(empth_rwlock_t *rwlock)
|
||||
{
|
||||
pthread_mutex_unlock(&mtx_ctxsw);
|
||||
pthread_rwlock_wrlock(&rwlock->lock);
|
||||
pthread_mutex_lock(&mtx_ctxsw);
|
||||
empth_restorectx();
|
||||
}
|
||||
|
||||
void
|
||||
empth_rwlock_rdlock(empth_rwlock_t *rwlock)
|
||||
{
|
||||
pthread_mutex_unlock(&mtx_ctxsw);
|
||||
pthread_rwlock_rdlock(&rwlock->lock);
|
||||
pthread_mutex_lock(&mtx_ctxsw);
|
||||
empth_restorectx();
|
||||
}
|
||||
|
||||
void
|
||||
empth_rwlock_unlock(empth_rwlock_t *rwlock)
|
||||
{
|
||||
pthread_rwlock_unlock(&rwlock->lock);
|
||||
}
|
||||
|
|
134
src/lib/lwp/rwlock.c
Normal file
134
src/lib/lwp/rwlock.c
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Empire - A multi-player, client/server Internet based war game.
|
||||
* Copyright (C) 1994-2007, Dave Pare, Jeff Bailey, Thomas Ruschak,
|
||||
* Ken Stevens, Steve McClure
|
||||
* Copyright (C) 1991-3 Stephen Crane
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* See files README, COPYING and CREDITS in the root of the source
|
||||
* tree for related information and legal notices. It is expected
|
||||
* that future projects/authors will amend these files as needed.
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* rwlock.c: Read-write locks
|
||||
*
|
||||
* Known contributors to this file:
|
||||
* Ron Koenderink, 2007
|
||||
* Markus Armbruster, 2007
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "lwp.h"
|
||||
#include "lwpint.h"
|
||||
|
||||
struct lwp_rwlock {
|
||||
/*
|
||||
* Lock counter
|
||||
* 0: unlocked
|
||||
* -1: locked for writing
|
||||
* >0: locked for reading that many times
|
||||
*/
|
||||
int count;
|
||||
struct lwpQueue rq; /* read lock sleepers */
|
||||
struct lwpQueue wq; /* write lock sleepers */
|
||||
char *name;
|
||||
};
|
||||
|
||||
struct lwp_rwlock *
|
||||
lwp_rwlock_create(char *name)
|
||||
{
|
||||
struct lwp_rwlock *rwlock;
|
||||
|
||||
rwlock = malloc(sizeof(*rwlock));
|
||||
if (!rwlock)
|
||||
return NULL;
|
||||
|
||||
memset(rwlock, 0, sizeof(*rwlock));
|
||||
rwlock->name = strdup(name);
|
||||
return rwlock;
|
||||
}
|
||||
|
||||
void
|
||||
lwp_rwlock_destroy(struct lwp_rwlock *rwlock)
|
||||
{
|
||||
if (CANT_HAPPEN(rwlock->count))
|
||||
return;
|
||||
free(rwlock);
|
||||
}
|
||||
|
||||
void
|
||||
lwp_rwlock_wrlock(struct lwp_rwlock *rwlock)
|
||||
{
|
||||
if (rwlock->count) {
|
||||
lwpAddTail(&rwlock->wq, LwpCurrent);
|
||||
lwpStatus(LwpCurrent, "blocked to acquire rwlock %s for writing",
|
||||
rwlock->name);
|
||||
lwpReschedule();
|
||||
}
|
||||
CANT_HAPPEN(rwlock->count != 0);
|
||||
rwlock->count = -1;
|
||||
lwpStatus(LwpCurrent, "acquired rwlock %s for writing", rwlock->name);
|
||||
}
|
||||
|
||||
void
|
||||
lwp_rwlock_rdlock(struct lwp_rwlock *rwlock)
|
||||
{
|
||||
if (rwlock->count < 0 || rwlock->wq.head) {
|
||||
lwpStatus(LwpCurrent, "blocked to acquire rwlock %s for reading",
|
||||
rwlock->name);
|
||||
lwpAddTail(&rwlock->rq, LwpCurrent);
|
||||
lwpReschedule();
|
||||
}
|
||||
CANT_HAPPEN(rwlock->count < 0);
|
||||
rwlock->count++;
|
||||
lwpStatus(LwpCurrent, "acquired rwlock %s for reading", rwlock->name);
|
||||
}
|
||||
|
||||
void
|
||||
lwp_rwlock_unlock(struct lwp_rwlock *rwlock)
|
||||
{
|
||||
struct lwpProc *p;
|
||||
|
||||
lwpStatus(LwpCurrent, "unlocking rwlock %s", rwlock->name);
|
||||
if (CANT_HAPPEN(rwlock->count == 0))
|
||||
return;
|
||||
if (rwlock->count < 0)
|
||||
rwlock->count = 0;
|
||||
else
|
||||
rwlock->count--;
|
||||
|
||||
if (rwlock->count == 0 && rwlock->wq.head) {
|
||||
p = lwpGetFirst(&rwlock->wq);
|
||||
lwpStatus(p, "wake up next writer of rwlock %s", rwlock->name);
|
||||
} else if (rwlock->count >= 0 && rwlock->rq.head && !rwlock->wq.head) {
|
||||
p = lwpGetFirst(&rwlock->rq);
|
||||
lwpStatus(p, "wake up next reader of rwlock %s", rwlock->name);
|
||||
} else
|
||||
return;
|
||||
|
||||
lwpReady(p);
|
||||
if (LwpCurrent->pri < p->pri) {
|
||||
lwpStatus(LwpCurrent, "yielding to thread with higher priority");
|
||||
lwpYield();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue