From b87d9cb50e68da06210a49eedfd01356e127b011 Mon Sep 17 00:00:00 2001 From: David Young Date: Wed, 26 Feb 2020 20:38:57 -0600 Subject: Implement pthread_barrier(3) for Darwin using a counter, condition variable, and mutex. Untested. --- test/thread_id.c | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/test/thread_id.c b/test/thread_id.c index af36625..4637d9c 100644 --- a/test/thread_id.c +++ b/test/thread_id.c @@ -42,6 +42,129 @@ my_errx(int code, const char *fmt, ...) #if defined(H5_HAVE_THREADSAFE) && !defined(H5_HAVE_WIN_THREADS) +#if defined(H5_HAVE_DARWIN) + +typedef struct _pthread_barrierattr { + uint8_t unused; +} pthread_barrierattr_t; + +typedef struct _pthread_barrier { + uint32_t magic; + unsigned int count; + uint64_t nentered; + pthread_cond_t cv; + pthread_mutex_t mtx; +} pthread_barrier_t; + +int pthread_barrier_init(pthread_barrier_t *, const pthread_barrierattr_t *, + unsigned int); +int pthread_barrier_wait(pthread_barrier_t *); +int pthread_barrier_destroy(pthread_barrier_t *); + +static const uint32_t barrier_magic = 0xf00dd00f; + +int +pthread_barrier_init(pthread_barrier_t *barrier, + const pthread_barrierattr_t *attr, unsigned int count) +{ + int rc; + + if (count == 0) + return EINVAL; + + if (attr != NULL) + return EINVAL; + + memset(barrier, 0, sizeof(*barrier)); + + barrier->count = count; + + if ((rc = pthread_cond_init(&barrier->cv, NULL)) != 0) + return rc; + + if ((rc = pthread_mutex_init(&barrier->mtx, NULL)) != 0) { + (void)pthread_cond_destroy(&barrier->cv); + return rc; + } + + barrier->magic = barrier_magic; + + return 0; +} + +static void +barrier_lock(pthread_barrier_t *barrier) +{ + int rc; + + if ((rc = pthread_mutex_lock(&barrier->mtx)) != 0) { + my_errx(exit_failure, "%s: pthread_mutex_lock: %s", __func__, + strerror(rc)); + } +} + +static void +barrier_unlock(pthread_barrier_t *barrier) +{ + int rc; + + if ((rc = pthread_mutex_unlock(&barrier->mtx)) != 0) { + my_errx(exit_failure, "%s: pthread_mutex_unlock: %s", __func__, + strerror(rc)); + } +} + +int +pthread_barrier_destroy(pthread_barrier_t *barrier) +{ + int rc; + + barrier_lock(barrier); + if (barrier->magic != barrier_magic) + rc = EINVAL; + else if (barrier->count != 0) + rc = EBUSY; + else { + rc = 0; + barrier->magic = ~barrier->magic; + } + barrier_unlock(barrier); + + if (rc != 0) + return rc; + + (void)pthread_cond_destroy(&barrier->cv); + (void)pthread_mutex_destroy(&barrier->mtx); + + return 0; +} + +int +pthread_barrier_wait(pthread_barrier_t *barrier) +{ + int rc; + + if (barrier == NULL) + return EINVAL; + + barrier_lock(barrier); + if (barrier->magic != barrier_magic) { + rc = EINVAL; + goto out; + } + barrier->nentered++; + while (barrier->nentered % barrier->count != 0) { + if ((rc = pthread_cond_wait(&barrier->cv, &barrier->mtx)) != 0) + goto out; + } + rc = pthread_cond_broadcast(&barrier->cv); +out: + barrier_unlock(barrier); + return rc; +} + +#endif /* H5_HAVE_DARWIN */ + static void my_err(int, const char *, ...) H5_ATTR_FORMAT(printf, 2, 3); static void -- cgit v0.12 From 802bf2dc1eca3c3fd4f3f2cbc90c9c40c6d94607 Mon Sep 17 00:00:00 2001 From: David Young Date: Thu, 27 Feb 2020 10:16:49 -0600 Subject: s/exit_failure/EXIT_FAILURE/g --- test/thread_id.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/thread_id.c b/test/thread_id.c index 4637d9c..84676bf 100644 --- a/test/thread_id.c +++ b/test/thread_id.c @@ -98,7 +98,7 @@ barrier_lock(pthread_barrier_t *barrier) int rc; if ((rc = pthread_mutex_lock(&barrier->mtx)) != 0) { - my_errx(exit_failure, "%s: pthread_mutex_lock: %s", __func__, + my_errx(EXIT_FAILURE, "%s: pthread_mutex_lock: %s", __func__, strerror(rc)); } } @@ -109,7 +109,7 @@ barrier_unlock(pthread_barrier_t *barrier) int rc; if ((rc = pthread_mutex_unlock(&barrier->mtx)) != 0) { - my_errx(exit_failure, "%s: pthread_mutex_unlock: %s", __func__, + my_errx(EXIT_FAILURE, "%s: pthread_mutex_unlock: %s", __func__, strerror(rc)); } } -- cgit v0.12 From 11d22f3ea50d45796e110fe805a694692de91456 Mon Sep 17 00:00:00 2001 From: David Young Date: Thu, 27 Feb 2020 10:17:24 -0600 Subject: Test the right condition for the EBUSY return in pthread_barrier_destroy(). --- test/thread_id.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/thread_id.c b/test/thread_id.c index 84676bf..49e3710 100644 --- a/test/thread_id.c +++ b/test/thread_id.c @@ -122,7 +122,7 @@ pthread_barrier_destroy(pthread_barrier_t *barrier) barrier_lock(barrier); if (barrier->magic != barrier_magic) rc = EINVAL; - else if (barrier->count != 0) + else if (barrier->nentered % barrier->count != 0) rc = EBUSY; else { rc = 0; -- cgit v0.12 From 4be589813a8fc5e2c5313464b088d7d3c31a754d Mon Sep 17 00:00:00 2001 From: David Young Date: Thu, 27 Feb 2020 11:27:45 -0600 Subject: The first implementation seemed to allow for the possibility that a thread could block at the barrier, wake and exit the barrier, re-acquire the barrier lock and increase `nentered` before the other blocked threads woke and checked `nentered % count == 0`. Then the other blocked threads would check `nentered % count == 0` and, finding it false, go back to sleep in the barrier. This new implementation waits for a looser condition to obtain so that threads don't go back to sleep in the barrier. --- test/thread_id.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/thread_id.c b/test/thread_id.c index 49e3710..e3cf42e 100644 --- a/test/thread_id.c +++ b/test/thread_id.c @@ -143,6 +143,7 @@ int pthread_barrier_wait(pthread_barrier_t *barrier) { int rc; + uint64_t threshold; if (barrier == NULL) return EINVAL; @@ -152,8 +153,14 @@ pthread_barrier_wait(pthread_barrier_t *barrier) rc = EINVAL; goto out; } + /* Compute the release `threshold`. All threads entering with count = 5 + * and `nentered` in [0, 4] should be released once `nentered` reaches 5: + * call 5 the release `threshold`. All threads entering with count = 5 + * and `nentered` in [5, 9] should be released once `nentered` reaches 10. + */ + threshold = (barrier->nentered / barrier->count + 1) * barrier->count; barrier->nentered++; - while (barrier->nentered % barrier->count != 0) { + while (barrier->nentered < threshold) { if ((rc = pthread_cond_wait(&barrier->cv, &barrier->mtx)) != 0) goto out; } -- cgit v0.12 From 803d805c74466a9d736455930b17de2d9f5cb02d Mon Sep 17 00:00:00 2001 From: David Young Date: Thu, 27 Feb 2020 16:14:44 -0600 Subject: Complete the comment on thread_main(), explaining why the barrier is used. --- test/thread_id.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/thread_id.c b/test/thread_id.c index e3cf42e..818ab4a 100644 --- a/test/thread_id.c +++ b/test/thread_id.c @@ -226,7 +226,15 @@ atomic_printf(const char *fmt, ...) /* Each thread runs this routine. The routine fetches the current * thread's ID, makes sure that it is in the expected range, makes * sure that in this round of testing, no two threads shared the - * same ID, + * same ID, and checks that each thread's ID is constant over its lifetime. + * + * main() checks that every ID in [1, NTHREADS] is used in each round + * of testing. All NTHREADS threads synchronize on a barrier after each + * has fetched its ID. The barrier guarantees that all threads' lifetimes + * overlap at least momentarily, so the IDs will be unique, and there + * will be NTHREADS of them. Further, since thread IDs are assigned + * starting with 1, and the number of threads with IDs alive never exceeds + * NTHREADS, the least ID has to be 1 and the greatest, NTHREADS. */ static void * thread_main(void H5_ATTR_UNUSED *arg) -- cgit v0.12