[Mesa-dev] [PATCHv2 06/13] glsl: add a generic thread pool data structure
Chia-I Wu
olvaffe at gmail.com
Tue Aug 19 00:50:26 PDT 2014
On Thu, Aug 14, 2014 at 8:38 AM, Ian Romanick <idr at freedesktop.org> wrote:
> On 07/09/2014 12:47 AM, Chia-I Wu wrote:
>> It can be used to implement, for example, threaded glCompileShader and
>> glLinkProgram.
>>
>> v2: allow tasks to "complete" other tasks
>>
>> Signed-off-by: Chia-I Wu <olv at lunarg.com>
>> ---
>> src/glsl/Makefile.am | 12 +-
>> src/glsl/Makefile.sources | 3 +-
>> src/glsl/tests/threadpool_test.cpp | 137 +++++++++++
>
> Some description of what the tests are doing would be good.
Sure.
>
>> src/glsl/threadpool.c | 476 +++++++++++++++++++++++++++++++++++++
>> src/glsl/threadpool.h | 67 ++++++
>> 5 files changed, 693 insertions(+), 2 deletions(-)
>> create mode 100644 src/glsl/tests/threadpool_test.cpp
>> create mode 100644 src/glsl/threadpool.c
>> create mode 100644 src/glsl/threadpool.h
>>
>> diff --git a/src/glsl/Makefile.am b/src/glsl/Makefile.am
>> index 00261fd..3d07af3 100644
>> --- a/src/glsl/Makefile.am
>> +++ b/src/glsl/Makefile.am
>> @@ -35,6 +35,7 @@ TESTS = glcpp/tests/glcpp-test \
>> tests/general-ir-test \
>> tests/optimization-test \
>> tests/ralloc-test \
>> + tests/threadpool-test \
>> tests/sampler-types-test \
>> tests/uniform-initializer-test
>>
>> @@ -48,6 +49,7 @@ check_PROGRAMS = \
>> glsl_test \
>> tests/general-ir-test \
>> tests/ralloc-test \
>> + tests/threadpool-test \
>> tests/sampler-types-test \
>> tests/uniform-initializer-test
>>
>> @@ -95,6 +97,14 @@ tests_ralloc_test_LDADD = \
>> $(top_builddir)/src/gtest/libgtest.la \
>> $(PTHREAD_LIBS)
>>
>> +tests_threadpool_test_SOURCES = \
>> + tests/threadpool_test.cpp \
>> + $(top_builddir)/src/glsl/threadpool.c
>> +tests_threadpool_test_CFLAGS = $(PTHREAD_CFLAGS)
>> +tests_threadpool_test_LDADD = \
>> + $(top_builddir)/src/gtest/libgtest.la \
>> + $(PTHREAD_LIBS)
>> +
>> tests_sampler_types_test_SOURCES = \
>> $(top_srcdir)/src/mesa/program/prog_hash_table.c\
>> $(top_srcdir)/src/mesa/program/symbol_table.c \
>> @@ -120,7 +130,7 @@ glcpp_glcpp_LDADD = \
>> libglcpp.la \
>> -lm
>>
>> -libglsl_la_LIBADD = libglcpp.la
>> +libglsl_la_LIBADD = libglcpp.la $(PTHREAD_LIBS)
>> libglsl_la_SOURCES = \
>> glsl_lexer.cpp \
>> glsl_parser.cpp \
>> diff --git a/src/glsl/Makefile.sources b/src/glsl/Makefile.sources
>> index 6fc94d6..bab2358 100644
>> --- a/src/glsl/Makefile.sources
>> +++ b/src/glsl/Makefile.sources
>> @@ -103,7 +103,8 @@ LIBGLSL_FILES = \
>> $(GLSL_SRCDIR)/opt_tree_grafting.cpp \
>> $(GLSL_SRCDIR)/opt_vectorize.cpp \
>> $(GLSL_SRCDIR)/s_expression.cpp \
>> - $(GLSL_SRCDIR)/strtod.cpp
>> + $(GLSL_SRCDIR)/strtod.cpp \
>> + $(GLSL_SRCDIR)/threadpool.c
>>
>> # glsl_compiler
>>
>> diff --git a/src/glsl/tests/threadpool_test.cpp b/src/glsl/tests/threadpool_test.cpp
>> new file mode 100644
>> index 0000000..63f55c5
>> --- /dev/null
>> +++ b/src/glsl/tests/threadpool_test.cpp
>> @@ -0,0 +1,137 @@
>> +/*
>> + * Copyright © 2014 LunarG, Inc.
>> + *
>> + * Permission is hereby granted, free of charge, to any person obtaining a
>> + * copy of this software and associated documentation files (the "Software"),
>> + * to deal in the Software without restriction, including without limitation
>> + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
>> + * and/or sell copies of the Software, and to permit persons to whom the
>> + * Software is furnished to do so, subject to the following conditions:
>> + *
>> + * The above copyright notice and this permission notice (including the next
>> + * paragraph) shall be included in all copies or substantial portions of the
>> + * Software.
>> + *
>> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
>> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
>> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
>> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
>> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
>> + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
>> + * DEALINGS IN THE SOFTWARE.
>> + */
>> +#include <gtest/gtest.h>
>> +#include <string.h>
>> +#include <stdbool.h>
>> +#include <stdlib.h>
>> +#include <time.h>
>> +#include <unistd.h>
>> +#include "c11/threads.h"
>> +
>> +#include "threadpool.h"
>> +
>> +#define NUM_THREADS 10
>> +#define OPS_PER_THREAD 100
>> +#define MAX_TASKS 10
>> +
>> +static void
>> +race_cb(void *data)
>> +{
>> + usleep(1000 * 5);
>> +}
>> +
>> +static int
>> +race_random_op(void *data)
>> +{
>> + struct _mesa_threadpool *pool = (struct _mesa_threadpool *) data;
>> + struct _mesa_threadpool_task *tasks[MAX_TASKS];
>> + int num_tasks = 0;
>> + int num_ops = 0;
>> + int i;
>> +
>> + while (num_ops < OPS_PER_THREAD) {
>> + int op = (random() % 1000);
>> +
>> + if (op < 480) { /* 48% */
>> + if (num_tasks < MAX_TASKS) {
>> + tasks[num_tasks++] =
>> + _mesa_threadpool_queue_task(pool, race_cb, NULL);
>> + }
>> + }
>> + else if (op < 980) { /* 50% */
>> + if (num_tasks)
>> + _mesa_threadpool_complete_task(pool, tasks[--num_tasks]);
>> + }
>> + else if (op < 995) { /* 1.5% */
>> + for (i = 0; i < num_tasks; i++)
>> + _mesa_threadpool_complete_task(pool, tasks[i]);
>> + num_tasks = 0;
>> + }
>> + else { /* 0.5% */
>> + _mesa_threadpool_join(pool, (op < 998));
>> + }
>> +
>> + num_ops++;
>> + }
>> +
>> + for (i = 0; i < num_tasks; i++)
>> + _mesa_threadpool_complete_task(pool, tasks[i]);
>> +
>> + _mesa_threadpool_unref(pool);
>> +
>> + return 0;
>> +}
>> +
>> +/**
>> + * \name Thread safety
>> + */
>> +/*@{*/
>> +TEST(threadpool_test, race)
>> +{
>> + struct _mesa_threadpool *pool;
>> + thrd_t threads[NUM_THREADS];
>> + int i;
>> +
>> + srandom(time(NULL));
>> + pool = _mesa_threadpool_create(4);
>> + for (i = 0; i < NUM_THREADS; i++) {
>> + thrd_create(&threads[i], race_random_op,
>> + (void *) _mesa_threadpool_ref(pool));
>> + }
>> + _mesa_threadpool_unref(pool);
>> +
>> + for (i = 0; i < NUM_THREADS; i++)
>> + thrd_join(threads[i], NULL);
>> +
>> + /* this is not really a unit test */
>> + EXPECT_TRUE(true);
>> +}
>> +
>> +static void
>> +basic_cb(void *data)
>> +{
>> + int *val = (int *) data;
>> +
>> + usleep(1000 * 5);
>> + *val = 1;
>> +}
>> +
>> +TEST(threadpool_test, basic)
>> +{
>> + struct _mesa_threadpool *pool;
>> + struct _mesa_threadpool_task *tasks[2];
>> + int vals[2];
>> +
>> + pool = _mesa_threadpool_create(2);
>> +
>> + vals[0] = vals[1] = 0;
>> + tasks[0] = _mesa_threadpool_queue_task(pool, basic_cb, (void *) &vals[0]);
>> + tasks[1] = _mesa_threadpool_queue_task(pool, basic_cb, (void *) &vals[1]);
>> + _mesa_threadpool_complete_task(pool, tasks[0]);
>> + _mesa_threadpool_complete_task(pool, tasks[1]);
>> + EXPECT_TRUE(vals[0] == 1 && vals[1] == 1);
>> +
>> + _mesa_threadpool_unref(pool);
>> +}
>> +
>> +/*@}*/
>> diff --git a/src/glsl/threadpool.c b/src/glsl/threadpool.c
>> new file mode 100644
>> index 0000000..c069fd3
>> --- /dev/null
>> +++ b/src/glsl/threadpool.c
>> @@ -0,0 +1,476 @@
>> +/*
>> + * Mesa 3-D graphics library
>> + *
>> + * Copyright (C) 2014 LunarG, Inc. All Rights Reserved.
>> + *
>> + * Permission is hereby granted, free of charge, to any person obtaining a
>> + * copy of this software and associated documentation files (the "Software"),
>> + * to deal in the Software without restriction, including without limitation
>> + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
>> + * and/or sell copies of the Software, and to permit persons to whom the
>> + * Software is furnished to do so, subject to the following conditions:
>> + *
>> + * The above copyright notice and this permission notice shall be included
>> + * in all copies or substantial portions of the Software.
>> + *
>> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
>> + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
>> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
>> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
>> + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
>> + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
>> + * OTHER DEALINGS IN THE SOFTWARE.
>> + */
>> +
>> +#include <stdio.h>
>> +#include <stdbool.h>
>> +#include "c11/threads.h"
>> +#include "main/compiler.h"
>> +#include "main/simple_list.h"
>> +#include "threadpool.h"
>> +
>> +enum _mesa_threadpool_control {
>> + MESA_THREADPOOL_NORMAL, /* threads wait when there is no task */
>> + MESA_THREADPOOL_QUIT, /* threads quit when there is no task */
>> + MESA_THREADPOOL_QUIT_NOW /* threads quit as soon as possible */
>> +};
>> +
>> +enum _mesa_threadpool_task_state {
>> + MESA_THREADPOOL_TASK_PENDING, /* task is on the pending list */
>> + MESA_THREADPOOL_TASK_ACTIVE, /* task is being worked on */
>> + MESA_THREADPOOL_TASK_COMPLETED, /* task has been completed */
>> + MESA_THREADPOOL_TASK_CANCELLED /* task is cancelled */
>> +};
>> +
>> +struct _mesa_threadpool_task {
>> + /* these are protected by the pool's mutex */
>> + struct simple_node link; /* must be the first */
>
> I think we're trying to kill off simple_node. Matt did some work in
> this area. Perhaps he has an opinion.
What will be the replacement? I didn't use anything fancy from
simple_node so the conversion should be easy.
FWIW, kernel-style lists work pretty well for me.
>
>> + enum _mesa_threadpool_task_state state;
>> + cnd_t completed;
>> +
>> + void (*func)(void *);
>> + void *data;
>> +};
>> +
>> +struct _mesa_threadpool {
>> + mtx_t mutex;
>> + int refcnt;
>> +
>> + enum _mesa_threadpool_control thread_control;
>> + thrd_t *threads;
>> + int num_threads, max_threads;
>> + int idle_threads; /* number of threads that are idle */
>> + cnd_t thread_wakeup;
>> + cnd_t thread_joined;
>> +
>> + struct simple_node pending_tasks;
>> + int num_pending_tasks;
>> + int num_tasks;
>> +};
>> +
>> +static struct _mesa_threadpool_task *
>> +task_create(void)
>> +{
>> + struct _mesa_threadpool_task *task;
>> +
>> + task = malloc(sizeof(*task));
>> + if (!task)
>> + return NULL;
>> +
>> + if (cnd_init(&task->completed)) {
>> + free(task);
>> + return NULL;
>> + }
>> +
>> + task->state = MESA_THREADPOOL_TASK_PENDING;
>> +
>> + return task;
>> +}
>> +
>> +static void
>> +task_destroy(struct _mesa_threadpool_task *task)
>> +{
>> + cnd_destroy(&task->completed);
>> + free(task);
>> +}
>> +
>> +static void
>> +pool_exec_task(struct _mesa_threadpool *pool,
>> + struct _mesa_threadpool_task *task)
>> +{
>> + assert(task->state == MESA_THREADPOOL_TASK_PENDING);
>> +
>> + remove_from_list(&task->link);
>> + pool->num_pending_tasks--;
>> +
>> + task->state = MESA_THREADPOOL_TASK_ACTIVE;
>> +
>> + /* do the work! */
>> + mtx_unlock(&pool->mutex);
>> + task->func(task->data);
>> + mtx_lock(&pool->mutex);
>> +
>> + task->state = MESA_THREADPOOL_TASK_COMPLETED;
>> +}
>> +
>> +static int
>> +_mesa_threadpool_worker(void *arg)
>> +{
>> + struct _mesa_threadpool *pool = (struct _mesa_threadpool *) arg;
>> +
>> + mtx_lock(&pool->mutex);
>> +
>> + while (true) {
>> + struct _mesa_threadpool_task *task;
>> +
>> + /* wait until there are tasks */
>> + while (is_empty_list(&pool->pending_tasks) &&
>> + pool->thread_control == MESA_THREADPOOL_NORMAL) {
>> + pool->idle_threads++;
>> + cnd_wait(&pool->thread_wakeup, &pool->mutex);
>> + pool->idle_threads--;
>> + }
>> +
>> + if (pool->thread_control != MESA_THREADPOOL_NORMAL) {
>> + if (pool->thread_control == MESA_THREADPOOL_QUIT_NOW ||
>> + is_empty_list(&pool->pending_tasks))
>> + break;
>> + }
>> +
>> + assert(!is_empty_list(&pool->pending_tasks));
>> + task = (struct _mesa_threadpool_task *)
>> + first_elem(&pool->pending_tasks);
>> +
>> + pool_exec_task(pool, task);
>> + cnd_signal(&task->completed);
>> + }
>> +
>> + mtx_unlock(&pool->mutex);
>> +
>> + return 0;
>> +}
>> +
>> +/**
>> + * Queue a new task.
>> + */
>> +struct _mesa_threadpool_task *
>> +_mesa_threadpool_queue_task(struct _mesa_threadpool *pool,
>> + void (*func)(void *), void *data)
>> +{
>> + struct _mesa_threadpool_task *task;
>> +
>> + task = task_create();
>> + if (!task)
>> + return NULL;
>> +
>> + task->func = func;
>> + task->data = data;
>> +
>> + mtx_lock(&pool->mutex);
>> +
>> + /* someone is joining with the threads */
>> + while (unlikely(pool->thread_control != MESA_THREADPOOL_NORMAL))
>> + cnd_wait(&pool->thread_joined, &pool->mutex);
>> +
>> + /* spawn threads as needed */
>> + if (pool->idle_threads <= pool->num_pending_tasks &&
>> + pool->num_threads < pool->max_threads) {
>> + int err;
>> +
>> + err = thrd_create(&pool->threads[pool->num_threads],
>> + _mesa_threadpool_worker, (void *) pool);
>> + if (!err)
>> + pool->num_threads++;
>> +
>> + if (!pool->num_threads) {
>> + mtx_unlock(&pool->mutex);
>> + task_destroy(task);
>> + return NULL;
>> + }
>> + }
>> +
>> + insert_at_tail(&pool->pending_tasks, &task->link);
>> + pool->num_tasks++;
>> + pool->num_pending_tasks++;
>> + cnd_signal(&pool->thread_wakeup);
>> +
>> + mtx_unlock(&pool->mutex);
>> +
>> + return task;
>> +}
>> +
>> +/**
>> + * Wait and destroy the tasks.
>> + */
>> +static bool
>> +pool_wait_tasks(struct _mesa_threadpool *pool,
>> + struct _mesa_threadpool_task **tasks,
>> + int num_tasks)
>> +{
>> + bool all_completed = true;
>> + int i;
>> +
>> + for (i = 0; i < num_tasks; i++) {
>> + struct _mesa_threadpool_task *task = tasks[i];
>> +
>> + while (task->state != MESA_THREADPOOL_TASK_COMPLETED &&
>> + task->state != MESA_THREADPOOL_TASK_CANCELLED)
>> + cnd_wait(&task->completed, &pool->mutex);
>> +
>> + if (task->state != MESA_THREADPOOL_TASK_COMPLETED)
>> + all_completed = false;
>> +
>> + task_destroy(task);
>> + }
>> +
>> + pool->num_tasks -= num_tasks;
>> +
>> + return all_completed;
>> +}
>> +
>> +/**
>> + * Wait for \p tasks to complete, and destroy them. If some of \p tasks
>> + * cannot not be completed, return false.
>> + *
>> + * This function can be called from within the worker threads.
>> + */
>> +bool
>> +_mesa_threadpool_complete_tasks(struct _mesa_threadpool *pool,
>> + struct _mesa_threadpool_task **tasks,
>> + int num_tasks)
>> +{
>> + bool prioritized = false, completed;
>> + int i;
>> +
>> + mtx_lock(&pool->mutex);
>> +
>> + /* we need to do something about tasks that are pending */
>> + for (i = 0; i < num_tasks; i++) {
>> + struct _mesa_threadpool_task *task = tasks[i];
>> +
>> + if (task->state != MESA_THREADPOOL_TASK_PENDING)
>> + continue;
>> +
>> + /* move them to the head so that they are executed next */
>> + if (!prioritized) {
>> + int j;
>> +
>> + for (j = i + 1; j < num_tasks; j++) {
>> + if (task->state == MESA_THREADPOOL_TASK_PENDING)
>> + move_to_head(&pool->pending_tasks, &task->link);
>> + }
>> + prioritized = true;
>> + }
>> +
>> + /*
>> + * Execute right away for we would have to wait for the worker threads
>> + * otherwise, which is no faster. More importantly, when this is called
>> + * from within worker threads, there may be no idle thread available to
>> + * execute them.
>> + */
>> + pool_exec_task(pool, task);
>> + }
>> +
>> + completed = pool_wait_tasks(pool, tasks, num_tasks);
>> +
>> + mtx_unlock(&pool->mutex);
>> +
>> + return completed;
>> +}
>> +
>> +/**
>> + * This is equivalent to calling \p _mesa_threadpool_complete_tasks with one
>> + * task.
>> + */
>> +bool
>> +_mesa_threadpool_complete_task(struct _mesa_threadpool *pool,
>> + struct _mesa_threadpool_task *task)
>> +{
>> + bool completed;
>> +
>> + mtx_lock(&pool->mutex);
>> +
>> + if (task->state == MESA_THREADPOOL_TASK_PENDING)
>> + pool_exec_task(pool, task);
>> +
>> + completed = pool_wait_tasks(pool, &task, 1);
>> +
>> + mtx_unlock(&pool->mutex);
>> +
>> + return completed;
>> +}
>> +
>> +static void
>> +pool_cancel_pending_tasks(struct _mesa_threadpool *pool)
>> +{
>> + struct simple_node *node, *temp;
>> +
>> + if (is_empty_list(&pool->pending_tasks))
>> + return;
>> +
>> + foreach_s(node, temp, &pool->pending_tasks) {
>> + struct _mesa_threadpool_task *task =
>> + (struct _mesa_threadpool_task *) node;
>> +
>> + remove_from_list(&task->link);
>> + task->state = MESA_THREADPOOL_TASK_CANCELLED;
>> +
>> + /* in case some thread is already waiting */
>> + cnd_signal(&task->completed);
>> + }
>> +
>> + pool->num_pending_tasks = 0;
>> +}
>> +
>> +static void
>> +pool_join_threads(struct _mesa_threadpool *pool, bool graceful)
>> +{
>> + int joined_threads = 0;
>> +
>> + if (!pool->num_threads)
>> + return;
>> +
>> + pool->thread_control = (graceful) ?
>> + MESA_THREADPOOL_QUIT : MESA_THREADPOOL_QUIT_NOW;
>> +
>> + while (joined_threads < pool->num_threads) {
>> + int i = joined_threads, num_threads = pool->num_threads;
>> +
>> + cnd_broadcast(&pool->thread_wakeup);
>> + mtx_unlock(&pool->mutex);
>> + while (i < num_threads)
>> + thrd_join(pool->threads[i++], NULL);
>> + mtx_lock(&pool->mutex);
>> +
>> + joined_threads = num_threads;
>> + }
>> +
>> + pool->thread_control = MESA_THREADPOOL_NORMAL;
>> + pool->num_threads = 0;
>> + assert(!pool->idle_threads);
>> +}
>> +
>> +/**
>> + * Join with all pool threads. When \p graceful is true, wait for the pending
>> + * tasks to be completed.
>> + */
>> +void
>> +_mesa_threadpool_join(struct _mesa_threadpool *pool, bool graceful)
>> +{
>> + mtx_lock(&pool->mutex);
>> +
>> + /* someone is already joining with the threads */
>> + while (unlikely(pool->thread_control != MESA_THREADPOOL_NORMAL))
>> + cnd_wait(&pool->thread_joined, &pool->mutex);
>> +
>> + if (pool->num_threads) {
>> + pool_join_threads(pool, graceful);
>> + /* wake up whoever is waiting */
>> + cnd_broadcast(&pool->thread_joined);
>> + }
>> +
>> + if (!graceful)
>> + pool_cancel_pending_tasks(pool);
>> +
>> + assert(pool->num_threads == 0);
>> + assert(is_empty_list(&pool->pending_tasks) && !pool->num_pending_tasks);
>> +
>> + mtx_unlock(&pool->mutex);
>> +}
>> +
>> +/**
>> + * Decrease the reference count. Destroy \p pool when the reference count
>> + * reaches zero.
>> + */
>> +void
>> +_mesa_threadpool_unref(struct _mesa_threadpool *pool)
>> +{
>> + bool destroy = false;
>> +
>> + mtx_lock(&pool->mutex);
>> + pool->refcnt--;
>> + destroy = (pool->refcnt == 0);
>> + mtx_unlock(&pool->mutex);
>
> Add this to the list of candidates for atomic counters.
No problem.
>
>> +
>> + if (destroy) {
>> + _mesa_threadpool_join(pool, false);
>> +
>> + if (pool->num_tasks) {
>> + fprintf(stderr, "thread pool destroyed with %d tasks\n",
>> + pool->num_tasks);
>> + }
>> +
>> + free(pool->threads);
>> + cnd_destroy(&pool->thread_joined);
>> + cnd_destroy(&pool->thread_wakeup);
>> + mtx_destroy(&pool->mutex);
>> + free(pool);
>> + }
>> +}
>> +
>> +/**
>> + * Increase the reference count.
>> + */
>> +struct _mesa_threadpool *
>> +_mesa_threadpool_ref(struct _mesa_threadpool *pool)
>> +{
>> + mtx_lock(&pool->mutex);
>> + pool->refcnt++;
>> + mtx_unlock(&pool->mutex);
>> +
>> + return pool;
>> +}
>> +
>> +/**
>> + * Create a thread pool. As threads are spawned as needed, this is
>> + * inexpensive.
>> + */
>> +struct _mesa_threadpool *
>> +_mesa_threadpool_create(int max_threads)
>> +{
>> + struct _mesa_threadpool *pool;
>> +
>> + if (max_threads < 1)
>> + return NULL;
>> +
>> + pool = calloc(1, sizeof(*pool));
>> + if (!pool)
>> + return NULL;
>> +
>> + if (mtx_init(&pool->mutex, mtx_plain)) {
>> + free(pool);
>> + return NULL;
>> + }
>> +
>> + pool->refcnt = 1;
>> +
>> + if (cnd_init(&pool->thread_wakeup)) {
>> + mtx_destroy(&pool->mutex);
>> + free(pool);
>> + return NULL;
>> + }
>> +
>> + if (cnd_init(&pool->thread_joined)) {
>> + cnd_destroy(&pool->thread_wakeup);
>> + mtx_destroy(&pool->mutex);
>> + free(pool);
>> + return NULL;
>> + }
>> +
>> + pool->thread_control = MESA_THREADPOOL_NORMAL;
>> +
>> + pool->threads = malloc(sizeof(pool->threads[0]) * max_threads);
>
> Can the size of threads change after pool creation? If not, I'd almost
> be inclined to allocate it and pool using a single allocation. Since
> they have the same lifetime, it looks like that will simplify some of
> the clean up code, etc. Ken or Matt may have opinions.
And accessing them using an zero-length array or the like? I am not
against it, but that seems would only save us an free() call when the
pool is destroyed.
>
>> + if (!pool->threads) {
>> + cnd_destroy(&pool->thread_joined);
>> + cnd_destroy(&pool->thread_wakeup);
>> + mtx_destroy(&pool->mutex);
>> + free(pool);
>> + return NULL;
>> + }
>> +
>> + pool->max_threads = max_threads;
>> +
>> + make_empty_list(&pool->pending_tasks);
>> +
>> + return pool;
>> +}
>> diff --git a/src/glsl/threadpool.h b/src/glsl/threadpool.h
>> new file mode 100644
>> index 0000000..48e4a47
>> --- /dev/null
>> +++ b/src/glsl/threadpool.h
>> @@ -0,0 +1,67 @@
>> +/*
>> + * Mesa 3-D graphics library
>> + *
>> + * Copyright (C) 2014 LunarG, Inc. All Rights Reserved.
>> + *
>> + * Permission is hereby granted, free of charge, to any person obtaining a
>> + * copy of this software and associated documentation files (the "Software"),
>> + * to deal in the Software without restriction, including without limitation
>> + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
>> + * and/or sell copies of the Software, and to permit persons to whom the
>> + * Software is furnished to do so, subject to the following conditions:
>> + *
>> + * The above copyright notice and this permission notice shall be included
>> + * in all copies or substantial portions of the Software.
>> + *
>> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
>> + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
>> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
>> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
>> + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
>> + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
>> + * OTHER DEALINGS IN THE SOFTWARE.
>> + */
>> +
>> +
>> +#ifndef THREADPOOL_H
>> +#define THREADPOOL_H
>> +
>> +#include <stdbool.h>
>> +
>> +#ifdef __cplusplus
>> +extern "C" {
>> +#endif
>> +
>> +struct _mesa_threadpool;
>> +struct _mesa_threadpool_task;
>> +
>> +struct _mesa_threadpool *
>> +_mesa_threadpool_create(int max_threads);
>> +
>> +struct _mesa_threadpool *
>> +_mesa_threadpool_ref(struct _mesa_threadpool *pool);
>> +
>> +void
>> +_mesa_threadpool_unref(struct _mesa_threadpool *pool);
>> +
>> +void
>> +_mesa_threadpool_join(struct _mesa_threadpool *pool, bool graceful);
>> +
>> +struct _mesa_threadpool_task *
>> +_mesa_threadpool_queue_task(struct _mesa_threadpool *pool,
>> + void (*func)(void *), void *data);
>> +
>> +bool
>> +_mesa_threadpool_complete_tasks(struct _mesa_threadpool *pool,
>> + struct _mesa_threadpool_task **tasks,
>> + int num_tasks);
>> +
>> +bool
>> +_mesa_threadpool_complete_task(struct _mesa_threadpool *pool,
>> + struct _mesa_threadpool_task *task);
>> +
>> +#ifdef __cplusplus
>> +}
>> +#endif
>> +
>> +#endif /* THREADPOOL_H */
>>
>
--
olv at LunarG.com
More information about the mesa-dev
mailing list