/* wg.c - Thread management tools modeled on * https://golang.org/pkg/sync/#WaitGroup * * Copyright (C) 2016 Luke Shumaker * * 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 3 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, see . */ #include #include #include /* for EXIT_FAILURE */ #include #include "wg.h" /* pthread_cond_t is overly complicated. Just use a self-pipe. */ static void *wg_gc(void *wg_anon) { struct wg *wg = wg_anon; pthread_t thread; while (1) { ssize_t r = read(wg->fd_threads[0], &thread, sizeof(thread)); if (r < 0) { if (errno == EINTR) continue; error(EXIT_FAILURE, errno, "wg_gc: read"); } else if ((size_t)r < sizeof(thread)) { error(EXIT_FAILURE, 0, "wg_gc: read: only read %zd/%zu", r, sizeof(thread)); } pthread_join(thread, NULL); int p; if ((p = pthread_mutex_lock(&wg->lock)) != 0) error(EXIT_FAILURE, p, "wg_gc: pthread_mutex_lock"); wg->count--; if (wg->count == 0) { if ((p = pthread_mutex_unlock(&wg->lock)) != 0) error(EXIT_FAILURE, p, "wg_gc: pthread_mutex_unlock"); return NULL; } if ((p = pthread_mutex_unlock(&wg->lock)) != 0) error(EXIT_FAILURE, p, "wg_gc: pthread_mutex_unlock"); } } void wg_init(struct wg *wg) { wg->count = 0; int r; if ((r = pthread_mutex_init(&wg->lock, NULL)) != 0) error(EXIT_FAILURE, r, "wg_init: pthread_mutex_init"); if (pipe(wg->fd_threads) != 0) error(EXIT_FAILURE, errno, "wg_init: pipe"); if ((r = pthread_create(&wg->gc, NULL, wg_gc, (void*)wg)) != 0) error(EXIT_FAILURE, r, "wg_init: pthread_create"); } void wg_add(struct wg *wg) { int r; if ((r = pthread_mutex_lock(&wg->lock)) != 0) error(EXIT_FAILURE, r, "wg_add: pthread_mutex_lock"); wg->count++; if ((r = pthread_mutex_unlock(&wg->lock)) != 0) error(EXIT_FAILURE, r, "wg_add: pthread_mutex_unlock"); } void wg_sub(struct wg *wg) { pthread_t thread = pthread_self(); ssize_t r = write(wg->fd_threads[1], &thread, sizeof(thread)); if (r < 0) { error(EXIT_FAILURE, errno, "wg_sub: write"); } else if ((size_t)r < sizeof(thread)) { error(EXIT_FAILURE, 0, "wg_sub: only wrote %zd/%zu", r, sizeof(thread)); } } void wg_wait(struct wg *wg) { int r; if ((r = pthread_join(wg->gc, NULL)) != 0) error(EXIT_FAILURE, r, "wg_wait: pthread_join"); if (close(wg->fd_threads[1]) != 0) error(EXIT_FAILURE, errno, "wg_wait: close(fd_threads[1])"); if (close(wg->fd_threads[0]) != 0) error(EXIT_FAILURE, errno, "wg_wait: close(fd_threads[0])"); if ((r = pthread_mutex_destroy(&wg->lock)) != 0) error(EXIT_FAILURE, r, "wg_wait: pthread_mutex_destroy"); }