Skip to content

Conversation

akukanov
Copy link
Contributor

This PR adds the "RFC"/design document for isolated_task_group as an experimental feature of the library.

Copy link
Contributor

@vossmjp vossmjp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good to me but I found a few minor issues.


## Introduction

In-arena task isolation scopes were introduced into TBB to restrict task stealing within arena
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
In-arena task isolation scopes were introduced into TBB to restrict task stealing within arena
[In-arena task isolation scopes](https://oneapi-spec.uxlfoundation.org/specifications/oneapi/latest/elements/onetbb/source/task_scheduler/task_arena/this_task_arena_ns) were introduced into TBB to restrict task stealing within an arena

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +26 to +38
tbb::task_group tg;
mutex init_mtx;
parallel_XXXL { // pipeline, FG
while (!initialized(some_obj)) {
if (try_lock(init_mtx)) {
tg.run_and_wait {
initialize(some_obj); // uses parallel_for, etc.
};
unlock(init_mtx);
} else { tg.wait(); }
}
... // do the work that uses some_obj
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can it make sense for two isolated_task_groups to share one isolation tag?
I thought of the case of having two objects A and B, both are lazy initialized using the pattern described, but using the isolated_task_group. But an object B needs A to be initialized before running the parallel phase of the initialization:

A::initialize() { parallel_for(...) }

B::initialize() {
    A& a = getA();
    parallel_for(...);
}

A& getA() {
    while (!initialized(a_obj)) {
        if (try_lock(a_mutex)) {
           a_isolated_tg.run_and_wait( { initialize(a_obj); });
           unlock(a_mutex);
        } else { a_isolated_tg.wait(); }
    }
    return a_obj;
}
B& getB() { /*same as getA, but using b_mutex, b_isolated_tg and b_obj*/ }

int main() {
    parallel_for( {
        B& b = getB();
    })
}

As far as I understand, workers performing tasks of the parallel_for in main will be able to help initializing B.
But would they be able to help initializing A, as it is required to initialize B?

Copy link
Contributor Author

@akukanov akukanov Jul 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the initialization of A is in its own isolation scope and is called only once in B::initialize - then no, threads that call getB will not be able to help with initializing A. It is prevented by the implementation mechanics: a task dispatcher that runs in an isolation scope can only take tasks with that scope's tag, not any other tasks.

If however getA would be called in the parallel loop of B::initialize, then threads working on that loop could possibly enter the isolation scope in getA and help.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just thought if it makes sense to have API in the future that allow sharing the same isolation for several task_groups?
In my understanding, in this example having a_isolated_tg and b_isolated_tg to share the same isolation tag will allow workers in main's parallel_for to participate in A initialization as well.

Copy link
Contributor Author

@akukanov akukanov Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sharing the same tag means having no isolation between layers, in this case between A::initialize and B::initialize. If that's the goal, the simplest thing is to use a regular task group for A.

I guess you might want to allow B::initialize to take tasks from A::initialize, but not vice versa - i.e., to have one-way isolation. The current implementation of isolated tasks simply does not allow to do that. For that to be possible, we would need something similar to the binding of task_group_contexts.

Updated: perhaps even something more complicated, because task_group_contexts form a tree, but with task groups it is not hard to build "isolation DAGs". Imagine in the example above there is an object C which lazy initialization also needs A. That means, A::initialize can be "nested" into both B::initialize and C::initialize at the same time.

akukanov and others added 2 commits July 12, 2025 02:25
@akukanov akukanov force-pushed the rfcs/isolated_task_group branch from a49c69f to 7cffbc5 Compare July 12, 2025 16:28
@akukanov akukanov requested review from kboyarinov and vossmjp July 29, 2025 14:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants