Kacper Michajłowvarious: use mp_assert macro1c49cfd1 year agoHistory
223 lines · 6.3 KB · C
RawDownload
/* Copyright (C) 2018 the mpv developers * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "common/common.h"#include "osdep/threads.h"#include "osdep/timer.h" #include "thread_pool.h" // Threads destroy themselves after this many seconds, if there's no new work// and the thread count is above the configured minimum.#define DESTROY_TIMEOUT 10 struct work {    void (*fn)(void *ctx);    void *fn_ctx;}; struct mp_thread_pool {    int min_threads, max_threads;     mp_mutex lock;    mp_cond wakeup;     // --- the following fields are protected by lock     mp_thread *threads;    int num_threads;     // Number of threads which have taken up work and are still processing it.    int busy_threads;     bool terminate;     struct work *work;    int num_work;}; static MP_THREAD_VOID worker_thread(void *arg){    struct mp_thread_pool *pool = arg;     mp_thread_set_name("worker");     mp_mutex_lock(&pool->lock);     int64_t destroy_deadline = 0;    bool got_timeout = false;    while (1) {        struct work work = {0};        if (pool->num_work > 0) {            work = pool->work[pool->num_work - 1];            pool->num_work -= 1;        }         if (!work.fn) {            if (got_timeout || pool->terminate)                break;             if (pool->num_threads > pool->min_threads) {                if (!destroy_deadline)                    destroy_deadline = mp_time_ns() + MP_TIME_S_TO_NS(DESTROY_TIMEOUT);                if (mp_cond_timedwait_until(&pool->wakeup, &pool->lock, destroy_deadline))                    got_timeout = pool->num_threads > pool->min_threads;            } else {                mp_cond_wait(&pool->wakeup, &pool->lock);            }            continue;        }         pool->busy_threads += 1;        mp_mutex_unlock(&pool->lock);         work.fn(work.fn_ctx);         mp_mutex_lock(&pool->lock);        pool->busy_threads -= 1;         destroy_deadline = 0;        got_timeout = false;    }     // If no termination signal was given, it must mean we died because of a    // timeout, and nobody is waiting for us. We have to remove ourselves.    if (!pool->terminate) {        for (int n = 0; n < pool->num_threads; n++) {            if (mp_thread_id_equal(mp_thread_get_id(pool->threads[n]),                                   mp_thread_current_id()))            {                mp_thread_detach(pool->threads[n]);                MP_TARRAY_REMOVE_AT(pool->threads, pool->num_threads, n);                mp_mutex_unlock(&pool->lock);                MP_THREAD_RETURN();            }        }        MP_ASSERT_UNREACHABLE();    }     mp_mutex_unlock(&pool->lock);    MP_THREAD_RETURN();} static void thread_pool_dtor(void *ctx){    struct mp_thread_pool *pool = ctx;      mp_mutex_lock(&pool->lock);     pool->terminate = true;    mp_cond_broadcast(&pool->wakeup);     mp_thread *threads = pool->threads;    int num_threads = pool->num_threads;     pool->threads = NULL;    pool->num_threads = 0;     mp_mutex_unlock(&pool->lock);     for (int n = 0; n < num_threads; n++)        mp_thread_join(threads[n]);     mp_assert(pool->num_work == 0);    mp_assert(pool->num_threads == 0);    mp_cond_destroy(&pool->wakeup);    mp_mutex_destroy(&pool->lock);} static bool add_thread(struct mp_thread_pool *pool){    mp_thread thread;     if (mp_thread_create(&thread, worker_thread, pool) != 0)        return false;     MP_TARRAY_APPEND(pool, pool->threads, pool->num_threads, thread);    return true;} struct mp_thread_pool *mp_thread_pool_create(void *ta_parent, int init_threads,                                             int min_threads, int max_threads){    mp_assert(min_threads >= 0);    mp_assert(init_threads <= min_threads);    mp_assert(max_threads > 0 && max_threads >= min_threads);     struct mp_thread_pool *pool = talloc_zero(ta_parent, struct mp_thread_pool);    talloc_set_destructor(pool, thread_pool_dtor);     mp_mutex_init(&pool->lock);    mp_cond_init(&pool->wakeup);     pool->min_threads = min_threads;    pool->max_threads = max_threads;     mp_mutex_lock(&pool->lock);    for (int n = 0; n < init_threads; n++)        add_thread(pool);    bool ok = pool->num_threads >= init_threads;    mp_mutex_unlock(&pool->lock);     if (!ok)        TA_FREEP(&pool);     return pool;} static bool thread_pool_add(struct mp_thread_pool *pool, void (*fn)(void *ctx),                            void *fn_ctx, bool allow_queue){    bool ok = true;     mp_assert(fn);     mp_mutex_lock(&pool->lock);    struct work work = {fn, fn_ctx};     // If there are not enough threads to process all at once, but we can    // create a new thread, then do so. If work is queued quickly, it can    // happen that not all available threads have picked up work yet (up to    // num_threads - busy_threads threads), which has to be accounted for.    if (pool->busy_threads + pool->num_work + 1 > pool->num_threads &&        pool->num_threads < pool->max_threads)    {        if (!add_thread(pool)) {            // If we can queue it, it'll get done as long as there is 1 thread.            ok = allow_queue && pool->num_threads > 0;        }    }     if (ok) {        MP_TARRAY_INSERT_AT(pool, pool->work, pool->num_work, 0, work);        mp_cond_signal(&pool->wakeup);    }     mp_mutex_unlock(&pool->lock);    return ok;} bool mp_thread_pool_queue(struct mp_thread_pool *pool, void (*fn)(void *ctx),                          void *fn_ctx){    return thread_pool_add(pool, fn, fn_ctx, true);} bool mp_thread_pool_run(struct mp_thread_pool *pool, void (*fn)(void *ctx),                        void *fn_ctx){    return thread_pool_add(pool, fn, fn_ctx, false);}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t