Add support for pthread condition variables and add a test.
This commit is contained in:
Родитель
fb4b1e9fa1
Коммит
d7c47b1ffe
|
@ -0,0 +1,57 @@
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
#include <emscripten/threading.h>
|
||||||
|
#include <emscripten/emscripten.h>
|
||||||
|
#else
|
||||||
|
#include "futex.h"
|
||||||
|
#endif
|
||||||
|
#include "syscall.h"
|
||||||
|
|
||||||
|
static int do_wait(volatile int *addr, int val,
|
||||||
|
clockid_t clk, const struct timespec *at, int priv)
|
||||||
|
{
|
||||||
|
int r;
|
||||||
|
struct timespec to, *top=0;
|
||||||
|
|
||||||
|
if (at) {
|
||||||
|
if (at->tv_nsec >= 1000000000UL) return EINVAL;
|
||||||
|
if (clock_gettime(clk, &to)) return EINVAL;
|
||||||
|
to.tv_sec = at->tv_sec - to.tv_sec;
|
||||||
|
if ((to.tv_nsec = at->tv_nsec - to.tv_nsec) < 0) {
|
||||||
|
to.tv_sec--;
|
||||||
|
to.tv_nsec += 1000000000;
|
||||||
|
}
|
||||||
|
if (to.tv_sec < 0) return ETIMEDOUT;
|
||||||
|
top = &to;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
double timeout = top->tv_sec * 1000000000.0 + top->tv_nsec;
|
||||||
|
if (timeout > 1 * 1000000000.0) timeout = 1 * 1000000000.0;
|
||||||
|
EM_ASM_INT( { Module['printErr']('Wait ' + $0 + '.') }, timeout);
|
||||||
|
r = emscripten_futex_wait((void*)addr, val, timeout);
|
||||||
|
#else
|
||||||
|
r = -__syscall_cp(SYS_futex, addr, FUTEX_WAIT, val, top);
|
||||||
|
#endif
|
||||||
|
if (r == EINTR || r == EINVAL || r == ETIMEDOUT) return r;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int __timedwait(volatile int *addr, int val,
|
||||||
|
clockid_t clk, const struct timespec *at,
|
||||||
|
void (*cleanup)(void *), void *arg, int priv)
|
||||||
|
{
|
||||||
|
int r, cs;
|
||||||
|
|
||||||
|
if (!cleanup) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
|
||||||
|
pthread_cleanup_push(cleanup, arg);
|
||||||
|
|
||||||
|
r = do_wait(addr, val, clk, at, priv);
|
||||||
|
|
||||||
|
pthread_cleanup_pop(0);
|
||||||
|
if (!cleanup) pthread_setcancelstate(cs, 0);
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
#include "pthread_impl.h"
|
||||||
|
|
||||||
|
int pthread_cond_broadcast(pthread_cond_t *c)
|
||||||
|
{
|
||||||
|
pthread_mutex_t *m;
|
||||||
|
|
||||||
|
if (!c->_c_waiters) return 0;
|
||||||
|
|
||||||
|
a_inc(&c->_c_seq);
|
||||||
|
|
||||||
|
/* If cond var is process-shared, simply wake all waiters. */
|
||||||
|
if (c->_c_mutex == (void *)-1) {
|
||||||
|
__wake(&c->_c_seq, -1, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Block waiters from returning so we can use the mutex. */
|
||||||
|
while (a_swap(&c->_c_lock, 1))
|
||||||
|
__wait(&c->_c_lock, &c->_c_lockwait, 1, 1);
|
||||||
|
if (!c->_c_waiters)
|
||||||
|
goto out;
|
||||||
|
m = c->_c_mutex;
|
||||||
|
|
||||||
|
/* Move waiter count to the mutex */
|
||||||
|
a_fetch_add(&m->_m_waiters, c->_c_waiters2);
|
||||||
|
c->_c_waiters2 = 0;
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
emscripten_futex_requeue(&c->_c_seq, !m->_m_type || (m->_m_lock&INT_MAX)!=pthread_self()->tid, &m->_m_lock, INT_MAX);
|
||||||
|
#else
|
||||||
|
/* Perform the futex requeue, waking one waiter unless we know
|
||||||
|
* that the calling thread holds the mutex. */
|
||||||
|
__syscall(SYS_futex, &c->_c_seq, FUTEX_REQUEUE,
|
||||||
|
!m->_m_type || (m->_m_lock&INT_MAX)!=pthread_self()->tid,
|
||||||
|
INT_MAX, &m->_m_lock);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
out:
|
||||||
|
a_store(&c->_c_lock, 0);
|
||||||
|
if (c->_c_lockwait) __wake(&c->_c_lock, 1, 0);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
#include "pthread_impl.h"
|
||||||
|
|
||||||
|
int pthread_cond_destroy(pthread_cond_t *c)
|
||||||
|
{
|
||||||
|
int priv = c->_c_mutex != (void *)-1;
|
||||||
|
int cnt;
|
||||||
|
c->_c_destroy = 1;
|
||||||
|
if (c->_c_waiters)
|
||||||
|
__wake(&c->_c_seq, -1, priv);
|
||||||
|
while ((cnt = c->_c_waiters))
|
||||||
|
__wait(&c->_c_waiters, 0, cnt, priv);
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
#include "pthread_impl.h"
|
||||||
|
|
||||||
|
int pthread_cond_init(pthread_cond_t *restrict c, const pthread_condattr_t *restrict a)
|
||||||
|
{
|
||||||
|
*c = (pthread_cond_t){0};
|
||||||
|
if (a) {
|
||||||
|
c->_c_clock = a->__attr & 0x7fffffff;
|
||||||
|
if (a->__attr>>31) c->_c_mutex = (void *)-1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
#include "pthread_impl.h"
|
||||||
|
|
||||||
|
int pthread_cond_signal(pthread_cond_t *c)
|
||||||
|
{
|
||||||
|
if (!c->_c_waiters) return 0;
|
||||||
|
a_inc(&c->_c_seq);
|
||||||
|
if (c->_c_waiters) __wake(&c->_c_seq, 1, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
#include "pthread_impl.h"
|
||||||
|
|
||||||
|
struct cm {
|
||||||
|
pthread_cond_t *c;
|
||||||
|
pthread_mutex_t *m;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void unwait(pthread_cond_t *c, pthread_mutex_t *m)
|
||||||
|
{
|
||||||
|
/* Removing a waiter is non-trivial if we could be using requeue
|
||||||
|
* based broadcast signals, due to mutex access issues, etc. */
|
||||||
|
|
||||||
|
if (c->_c_mutex == (void *)-1) {
|
||||||
|
a_dec(&c->_c_waiters);
|
||||||
|
if (c->_c_destroy) __wake(&c->_c_waiters, 1, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (a_swap(&c->_c_lock, 1))
|
||||||
|
__wait(&c->_c_lock, &c->_c_lockwait, 1, 1);
|
||||||
|
|
||||||
|
if (c->_c_waiters2) c->_c_waiters2--;
|
||||||
|
else a_dec(&m->_m_waiters);
|
||||||
|
|
||||||
|
a_store(&c->_c_lock, 0);
|
||||||
|
if (c->_c_lockwait) __wake(&c->_c_lock, 1, 1);
|
||||||
|
|
||||||
|
a_dec(&c->_c_waiters);
|
||||||
|
if (c->_c_destroy) __wake(&c->_c_waiters, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cleanup(void *p)
|
||||||
|
{
|
||||||
|
struct cm *cm = p;
|
||||||
|
unwait(cm->c, cm->m);
|
||||||
|
pthread_mutex_lock(cm->m);
|
||||||
|
}
|
||||||
|
|
||||||
|
int pthread_cond_timedwait(pthread_cond_t *restrict c, pthread_mutex_t *restrict m, const struct timespec *restrict ts)
|
||||||
|
{
|
||||||
|
struct cm cm = { .c=c, .m=m };
|
||||||
|
int r, e=0, seq;
|
||||||
|
|
||||||
|
if (m->_m_type && (m->_m_lock&INT_MAX) != pthread_self()->tid)
|
||||||
|
return EPERM;
|
||||||
|
|
||||||
|
if (ts && ts->tv_nsec >= 1000000000UL)
|
||||||
|
return EINVAL;
|
||||||
|
|
||||||
|
pthread_testcancel();
|
||||||
|
|
||||||
|
a_inc(&c->_c_waiters);
|
||||||
|
|
||||||
|
if (c->_c_mutex != (void *)-1) {
|
||||||
|
c->_c_mutex = m;
|
||||||
|
while (a_swap(&c->_c_lock, 1))
|
||||||
|
__wait(&c->_c_lock, &c->_c_lockwait, 1, 1);
|
||||||
|
c->_c_waiters2++;
|
||||||
|
a_store(&c->_c_lock, 0);
|
||||||
|
if (c->_c_lockwait) __wake(&c->_c_lock, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
seq = c->_c_seq;
|
||||||
|
|
||||||
|
pthread_mutex_unlock(m);
|
||||||
|
|
||||||
|
do e = __timedwait(&c->_c_seq, seq, c->_c_clock, ts, cleanup, &cm, 0);
|
||||||
|
while (c->_c_seq == seq && (!e || e==EINTR));
|
||||||
|
if (e == EINTR) e = 0;
|
||||||
|
|
||||||
|
unwait(c, m);
|
||||||
|
|
||||||
|
if ((r=pthread_mutex_lock(m))) return r;
|
||||||
|
|
||||||
|
return e;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include "pthread_impl.h"
|
||||||
|
|
||||||
|
int pthread_cond_wait(pthread_cond_t *restrict c, pthread_mutex_t *restrict m)
|
||||||
|
{
|
||||||
|
return pthread_cond_timedwait(c, m, 0);
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <emscripten/emscripten.h>
|
||||||
|
|
||||||
|
#define NUM_THREADS 3
|
||||||
|
#define TCOUNT 10
|
||||||
|
#define COUNT_LIMIT 12
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
int thread_ids[3] = {0,1,2};
|
||||||
|
pthread_mutex_t count_mutex;
|
||||||
|
pthread_cond_t count_threshold_cv;
|
||||||
|
|
||||||
|
void *inc_count(void *t)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
long my_id = (long)t;
|
||||||
|
|
||||||
|
for (i=0; i<TCOUNT; i++) {
|
||||||
|
pthread_mutex_lock(&count_mutex);
|
||||||
|
count++;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check the value of count and signal waiting thread when condition is
|
||||||
|
reached. Note that this occurs while mutex is locked.
|
||||||
|
*/
|
||||||
|
if (count == COUNT_LIMIT) {
|
||||||
|
pthread_cond_signal(&count_threshold_cv);
|
||||||
|
// printf("inc_count(): thread %ld, count = %d Threshold reached.\n",
|
||||||
|
// my_id, count);
|
||||||
|
EM_ASM_INT( { Module['print']('inc_count(): thread ' + $0 + ', count = ' + $1 + ', Threshold reached.'); }, my_id, count);
|
||||||
|
}
|
||||||
|
// printf("inc_count(): thread %ld, count = %d, unlocking mutex\n",
|
||||||
|
// my_id, count);
|
||||||
|
EM_ASM_INT( { Module['print']('inc_count(): thread ' + $0 + ', count = ' + $1 + ', unlocking mutex.'); }, my_id, count);
|
||||||
|
pthread_mutex_unlock(&count_mutex);
|
||||||
|
|
||||||
|
/* Do some "work" so threads can alternate on mutex lock */
|
||||||
|
//sleep(1);
|
||||||
|
}
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *watch_count(void *t)
|
||||||
|
{
|
||||||
|
long my_id = (long)t;
|
||||||
|
|
||||||
|
// printf("Starting watch_count(): thread %ld\n", my_id);
|
||||||
|
EM_ASM_INT( { Module['print']('Starting watch_count(): thread ' + $0); }, my_id);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Lock mutex and wait for signal. Note that the pthread_cond_wait
|
||||||
|
routine will automatically and atomically unlock mutex while it waits.
|
||||||
|
Also, note that if COUNT_LIMIT is reached before this routine is run by
|
||||||
|
the waiting thread, the loop will be skipped to prevent pthread_cond_wait
|
||||||
|
from never returning.
|
||||||
|
*/
|
||||||
|
pthread_mutex_lock(&count_mutex);
|
||||||
|
while (count<COUNT_LIMIT) {
|
||||||
|
pthread_cond_wait(&count_threshold_cv, &count_mutex);
|
||||||
|
// printf("watch_count(): thread %ld Condition signal received.\n", my_id);
|
||||||
|
EM_ASM_INT( { Module['print']('watch_count(): thread ' + $0 + ' Condition signal received.'); }, my_id);
|
||||||
|
count += 125;
|
||||||
|
// printf("watch_count(): thread %ld count now = %d.\n", my_id, count);
|
||||||
|
EM_ASM_INT( { Module['print']('watch_count(): thread ' + $0 + ', count now = ' + $1); }, my_id, count);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&count_mutex);
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int i, rc;
|
||||||
|
long t1=1, t2=2, t3=3;
|
||||||
|
pthread_t threads[3];
|
||||||
|
pthread_attr_t attr;
|
||||||
|
|
||||||
|
/* Initialize mutex and condition variable objects */
|
||||||
|
pthread_mutex_init(&count_mutex, NULL);
|
||||||
|
pthread_cond_init (&count_threshold_cv, NULL);
|
||||||
|
|
||||||
|
/* For portability, explicitly create threads in a joinable state */
|
||||||
|
pthread_attr_init(&attr);
|
||||||
|
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
|
||||||
|
pthread_create(&threads[0], &attr, watch_count, (void *)t1);
|
||||||
|
pthread_create(&threads[1], &attr, inc_count, (void *)t2);
|
||||||
|
pthread_create(&threads[2], &attr, inc_count, (void *)t3);
|
||||||
|
|
||||||
|
/* Wait for all threads to complete */
|
||||||
|
for (i=0; i<NUM_THREADS; i++) {
|
||||||
|
pthread_join(threads[i], NULL);
|
||||||
|
}
|
||||||
|
printf ("Main(): Waited on %d threads. Done.\n", NUM_THREADS);
|
||||||
|
|
||||||
|
/* Clean up and exit */
|
||||||
|
pthread_attr_destroy(&attr);
|
||||||
|
pthread_mutex_destroy(&count_mutex);
|
||||||
|
pthread_cond_destroy(&count_threshold_cv);
|
||||||
|
#ifdef REPORT_RESULT
|
||||||
|
int result = 0;
|
||||||
|
REPORT_RESULT();
|
||||||
|
#endif
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
|
@ -2574,6 +2574,10 @@ window.close = function() {
|
||||||
for arg in [[], ['-DUSE_C_VOLATILE']]:
|
for arg in [[], ['-DUSE_C_VOLATILE']]:
|
||||||
self.btest(path_from_root('tests', 'pthread', 'test_pthread_volatile.cpp'), expected='1', args=['-lpthread'] + arg)
|
self.btest(path_from_root('tests', 'pthread', 'test_pthread_volatile.cpp'), expected='1', args=['-lpthread'] + arg)
|
||||||
|
|
||||||
# Test that basic thread creation works.
|
# Test thread-specific data (TLS).
|
||||||
def test_pthread_thread_local_storage(self):
|
def test_pthread_thread_local_storage(self):
|
||||||
self.btest(path_from_root('tests', 'pthread', 'test_pthread_thread_local_storage.cpp'), expected='0', args=['-lpthread'])
|
self.btest(path_from_root('tests', 'pthread', 'test_pthread_thread_local_storage.cpp'), expected='0', args=['-lpthread'])
|
||||||
|
|
||||||
|
# Test the pthread condition variable creation and waiting.
|
||||||
|
def test_pthread_condition_variable(self):
|
||||||
|
self.btest(path_from_root('tests', 'pthread', 'test_pthread_condition_variable.cpp'), expected='0', args=['-lpthread'])
|
||||||
|
|
Загрузка…
Ссылка в новой задаче