Name

task Class — Base class for tasks.

Synopsis

#include "tbb/task.h"

class task;

Description

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.

Warning

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

owner

The worker thread that is currently in charge of the task.

parent

Either NULL or the parent/continuation task that allocated this task.

depth

The depth of the task in the task tree.

refcount

The number of tasks that have this as their parent. Increments and decrements of refcount must always be atomic.

Tip

The copy constructor and assignment operators for task are not accessible. This prevents the accidental copying of a task, which would be ill-defined and would corrupt internal data structures.

Notation

Some member descriptions illustrate the effects of running the methods by diagrams such as Figure 9-6.

Example effect diagram

Figure 9-6. Example effect diagram

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.

Members

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, const proxy1& p );
	void operator delete( void* task, const proxy1& p );
	void *operator new( size_t bytes, const proxy2& p );
	void operator delete( void* task, const proxy2& p );
	void *operator new( size_t bytes, const proxy3& p );
	void operator delete( void* task, const proxy3& p );
	void *operator new( size_t bytes, proxy4& p );
	void operator delete( void* task, proxy4& p );

Task derivation

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.

Processing of execute( )

When the scheduler decides that a thread should begin executing a task, it performs the following steps:

  1. It invokes execute() and waits for it to return.

  2. 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.

  3. It calls the task’s destructor.

  4. It frees the memory used by the task.

  5. 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.)

Task allocation

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.

Effect of task::allocate_root( )

Figure 9-7. Effect of task::allocate_root( )

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.

Effect of task::allocate_continuation( )

Figure 9-8. Effect of task::allocate_continuation( )

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.

Effect of task::allocate_child( )

Figure 9-9. Effect of task::allocate_child( )

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.

Effect of task::allocate_additional_child_of(parent)

Figure 9-10. Effect of task::allocate_additional_child_of(parent)

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.

Explicit task destruction

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.

Effect of destroy(victim)

Figure 9-11. Effect of destroy(victim)

Recycling tasks

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.

Task depth

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.

Effect of set_depth

Figure 9-12. Effect of set_depth

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.

Effect of add_to-depth(delta)

Figure 9-13. Effect of add_to-depth(delta)

Synchronization

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.

Warning

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=n+1, where n 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.

Effect of wait_for_all

Figure 9-14. Effect of wait_for_all

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.

Effect of spawn_and_wait_for_all

Figure 9-15. Effect of spawn_and_wait_for_all

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.

Task context

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.

Task debugging

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.

Warning

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

allocated

The task is freshly allocated or recycled.

ready

The task is in the ready pool, or is in the process of being transferred to or from the pool.

executing

The task is running, and it will be destroyed after its execute method returns.

freed

The task is on the internal free list, or is in the process of being transferred to or from the list.

reexecute

The task is running, and will be respawned after its execute method returns.

int ref_count() const

Returns: the value of the attribute refcount.

Tip

This method is intended for debugging only. Its behavior or performance may change in future implementations.

Typical task::state( ) transitions

Figure 9-16. Typical task::state( ) transitions

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.213.126