This class is the base class for tasks. Programmers are expected to derive classes from it and override the virtual method task*task::execute()
.
Each instance of task
has associated attributes, which are described in Table 9-3. Although
they are not directly visible, they must be understood to fully grasp how task objects are used.
Always allocate memory for task objects using the special overloaded new
operators provided by the library. Otherwise, results are undefined. Destruction of a task is normally implicit.
Table 9-3. Task attributes
Attribute |
Description |
---|---|
|
The worker thread that is currently in charge of the task. |
|
Either |
| |
|
The number of tasks that have |
Some member descriptions illustrate the effects of running the methods by diagrams such as Figure 9-6.
Conventions in the diagram, such as Figure 9-6, are as follows:
Each task’s state is shown as a box divided into parent, depth
, and refcount
subboxes.
The arrow denotes the transition from the old state to the new state. Often, two objects take on a new state: the object that invokes the method (this)
, and the object returned by the method (the result).
Gray denotes a state that is ignored. Sometimes an ignored state is simply left blank.
Black denotes a state that is read.
Bold black with a thick box outline denotes a state that is written.
In the following description, the types proxy1…proxy4
are internal types. Methods returning such types should be used only in conjunction with the special overloaded new
operators.
namespace tbb { class task { protected: task(); public: virtual ~task() {} virtualtask* execute() = 0; // task allocation and destruction static proxy1 allocate_root(); proxy2 allocate_continuation(); proxy3 allocate_child(); proxy4 allocate_additional_child_of( task& t ); // Explicit task destruction void destroy( task& victim ); // Recycling void recycle_as_continuation(); void recycle_as_child_of( task& parent ); void recycle_to_reexecute(); // task depth typedef implementation-defined-signed-integral-type depth_type; depth_type depth() const; void set_depth( depth_type new_depth ); void add_to_depth( int delta ); // Synchronization void set_ref_count( int count ); void wait_for_all(); void spawn( task& child ); void spawn( task_list& list ); void spawn_and_wait_for_all( task& child ); void spawn_and_wait_for_all( task_list& list ); static void spawn_root_and_wait( task& root ); static void spawn_root_and_wait( task_list& root ); // task context static task& self(); task* parent() const; bool is_stolen_task() const; // task debugging enum state_type { executing, reexecute, ready, allocated, freed }; int ref_count() const; state_type state() const; }; } // namespace tbb void *operator new( size_t bytes, constproxy1
& p ); void operator delete( void* task, constproxy1
& p ); void *operator new( size_t bytes, constproxy2
& p ); void operator delete( void* task, constproxy2
& p ); void *operator new( size_t bytes, constproxy3
& p ); void operator delete( void* task, constproxy3
& p ); void *operator new( size_t bytes,proxy4
& p ); void operator delete( void* task,proxy4
& p );
task
is an abstract base class. You must override the task::execute
method. It should perform the necessary actions for running the task and then return the next task to execute, or return NULL
if the scheduler should choose the next task to execute. Typically, if non-NULL
, the returned task is one of the children of this
. Unless one of the recycle/reschedule methods is called while the execute
method is running, the this
object will be implicitly destroyed after execute
returns.
The derived class should override the virtual destructor if necessary to release resources allocated by the constructor.
When the scheduler decides that a thread should begin executing a task, it performs the following steps:
It invokes execute()
and waits for it to return.
If the task has not been marked for recycling by one of the recycle_*
methods, it checks the task’s parent
. If the parent
is non-NULL
, it atomically decrements parent-> refcount
, and if it becomes zero, it puts the parent
into the ready pool.
It calls the task’s destructor.
It frees the memory used by the task.
If the code has reached this point, the task has been marked for recycling. If it was marked by recycle_to_reexecute
, the scheduler puts the task back into the ready pool. Otherwise, the task was marked by recycle_as_child
or recycle_as_continuation
. (See the section “Recycling tasks,” later in this chapter.)
Always allocate memory for task objects using one of the special overloaded new
operators. The allocation methods do not construct the task. Instead, they return a proxy object that can be used as an argument to an overloaded version of new
provided by the library.
In general, the allocation methods must be called before any of the tasks allocated are spawned. The exception to this rule is allocate_additional_child_of(t)
, which can be called even if task t
is already running. The proxy types are defined by the implementation. Because these methods are used idiomatically, the headings in the subsection show the idiom, not the declaration. The argument this
is typically implicit, but it is shown explicitly in the headings to distinguish instance methods from static methods.
new(task::allocate_root())T
Effects: allocates a task of type T
with a depth that is one greater than the depth of the innermost task currently being executed by the current native thread. Figure 9-7 summarizes the state transition.
Use the spawn_root_and_wait
method to execute the task.
new(this.allocate_continuation())T
Effects: allocates and constructs a task of type T
at the same depth as this
, and transfers the parent from this
to the new task. No reference counts change. Figure 9-8 summarizes the state transition.
new(this.allocate_child())T
Effects: allocates a task with a depth that is one greater than this
, with this
as its parent. Figure 9-9 summarizes the state transition.
If you are using explicit continuation passing, call the allocation method from the continuation, not the parent, so that the parent
member is set correctly. The task this
must be owned by the current thread.
If the number of tasks is not a small fixed number, consider building a task_list
of the children first and spawning them with a single call to task::spawn
. If a task must spawn some children before all are constructed, it should use task::allocate_ additional_child_of(*this)
instead because that method atomically increments refcount
so that the additional child is properly counted. However, if using this procedure, the task must protect against premature zeroing of refcount
by using a blocking-style task pattern (as was shown in Figure 9-2).
new(this.task::allocate_additional_child_of(parent))
Effects: allocates a task as a child of another task, parent
. The result becomes a child of parent
, not this
. The parent may be owned by another thread and may already be running or have other children running. The task object this
must be owned by the current thread, and the result has the same owner as the current thread, not the parent. Figure 9-10 summarizes the state transition.
Because parent
may already have running children, the increment of parent.refcount
is thread-safe (unlike the other allocation methods, where the increment is not thread-safe). When adding a child to a parent with other children running, it is up to the programmer to ensure that the parent’s refcount
does not prematurely reach 0
and trigger execution of the parent before the child is added.
Usually, a task is automatically destroyed by the scheduler after its execute
method returns. But sometimes task objects are used idiomatically (e.g., for reference counting) without ever running execute
. Such tasks should be disposed of with the destroy
method.
void destroy(task&victim)
Requirements: the reference count of victim
should be 0
. This requirement is checked in the debug version of the library. The calling thread must own this
.
Effects: calls the victim object’s destructor and deallocates its memory. If this
has a non-NULL parent
, the method atomically decrements parent->refcount
. The parent is not put into the ready pool if parent->refcount
becomes 0
. Figure 9-11 summarizes the state transition.
The implicit argument this
is used internally, but it is not visibly affected. A task is allowed to destroy itself, so this->destroy(*this)
is permitted unless the task has been spawned but has not yet completed its execute
method.
It is often more efficient to recycle a task object than it is to reallocate one from scratch. Often, the parent can be reused as the continuation of one of its children.
void recycle_as_continuation()
Requirements: must be called while execute
is running.
The refcount
for the recycled task should be set to the number of current children of the continuation task.
The caller must guarantee that the task’s refcount
does not become 0
until after execute
returns. If this is not possible, use the method recycle_as_safe_continuation()
instead, and set refcount
to one greater than the number of current children of the continuation task.
Effects: causes this
not to be destroyed when its execute method returns.
void recycle_as_safe_continuation()
Requirements: must be called while execute
is running.
The refcount
for the recycled task should be set to one greater than the number of children of the continuation task. The additional one represents the task to be recycled.
Effects: causes this
not to be destroyed when its execute
method returns.
This method avoids race conditions that can arise from using the method recycle_as_continuation
. The race occurs when all of the following take place:
The task’s execute
method recycles this
as a continuation.
The continuation creates children.
All the children finish before the original task’s execute
method completes so that the continuation executes before the scheduler is done running this
. The out-come is a corrupted scheduler.
The recycle_as_safe_continuation
method avoids this race condition because the additional one in the refcount
prevents the continuation from executing until the task completes.
void recycle_as_child_of(task&parent)
Requirements: must be called while execute
is running.
Effects: causes this
to become a child of parent
and not be destroyed when execute
returns.
void recycle_to_reexecute()
Requirements: must be called while execute
is running. execute must return a pointer to another task.
Effects: causes this
to be automatically spawned after execute
returns.
For general fork-join parallelism, there is no need to explicitly set the depth of a task. However, in specialized task patterns that do not follow the fork-join pattern, it may be useful to explicitly set or adjust the depth of a task.
depth_type
The type task::depth_type
is an implementation-defined, signed integral type.
depth_typedepth()const
Returns: current depth
attribute for the task.
void set_depth(depth_typenew_depth)
Requirements: the value new_depth
must be non-negative.
Effects: sets the depth
attribute of the task to new_depth
. Figure 9-12 shows the effects.
void add_to_depth(int delta)
Requirements: the task must not be in the ready pool. The sum depth+delta
must be non-negative.
Effects: sets the depth
attribute of the task to depth+delta
. Figure 9-13 illustrates the effect. The update is not atomic.
Spawning a task
either causes the calling thread to invoke task.execute()
, or causes task to be put into the ready pool. Any thread participating in task scheduling may then acquire the task and invoke task.execute()
. The calls that spawn tasks come in two forms:
Calls that spawn a single task.
Calls that spawn multiple task objects specified by a task_list
and then clear task_list
.
The calls distinguish between spawning root tasks and child tasks. A root task is one that is created using the allocate_root
method.
A task should not spawn any child until it has called set_ref_count
to indicate both the number of children and whether it intends to use one of the wait_for_all
methods.
void set_ref_count(int count)
Requirements: count
must be greater than 0
. If the intent is to subsequently spawn n
children and wait, count
should be n
+1
. Otherwise, count should be n
.
Effects: sets the refcount
attribute to count
.
void wait_for_all()
Requirements: refcount=
, where n
+1n
is the number of children still running.
Effects: executes tasks in the ready pool until refcount
is 1
. Afterward, sets refcount
to 0
. Figure 9-14 summarizes the state transitions.
void spawn(task&child)
Requirements: child.refcount
must be greater than 0
. The calling thread must own this
and child
.
Effects: puts the task into the ready pool and immediately returns. The this
task that does the spawning must be owned by the caller thread. A task may spawn itself if it is owned by the caller thread. If no convenient task owned by the current thread is handy, use task::self().spawn(task)
to spawn the child.
The parent must call set_ref_count
before spawning any child tasks because once the child tasks are going, their completion will cause refcount
to be decremented asynchronously. The debug version of the library detects when a required call to set_ref_count
is not made, or is made too late.
void spawn(task_list&list)
Requirements: for each task in list
, refcount
must be greater than 0
. The calling thread must own the task invoking the method and each task in list
. Each task in list
must have the same value for its depth
attribute.
Effects: equivalent to executing spawn
on each task in list
and clearing list, but more efficient. If list
is empty, there is no effect.
void spawn_and_wait_for_all(task&child)
Requirements: any other children of this
must already be spawned. The task child
must have a non-NULL parent
attribute. There must be a chain of parent links from the child to the calling task. Typically, this chain contains a single link. That is, child
is typically an immediate child of this
.
Effects: similar to {spawn(task); wait_for_all();}
, but often more efficient. Further-more, it guarantees that task is executed by the current thread. This constraint can sometimes simplify synchronization. Figure 9-15 illustrates the state transitions.
void spawn_and_wait_for_all(task_list&list)
Effects: similar to {spawn(list); wait_for_all();}
, but often more efficient.
static void spawn_root_and_wait(task&root)
Requirements: the memory for task root
must have been allocated by task::allocate_ root()
. The calling thread must own root
.
Effects: sets the parent
attribute of root
to an undefined value and executes root
. Destroys root
afterward unless it was recycled.
static void spawn_root_and_wait(task_list&root_list)
Requirements: each task object in root_list
must meet the requirements for the parameter root of spawn_root_and_wait()
.
Effects: for each task object t
in root_list
, performs spawn_root_and_wait(t)
, possibly in parallel.
These methods expose relationships among task objects, and between task objects and the underlying physical threads:
static task& self()
Returns: reference to the innermost task that the calling thread is executing.
task* parent() const
Returns: value of the parent
attribute. The result is an undefined value if the task was allocated by allocate_root
and is currently running under control of spawn_root_and_ wait
.
bool is_stolen_task() const
Requirements: the attribute parent
must be non-NULL
and this.execute()
must be running. The calling task must not have been allocated with allocate_root
.
Returns: true
if the attribute owner
of this is unequal to the owner
of parent
.
Methods in this subsection are useful for debugging. They may change in future implementations.
state_type state() const
Returns: current state of the task. Table 9-4 describes valid states. Any other value is the result of memory corruption, such as caused by using a task whose memory has been deallocated. Figure 9-16 summarizes possible state transitions for a task.
This method is intended for debugging only. Its behavior or performance may change in future implementations. The definition of task::state_type
may also change in future implementations. The information in this section is provided because it can be useful for diagnosing problems during debugging.
Table 9-4. Values returned by task::state()
Value |
Description |
---|---|
|
The task is freshly allocated or recycled. |
|
The task is in the ready pool, or is in the process of being transferred to or from the pool. |
|
The task is running, and it will be destroyed after its |
|
The task is on the internal free list, or is in the process of being transferred to or from the list. |
|
The task is running, and will be respawned after its |
int ref_count() const
Returns: the value of the attribute refcount
.
18.216.213.126