To be used by the following commits.
Unified · Split
| @@ -0,0 +1,125 @@ | |||
| 1 | /* | ||
| 2 | * This file is part of mpv. | ||
| 3 | * | ||
| 4 | * mpv is free software; you can redistribute it and/or | ||
| 5 | * modify it under the terms of the GNU Lesser General Public | ||
| 6 | * License as published by the Free Software Foundation; either | ||
| 7 | * version 2.1 of the License, or (at your option) any later version. | ||
| 8 | * | ||
| 9 | * mpv is distributed in the hope that it will be useful, | ||
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 12 | * GNU Lesser General Public License for more details. | ||
| 13 | * | ||
| 14 | * You should have received a copy of the GNU Lesser General Public | ||
| 15 | * License along with mpv. If not, see <http://www.gnu.org/licenses/>. | ||
| 16 | */ | ||
| 17 | |||
| 18 | #include <pthread.h> | ||
| 19 | |||
| 20 | #include "common/common.h" | ||
| 21 | |||
| 22 | #include "thread_pool.h" | ||
| 23 | |||
| 24 | struct work { | ||
| 25 | void (*fn)(void *ctx); | ||
| 26 | void *fn_ctx; | ||
| 27 | }; | ||
| 28 | |||
| 29 | struct mp_thread_pool { | ||
| 30 | pthread_t *threads; | ||
| 31 | int num_threads; | ||
| 32 | |||
| 33 | pthread_mutex_t lock; | ||
| 34 | pthread_cond_t wakeup; | ||
| 35 | |||
| 36 | // --- the following fields are protected by lock | ||
| 37 | bool terminate; | ||
| 38 | struct work *work; | ||
| 39 | int num_work; | ||
| 40 | }; | ||
| 41 | |||
| 42 | static void *worker_thread(void *arg) | ||
| 43 | { | ||
| 44 | struct mp_thread_pool *pool = arg; | ||
| 45 | |||
| 46 | pthread_mutex_lock(&pool->lock); | ||
| 47 | while (1) { | ||
| 48 | while (!pool->num_work && !pool->terminate) | ||
| 49 | pthread_cond_wait(&pool->wakeup, &pool->lock); | ||
| 50 | |||
| 51 | if (!pool->num_work && pool->terminate) | ||
| 52 | break; | ||
| 53 | |||
| 54 | assert(pool->num_work > 0); | ||
| 55 | struct work work = pool->work[pool->num_work - 1]; | ||
| 56 | pool->num_work -= 1; | ||
| 57 | |||
| 58 | pthread_mutex_unlock(&pool->lock); | ||
| 59 | work.fn(work.fn_ctx); | ||
| 60 | pthread_mutex_lock(&pool->lock); | ||
| 61 | } | ||
| 62 | assert(pool->num_work == 0); | ||
| 63 | pthread_mutex_unlock(&pool->lock); | ||
| 64 | |||
| 65 | return NULL; | ||
| 66 | } | ||
| 67 | |||
| 68 | static void thread_pool_dtor(void *ctx) | ||
| 69 | { | ||
| 70 | struct mp_thread_pool *pool = ctx; | ||
| 71 | |||
| 72 | pthread_mutex_lock(&pool->lock); | ||
| 73 | pool->terminate = true; | ||
| 74 | pthread_cond_broadcast(&pool->wakeup); | ||
| 75 | pthread_mutex_unlock(&pool->lock); | ||
| 76 | |||
| 77 | for (int n = 0; n < pool->num_threads; n++) | ||
| 78 | pthread_join(pool->threads[n], NULL); | ||
| 79 | |||
| 80 | assert(pool->num_work == 0); | ||
| 81 | pthread_cond_destroy(&pool->wakeup); | ||
| 82 | pthread_mutex_destroy(&pool->lock); | ||
| 83 | } | ||
| 84 | |||
| 85 | // Create a thread pool with the given number of worker threads. This can return | ||
| 86 | // NULL if the worker threads could not be created. The thread pool can be | ||
| 87 | // destroyed with talloc_free(pool), or indirectly with talloc_free(ta_parent). | ||
| 88 | // If there are still work items on freeing, it will block until all work items | ||
| 89 | // are done, and the threads terminate. | ||
| 90 | struct mp_thread_pool *mp_thread_pool_create(void *ta_parent, int threads) | ||
| 91 | { | ||
| 92 | assert(threads > 0); | ||
| 93 | |||
| 94 | struct mp_thread_pool *pool = talloc_zero(ta_parent, struct mp_thread_pool); | ||
| 95 | talloc_set_destructor(pool, thread_pool_dtor); | ||
| 96 | |||
| 97 | pthread_mutex_init(&pool->lock, NULL); | ||
| 98 | pthread_cond_init(&pool->wakeup, NULL); | ||
| 99 | |||
| 100 | for (int n = 0; n < threads; n++) { | ||
| 101 | pthread_t thread; | ||
| 102 | if (pthread_create(&thread, NULL, worker_thread, pool)) { | ||
| 103 | talloc_free(pool); | ||
| 104 | return NULL; | ||
| 105 | } | ||
| 106 | MP_TARRAY_APPEND(pool, pool->threads, pool->num_threads, thread); | ||
| 107 | } | ||
| 108 | |||
| 109 | return pool; | ||
| 110 | } | ||
| 111 | |||
| 112 | // Queue a function to be run on a worker thread: fn(fn_ctx) | ||
| 113 | // If no worker thread is currently available, it's appended to a list in memory | ||
| 114 | // with unbounded size. This function always returns immediately. | ||
| 115 | // Concurrent queue calls are allowed, as long as it does not overlap with | ||
| 116 | // pool destruction. | ||
| 117 | void mp_thread_pool_queue(struct mp_thread_pool *pool, void (*fn)(void *ctx), | ||
| 118 | void *fn_ctx) | ||
| 119 | { | ||
| 120 | pthread_mutex_lock(&pool->lock); | ||
| 121 | struct work work = {fn, fn_ctx}; | ||
| 122 | MP_TARRAY_INSERT_AT(pool, pool->work, pool->num_work, 0, work); | ||
| 123 | pthread_cond_signal(&pool->wakeup); | ||
| 124 | pthread_mutex_unlock(&pool->lock); | ||
| 125 | } | ||
| @@ -0,0 +1,10 @@ | |||
| 1 | #ifndef MPV_MP_THREAD_POOL_H | ||
| 2 | #define MPV_MP_THREAD_POOL_H | ||
| 3 | |||
| 4 | struct mp_thread_pool; | ||
| 5 | |||
| 6 | struct mp_thread_pool *mp_thread_pool_create(void *ta_parent, int threads); | ||
| 7 | void mp_thread_pool_queue(struct mp_thread_pool *pool, void (*fn)(void *ctx), | ||
| 8 | void *fn_ctx); | ||
| 9 | |||
| 10 | #endif | ||