Merge lp:~mdavidsaver/epics-appdev/thread-pool into lp:~epics-documenters/epics-appdev/3.16
- thread-pool
- Merge into 3.16
Proposed by
Andrew Johnson
Status: | Merged |
---|---|
Merged at revision: | 54 |
Proposed branch: | lp:~mdavidsaver/epics-appdev/thread-pool |
Merge into: | lp:~epics-documenters/epics-appdev/3.16 |
Diff against target: |
303 lines (+293/-0) 1 file modified
tex/libCom.tex (+293/-0) |
To merge this branch: | bzr merge lp:~mdavidsaver/epics-appdev/thread-pool |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andrew Johnson | Approve | ||
mdavidsaver | Pending | ||
Review via email: mp+228549@code.launchpad.net |
This proposal supersedes a proposal from 2014-07-22.
Commit message
Description of the change
Resubmitting proposal to remove prerequisite branch.
To post a comment you must log in.
Revision history for this message
Andrew Johnson (anj) wrote : Posted in a previous version of this proposal | # |
review:
Needs Fixing
Revision history for this message
mdavidsaver (mdavidsaver) wrote : Posted in a previous version of this proposal | # |
I think I have made the requested changes.
review:
Needs Resubmitting
Revision history for this message
Andrew Johnson (anj) wrote : | # |
Would have been nice to have epicsThreadPool
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'tex/libCom.tex' |
2 | --- tex/libCom.tex 2014-04-22 19:15:31 +0000 |
3 | +++ tex/libCom.tex 2014-07-28 17:47:17 +0000 |
4 | @@ -1260,6 +1260,299 @@ |
5 | \index{macInstallMacros} |
6 | NOTE: The directory \textless{}base\textgreater{}/src/libCom/macLib contains two files \verb|macLibNOTES| and \verb|macLibREADME| that explain this library. |
7 | |
8 | +\section{epicsThreadPool} |
9 | + |
10 | +\index{epicsThreadPool.h} |
11 | + |
12 | +\verb|epicsThreadPool.h| implements general purpose threaded work queue. |
13 | +Pieces of work (jobs) are submitted to a queue which is shared by a group of worker threads. |
14 | +After jobs are placed on the queue they may be executed in any order. |
15 | + |
16 | +The thread pool library will never implicitly destroy pools or jobs. |
17 | +Such cleanup is always the responsibility of user code. |
18 | + |
19 | +\subsection{Configure a pool} |
20 | + |
21 | +An \verb|epicsThreadPool| instance can be obtained in two ways. The preferred |
22 | +method is the use an existing shared thread pool. Alternately, a new |
23 | +pool may be explicitly created. In both cases \verb|NULL| may be passed to use |
24 | +the configuration, or a non-default configuration may be prepared in the following way. |
25 | + |
26 | +\begin{verbatim} |
27 | +typedef struct { |
28 | + size_t initialThreads; |
29 | + size_t maxThreads; |
30 | + unsigned int workerStack; |
31 | + unsigned int workerPriority; |
32 | +} epicsThreadPoolConfig; |
33 | + |
34 | +void epicsThreadPoolConfigDefaults(epicsThreadPoolConfig *); |
35 | +\end{verbatim} |
36 | + |
37 | +\index{epicsThreadPoolConfig} |
38 | +\index{epicsThreadPoolConfigDefaults} |
39 | + |
40 | +Pool configuration should always be initialized with \verb|epicsThreadPoolConfigDefault()| |
41 | +before modification by user code. This will initialize configuration |
42 | +parameters to sensible defaults, which can then be overwritten as |
43 | +needed by user code. |
44 | + |
45 | +Note that no epicsThreadPool functions will ever retain a pointer |
46 | +to the user provided \verb|epicsThreadPoolConfig| instance. |
47 | + |
48 | +\begin{description} |
49 | +\item [{initialThreads}] A suggestion for the number of worker threads to start |
50 | +immediately when the pool is created. When this number is less than |
51 | +maxThreads additional workers may be started later as needed. Defaults to zero. |
52 | +\item [{maxThreads}] Upper limit on the number of worker threads in this |
53 | +pool. Defaults to number of CPU cores. |
54 | +\item [{workerStack}] Worker thread stack size. Defaults to size of \verb|epicsThreadStackSmall|. |
55 | +\item [{workerPriority}] Worker thread priority. Defaults to just above \verb|epicsThreadPriorityCAServerHigh| |
56 | +\end{description} |
57 | + |
58 | +These defaults are choosen to be suitable for CPU intensive background work by drivers. |
59 | + |
60 | +\subsection{Create a shared pool} |
61 | + |
62 | +\begin{verbatim} |
63 | +epicsThreadPool* epicsThreadPoolGetShared(epicsThreadPoolConfig *opts); |
64 | +void epicsThreadPoolReleaseShared(epicsThreadPool *pool); |
65 | +\end{verbatim} |
66 | + |
67 | +\index{epicsThreadPoolGetShared} |
68 | +\index{epicsThreadPoolReleaseShared} |
69 | + |
70 | +A shared thread pool is obtained by calling \verb|epicsThreadPoolGetShared()|. |
71 | +A global list of shared pools is examined. If an existing pool matches |
72 | +the requested configuration, then it is returned. Otherwise a new |
73 | +pool is created, added to the global list, then returned. \verb|epicsThreadPoolGetShared()| |
74 | +may return NULL in situations of memory exhaustion. |
75 | + |
76 | +Note that \verb|NULL| may be passed to use the default configuration. |
77 | + |
78 | +As for example: |
79 | + |
80 | +\begin{verbatim} |
81 | +void usercode() { |
82 | + epicsThreadPoolConfig myconf; |
83 | + epicsThreadPoolConfigDefault(&myconf); |
84 | + /* overwrite defaults if needed */ |
85 | + myconf.workerPriority = epicsThreadPriorityLow; |
86 | + ... = epicsThreadPoolGetShared(&myconf); |
87 | + |
88 | + /* or to use the defaults */ |
89 | + ... = epicsThreadPoolGetShared(NULL); |
90 | +} |
91 | +\end{verbatim} |
92 | + |
93 | +The user provided configuration may be altered to ensure that the |
94 | +maxThreads is greater than or equal to the number of threads the host |
95 | +system can run in parallel. In addition, when a existing shared pool |
96 | +is returned, the user supplied config is overwritten with the pool's |
97 | +actual config. |
98 | + |
99 | +If a thread pool will not be used further it must be released, which |
100 | +may cause it to be free'd when no other references exist. |
101 | +It is advisable to ensure that all queued |
102 | +jobs have completed as queued jobs may still run if the other references |
103 | +to the queue remain. |
104 | + |
105 | +When matching a requested configuration against the configuration |
106 | +of a existing shared pool, the following conditions must be meet |
107 | +for an existing shared queue to be used. |
108 | +\begin{itemize} |
109 | +\item workerPriority must match exactly. |
110 | +\item maxThreads and workerStack of the pool must be greater than or equal |
111 | +to the corresponding parameters of the request. |
112 | +\end{itemize} |
113 | + |
114 | +Note that the initialThreads option is ignored when requesting a shared pool. |
115 | + |
116 | +\subsection{Creating an exclusive pool} |
117 | + |
118 | +\begin{verbatim} |
119 | +epicsThreadPool* epicsThreadPoolCreate(epicsThreadPoolConfig *opts); |
120 | +void epicsThreadPoolDestroy(epicsThreadPool *); |
121 | +\end{verbatim} |
122 | + |
123 | + |
124 | +\index{epicsThreadPoolCreate} |
125 | +\index{epicsThreadPoolDestory} |
126 | + |
127 | +Unshared thread pools are created/destroyed in a similar fashion. |
128 | + |
129 | +Note that \verb|NULL| may be passed to use the default configuration. |
130 | + |
131 | +When a pool is destroyed it will freeze the queue to prevent new jobs |
132 | +from being added, then block until any previously queued jobs complete. |
133 | + |
134 | +\subsection{Basic jobs operations} |
135 | + |
136 | +\begin{verbatim} |
137 | +/* job modes */ |
138 | +typedef enum { |
139 | + epicsJobModeRun, |
140 | + epicsJobModeCleanup |
141 | +} epicsJobMode; |
142 | +typedef void (*epicsJobFunction)(void* arg, epicsJobMode mode); |
143 | + |
144 | +#define EPICSJOB_SELF ... |
145 | +epicsJob* epicsJobCreate(epicsThreadPool* pool, |
146 | + epicsJobFunction cb, |
147 | + void* user); |
148 | +void epicsJobDestroy(epicsJob*); |
149 | +int epicsJobQueue(epicsJob*); |
150 | +int epicsJobUnqueue(epicsJob*); |
151 | +\end{verbatim} |
152 | + |
153 | + |
154 | +\index{epicsJobMode} |
155 | +\index{epicsJobModeRun} |
156 | +\index{epicsJobModeCleanup} |
157 | +\index{EPICSJOB_SELF@EPICSJOB\_SELF} |
158 | +\index{epicsJobFunction} |
159 | +\index{epicsJobCreate} |
160 | +\index{epicsJobDestroy} |
161 | +\index{epicsJobQueue} |
162 | +\index{epicsJobUnqueue} |
163 | + |
164 | +The basic lifecycle of a job is to be created, queued some number of |
165 | +times, then destroyed. As with an \verb|epicsThreadPool*|, the lifecycle of |
166 | +an \verb|epicsJob*| is completely controlled by user code. Jobs will never |
167 | +be implicitly destroyed. When created, a pool, work function, and |
168 | +user argument are specified. The special user argument \verb|EPICSJOB_SELF| |
169 | +may be passed to set the user argument to the \verb|epicsJob*| returned |
170 | +by \verb|epicsJobCreate()|. |
171 | + |
172 | +A job may be queued at any time. The queuing process can fail (return |
173 | +non-zero) if: |
174 | + |
175 | +\begin{itemize} |
176 | +\item The job is not currently associated with a pool. |
177 | +\item The associated pool is not allowing jobs to be queued. |
178 | +\end{itemize} |
179 | + |
180 | +A job may be unqueued with \verb|epicsJobUnqueue()|. This function will return |
181 | +1 if the job was successful removed from the queue, or 0 if this is |
182 | +not possible. A job can not be unqueued if it was not queued to begin |
183 | +with, is running, or has completed. |
184 | + |
185 | +A job may also be destroyed at any time, including while its job function |
186 | +is running. In this case destruction is deferred until the job function returns. |
187 | + |
188 | +If a thread pool is destroyed before all of its jobs are destroyed, |
189 | +then each job function is called one final time with the mode \verb|epicsJobModeCleanup| |
190 | +to provide an opportunity to call \verb|epicsJobDestroy|. |
191 | +If this is not done, then the job is disassociated from the pool. |
192 | +It is always the responsibility of user code to explicitly call \verb|epicsJobDestroy|. |
193 | + |
194 | +\subsection{Writing job functions} |
195 | + |
196 | +\begin{verbatim} |
197 | +typedef struct { |
198 | + epicsJob* job; |
199 | + ... |
200 | +} myWork; |
201 | + |
202 | +static |
203 | +void myfunction(void* arg, |
204 | + epicsJobMode mode) |
205 | +{ |
206 | + myWork *priv=arg; |
207 | + if(mode==epicsJobModeCleanup) { |
208 | + epicsJobDestroy(priv->job); |
209 | + free(priv); |
210 | + return; |
211 | + } |
212 | + /* do normal work */ |
213 | +} |
214 | + |
215 | +static |
216 | +void somewhere(...) |
217 | +{ |
218 | + epicsThreadPool *pool; |
219 | + myWork *priv = ...; /* allocate somehow */ |
220 | + pool = epicsThreadPoolCreate(NULL); |
221 | + assert(pool!=NULL && priv!=NULL); |
222 | + priv->job = epicsJobCreate(pool, &myfunction, priv); |
223 | + assert(priv->job!=NULL); |
224 | + epicsJobQueue(priv->job); |
225 | + epicsThreadPoolDestroy(pool); |
226 | +} |
227 | +\end{verbatim} |
228 | + |
229 | + |
230 | +Some restrictions apply to job functions. Only the following epicsThreadPool |
231 | +functions may be called from a job function. When using a shared pool, |
232 | +no modification should be made to the worker threads (eg. don't change |
233 | +priority). If such modifications are needed, then an exclusively owned |
234 | +pool should be created. |
235 | + |
236 | +\begin{itemize} |
237 | +\item \verb|epicsJobQueue()| |
238 | +\item \verb|epicsJobUnqueue()| |
239 | +\item \verb|epicsJobCreate()| |
240 | +\item \verb|epicsJobDestroy()| |
241 | +\end{itemize} |
242 | + |
243 | +No internal locks are held while a job function runs. |
244 | +So a job function may lock arbitrary mutexes without causing a deadlock. |
245 | +When in a job function, care must be taken to only call those function explicitly |
246 | +marked as safe to call from a running job function as these functions |
247 | +are written to avoid corrupting the internal state of the pool. |
248 | + |
249 | + |
250 | +\subsection{Moving jobs between pools} |
251 | + |
252 | +It may be desirable to move epicsJob instances between pools, or to |
253 | +have jobs not associated with any pool. This is supported with the |
254 | +caveat that the \verb|epicsJobMove()| function must not run concurrently |
255 | +with any other epicsThreadPool functions operating on the same job. |
256 | +In addition to functions operating explicitly on this job, this also includes |
257 | +\verb|epicsThreadPoolDestroy()| |
258 | + |
259 | +A job may be created with no pool association by passing NULL to \verb|epicsJobCreate()| |
260 | +instead of an \verb|epicsThreadPool*| pointer. The association can be changed |
261 | +at runtime with the \verb|epicsJobMove()| function. |
262 | + |
263 | + |
264 | +\subsection{Pool control} |
265 | + |
266 | +\begin{verbatim} |
267 | +typedef enum { |
268 | + epicsThreadPoolQueueAdd, |
269 | + epicsThreadPoolQueueRun |
270 | +} epicsThreadPoolOption; |
271 | +void epicsThreadPoolControl(epicsThreadPool* pool, |
272 | + epicsThreadPoolQueueOption opt, |
273 | + unsigned int val); |
274 | +int epicsThreadPoolWait(epicsThreadPool* pool, double timeout); |
275 | +\end{verbatim} |
276 | + |
277 | + |
278 | +\index{epicsThreadPoolQueueAdd} |
279 | +\index{epicsThreadPoolQueueRun} |
280 | +\index{epicsThreadPoolControl} |
281 | +\index{epicsThreadPoolWait} |
282 | + |
283 | +It may be useful to manipulate the queue of a thread pool at runtime |
284 | +(eg. unittests). Currently defined options are: |
285 | +\begin{description} |
286 | +\item [{epicsThreadPoolQueueAdd}] Set to 0 to prevent additional jobs from |
287 | +being queued. Set to 1 to resume normal operation. |
288 | +\item [{epicsThreadPoolQueueRun}] Set to 0 to prevents workers from taking |
289 | +jobs from the queue. Set to 1 for normal operation. |
290 | +\end{description} |
291 | +These options may be combined with \verb|epicsThreadPoolWait()| to block |
292 | +until the queue is empty. |
293 | + |
294 | +\verb|epicsThreadPoolWait()| accepts a timeout in seconds. A timeout value |
295 | +less than 0.0 never times out, a value of exactly 0.0 will not block, |
296 | +and values greater than 0.0 will block for the requested time at most. |
297 | + |
298 | +This function returns 0 if the queue was emptied and no jobs are running at any moment during the timeout period, |
299 | +or ETIMEOUT if the timeout period ellapses and jobs remain in the queue or are running. |
300 | + |
301 | \section{misc} |
302 | |
303 | \subsection{aToIPAddr} |
This is also a review of the API to the thread-pool code, since it was easier to understand that by reading the documentation for it here.
Note that I'm trying out Launchpad's new "inline diff comments" feature, I have no idea how well that will work.