BLI_task: add support for full-background taskpools.

With current code, in single-threaded context, a pool of task may never be executed
until one calls BLI_task_pool_work_and_wait() on it, this is not acceptable for
asynchronous tasks where you never want to actually lock the main thread.

This commits adds an extra thread in single-threaded case, and a new 'type' of pool,
such that one can create real background pools of tasks. See code for details.

Review: D1565
This commit is contained in:
Bastien Montagne 2015-11-02 16:57:48 +01:00
parent 44774f8160
commit 04ac8768ef
2 changed files with 56 additions and 1 deletions

View File

@ -77,6 +77,7 @@ typedef void (*TaskRunFunction)(TaskPool *__restrict pool, void *taskdata, int t
typedef void (*TaskFreeFunction)(TaskPool *__restrict pool, void *taskdata, int threadid);
TaskPool *BLI_task_pool_create(TaskScheduler *scheduler, void *userdata);
TaskPool *BLI_task_pool_create_background(TaskScheduler *scheduler, void *userdata);
void BLI_task_pool_free(TaskPool *pool);
void BLI_task_pool_push_ex(

View File

@ -61,12 +61,17 @@ struct TaskPool {
ThreadMutex user_mutex;
volatile bool do_cancel;
/* If set, this pool may never be work_and_wait'ed, which means TaskScheduler has to use its special
* background fallback thread in case we are in single-threaded situation. */
bool run_in_background;
};
struct TaskScheduler {
pthread_t *threads;
struct TaskThread *task_threads;
int num_threads;
bool background_thread_only;
ListBase queue;
ThreadMutex queue_mutex;
@ -152,6 +157,11 @@ static bool task_scheduler_thread_wait_pop(TaskScheduler *scheduler, Task **task
current_task = current_task->next)
{
TaskPool *pool = current_task->pool;
if (scheduler->background_thread_only && !pool->run_in_background) {
continue;
}
if (pool->num_threads == 0 ||
pool->currently_running_tasks < pool->num_threads)
{
@ -216,6 +226,12 @@ TaskScheduler *BLI_task_scheduler_create(int num_threads)
/* main thread will also work, so we count it too */
num_threads -= 1;
/* Add background-only thread if needed. */
if (num_threads == 0) {
scheduler->background_thread_only = true;
num_threads = 1;
}
/* launch threads that will be waiting for work */
if (num_threads > 0) {
int i;
@ -326,15 +342,28 @@ static void task_scheduler_clear(TaskScheduler *scheduler, TaskPool *pool)
/* Task Pool */
TaskPool *BLI_task_pool_create(TaskScheduler *scheduler, void *userdata)
static TaskPool *task_pool_create_ex(TaskScheduler *scheduler, void *userdata, const bool is_background)
{
TaskPool *pool = MEM_callocN(sizeof(TaskPool), "TaskPool");
#ifndef NDEBUG
/* Assert we do not try to create a background pool from some parent task - those only work OK from main thread. */
if (is_background) {
const pthread_t thread_id = pthread_self();
int i = scheduler->num_threads;
while (i--) {
BLI_assert(scheduler->threads[i] != thread_id);
}
}
#endif
pool->scheduler = scheduler;
pool->num = 0;
pool->num_threads = 0;
pool->currently_running_tasks = 0;
pool->do_cancel = false;
pool->run_in_background = is_background;
BLI_mutex_init(&pool->num_mutex);
BLI_condition_init(&pool->num_cond);
@ -353,6 +382,31 @@ TaskPool *BLI_task_pool_create(TaskScheduler *scheduler, void *userdata)
return pool;
}
/**
* Create a normal task pool.
* This means that in single-threaded context, it will not be executed at all until you call
* \a BLI_task_pool_work_and_wait() on it.
*/
TaskPool *BLI_task_pool_create(TaskScheduler *scheduler, void *userdata)
{
return task_pool_create_ex(scheduler, userdata, false);
}
/**
* Create a background task pool.
* In multi-threaded context, there is no differences with \a BLI_task_pool_create(), but in single-threaded case
* it is ensured to have at least one worker thread to run on (i.e. you do not have to call
* \a BLI_task_pool_work_and_wait() on it to be sure it will be processed).
*
* \note Background pools are non-recursive (that is, you should not create other background pools in tasks assigned
* to a brackground pool, they could end never being executed, since the 'fallback' background thread is already
* busy with parent task in single-threaded context).
*/
TaskPool *BLI_task_pool_create_background(TaskScheduler *scheduler, void *userdata)
{
return task_pool_create_ex(scheduler, userdata, true);
}
void BLI_task_pool_free(TaskPool *pool)
{
BLI_task_pool_stop(pool);