Beatmup
parallelism.h
Go to the documentation of this file.
1 /*
2  Beatmup image and signal processing library
3  Copyright (C) 2019, lnstadrum
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation, either version 3 of the License, or
8  (at your option) any later version.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #pragma once
20 #include "basic_types.h"
21 #include <condition_variable>
22 #include <thread>
23 
24 namespace Beatmup {
25  /** \page ProgrammingModel
26 
27  \section secTasks Tasks
28  A task (instance of AbstractTask) is an isolated elementary processing operation. It can run in parallel in multiple threads for speed, on
29  CPU and/or GPU.
30 
31  \subsection ssecStruct Task structure
32  The tasks are not intended to contain any user code. If you need a specific processing function to be implemented, much likely you need to
33  subclass AbstractTask.
34 
35  In short, an AbstractTask has three main phases:
36  - a phase "before processing" run in a single thread to perform the necessary prepration,
37  - the main processing phase run in as many threads as possible, actually performing computatively intensive operations,
38  - a phase "after processing" to clean up.
39  The number of threads running the main phase is minimum of two values: the total number of threads in the thread pool the task is submitted
40  to, and the maximum allowed number of threads given by the task itself.
41 
42  A detailed description is available in AbstractTask documentation.
43 
44  \subsection ssecExceptions Exceptions handling
45  The tasks can throw exceptions. If this happens, the thread pool that is in charge of running the failing task stores the exception internally
46  and rethows it back to the application code, when the latter calls Context::check() function.
47 
48  It is recommended to call Context::check() in a timely manner to process exceptions produced by tasks.
49 
50  \subsection ssecJobs Jobs
51  When a task is submitted to a thread pool using Context::submitTask() function, it produces a job. A job is just a ticket number in
52  the queue of the corresponding thread pool. Context functions take it to check the task status or cancel it. In this way, the same task can
53  be submitted several times to the same thread pool, producing several different jobs, and will then be run several times.
54 
55  If the asynchronous behavior is not needed, a task can be run in a blocking call to Context::performTask(). This hides the mechanics of jobs
56  from the user and just runs a given task.
57 
58  \subsection ssecPersistent Persistent tasks
59  Usually, once a task is completed, it is dropped from the thread pool queue. This is referred to as the "normal mode", differently to
60  the "persistent mode" in which the task is getting repeated until it decides to quit itself. This is convenient for rendering and playback
61  tasks consuming signals from external sources, that are still run in a granular fashion (by frame or signal buffer) but persist until
62  the data is fully consumed.
63 
64  Context::submitPersistentTask() produces a persistent job for a specific task.
65  */
66 
67  typedef unsigned char PoolIndex; //!< number of tread pools or a pool index
68  typedef unsigned char ThreadIndex; //!< number of threads / thread index
69  typedef int Job;
70 
71  static const ThreadIndex MAX_THREAD_INDEX = 255; //!< maximum possible thread index value
72 
73  class GraphicPipeline;
74  class TaskThread;
75 
76  /**
77  * Task: an operation that can be executed by multiple threads in parallel.
78  * Before executing the task, ThreadPool queries the task concerning
79  * - the device (CPU or GPU) the task will be run on, by calling getUsedDevices(),
80  * - the maximum number of threads the task may be run in, by calling getMaxThreads().
81  * By default the tasks run on CPU in a single thread. This behavior can be overridden in subclasses.
82  * The task has three main stages:
83  * - beforeProcessing() to prepare the operation. It is run in a single thread.
84  * - processOnGPU() and process() to execute the operation.
85  * - processOnGPU() is called from a single thread having access to the GPU.
86  * - process() is potentially called from multiple threads to run the operation on CPU.
87  * The number of theads is not greatr than what was returned by getMaxThreads().
88  * - afterProcessing() to tear down the operation. It is run in a single thread as well.
89  */
90  class AbstractTask : public Object {
91  public:
92  /**
93  Specifies which device (CPU and/or GPU) is used to run the task.
94  */
95  enum class TaskDeviceRequirement {
96  CPU_ONLY, //!< this task does not use GPU
97  GPU_OR_CPU, //!< this task uses GPU if it is available, but CPU fallback is possible
98  GPU_ONLY //!< this task requires GPU, otherwise it cannot run
99  };
100 
101  /**
102  Instruction called before the task is executed.
103  \param threadCount Number of threads used to perform the task
104  \param target Device used to perform the task
105  \param gpu A graphic pipeline instance; may be null.
106  */
107  virtual void beforeProcessing(ThreadIndex threadCount, ProcessingTarget target, GraphicPipeline* gpu);
108 
109  /**
110  Instruction called after the task is executed.
111  \param threadCount Number of threads used to perform the task
112  \param gpu GPU to be used to execute the task; may be null.
113  \param aborted `true` if the task was aborted
114  */
115  virtual void afterProcessing(ThreadIndex threadCount, GraphicPipeline* gpu, bool aborted);
116 
117  /**
118  Executes the task on CPU within a given thread. Generally called by multiple threads.
119  \param thread associated task execution context
120  \returns `true` if the execution is finished correctly, `false` otherwise
121  */
122  virtual bool process(TaskThread& thread) = 0;
123 
124  /**
125  Executes the task on GPU.
126  \param gpu graphic pipeline instance
127  \param thread associated task execution context
128  \returns `true` if the execution is finished correctly, `false` otherwise
129  */
130  virtual bool processOnGPU(GraphicPipeline& gpu, TaskThread& thread);
131 
132  /**
133  Communicates devices (CPU and/or GPU) the task is run on.
134  */
135  virtual TaskDeviceRequirement getUsedDevices() const;
136 
137  /**
138  Gives the upper limint on the number of threads the task may be performed by.
139  The actual number of threads running a specific task may be less or equal to the returned value,
140  depending on the number of workers in ThreadPool running the task.
141  */
142  virtual ThreadIndex getMaxThreads() const;
143 
144  /**
145  Valid thread count from a given integer value
146  */
147  static ThreadIndex validThreadCount(int number);
148  };
149 
150 
151  /**
152  Thread executing tasks
153  */
154  class TaskThread {
155  TaskThread(const TaskThread&) = delete;
156  protected:
157  ThreadIndex index; //!< current thread index
158 
160 
161  public:
162  /**
163  \return number of this thread.
164  */
165  inline ThreadIndex currentThread() const {
166  return index;
167  }
168 
169  /**
170  \return `true` if this thread is the managing thread
171  */
172  inline bool isManaging() const {
173  return index == 0;
174  }
175 
176  /**
177  \return overall number of threads working on the current task.
178  */
179  virtual ThreadIndex numThreads() const = 0;
180 
181  /**
182  Returns `true` if the task is asked to stop from outside.
183  */
184  virtual bool isTaskAborted() const = 0;
185 
186  /**
187  Blocks until all the other threads running the same task reach the same point.
188  "The same point" is the same number of calls to synchronize().
189  This function does not throw any exception to be caught in the task code. However, if a thread running the task has failed, this function throws
190  an exception the thread pool takes care of. This ensures a valid thread pool state and avoids dead locks when a task fails to proceed.
191  */
192  virtual void synchronize() = 0;
193  };
194 
195 }
Task: an operation that can be executed by multiple threads in parallel.
Definition: parallelism.h:90
static ThreadIndex validThreadCount(int number)
Valid thread count from a given integer value.
Definition: parallelism.cpp:45
TaskDeviceRequirement
Specifies which device (CPU and/or GPU) is used to run the task.
Definition: parallelism.h:95
@ GPU_OR_CPU
this task uses GPU if it is available, but CPU fallback is possible
@ CPU_ONLY
this task does not use GPU
@ GPU_ONLY
this task requires GPU, otherwise it cannot run
virtual TaskDeviceRequirement getUsedDevices() const
Communicates devices (CPU and/or GPU) the task is run on.
Definition: parallelism.cpp:54
virtual void beforeProcessing(ThreadIndex threadCount, ProcessingTarget target, GraphicPipeline *gpu)
Instruction called before the task is executed.
Definition: parallelism.cpp:24
virtual bool processOnGPU(GraphicPipeline &gpu, TaskThread &thread)
Executes the task on GPU.
Definition: parallelism.cpp:34
virtual ThreadIndex getMaxThreads() const
Gives the upper limint on the number of threads the task may be performed by.
Definition: parallelism.cpp:40
virtual void afterProcessing(ThreadIndex threadCount, GraphicPipeline *gpu, bool aborted)
Instruction called after the task is executed.
Definition: parallelism.cpp:29
virtual bool process(TaskThread &thread)=0
Executes the task on CPU within a given thread.
Internal low-level GPU control API.
Definition: pipeline.h:33
Beatmup object base class
Definition: basic_types.h:67
Thread executing tasks.
Definition: parallelism.h:154
ThreadIndex index
current thread index
Definition: parallelism.h:157
virtual ThreadIndex numThreads() const =0
bool isManaging() const
Definition: parallelism.h:172
TaskThread(const TaskThread &)=delete
virtual void synchronize()=0
Blocks until all the other threads running the same task reach the same point.
ThreadIndex currentThread() const
Definition: parallelism.h:165
virtual bool isTaskAborted() const =0
Returns true if the task is asked to stop from outside.
TaskThread(ThreadIndex index)
Definition: parallelism.h:159
unsigned char ThreadIndex
number of threads / thread index
Definition: parallelism.h:68
static const ThreadIndex MAX_THREAD_INDEX
maximum possible thread index value
Definition: parallelism.h:71
unsigned char PoolIndex
number of tread pools or a pool index
Definition: parallelism.h:67
int Job
Definition: parallelism.h:69
ProcessingTarget
Definition: basic_types.h:55