1 | # Userspace Threading |
---|
2 | |
---|
3 | Note: priorities are not yet fully supported. |
---|
4 | |
---|
5 | # Threadpools |
---|
6 | |
---|
7 | The function `createThreadPool()` is used to create a new threadpool: |
---|
8 | |
---|
9 | threadpool pool = createThreadPool(int nthreads); |
---|
10 | |
---|
11 | The `nthreads` arguments defines how many worker threads the threadpool |
---|
12 | should have (and thus the maximum level of parallelism that the threadpool |
---|
13 | allows for). The value zero for `nthreads` is special; it will not actually |
---|
14 | create any separate threads, but create a pseudo threadpool that is |
---|
15 | subordinate to the current thread. It is useful for testing and debugging |
---|
16 | and can also be used to emulate user-space threads in single-threaded |
---|
17 | Singular. |
---|
18 | |
---|
19 | A threadpool can be shut down with the `closeThreadPool()` function: |
---|
20 | |
---|
21 | int remaining = closeThreadPool(threadpool pool[, int wait]); |
---|
22 | |
---|
23 | The `pool` argument is the threadpool that is to be shutdown. The pool |
---|
24 | will be shutdown immediately after all threads finish executing their |
---|
25 | current jobs. If the optional argument `wait` is non-zero, |
---|
26 | `closeThreadPool()` will wait with shutting down the pool until there |
---|
27 | are no remaining jobs that can be executed anymore, either because there |
---|
28 | are none left or because all remaining jobs are not ready. |
---|
29 | |
---|
30 | It returns an integer that describes the remaining number of jobs |
---|
31 | remaining in the threadpool. |
---|
32 | |
---|
33 | The current threadpool can be set and queried via the |
---|
34 | `setCurrentThreadPool()` and `currentThreadPool()` functions: |
---|
35 | |
---|
36 | setCurrentThreadPool(threadpool pool); |
---|
37 | threadpool pool = currentThreadPool(); |
---|
38 | |
---|
39 | Setting the current threadpool allows the threadpool argument for |
---|
40 | the `startJob()` and `scheduleJob()` functions to be omitted (see |
---|
41 | below). While executing a job, the current threadpool is automatically |
---|
42 | set without requiring external action. |
---|
43 | |
---|
44 | # Threadpool Initialization |
---|
45 | |
---|
46 | Threadpools can be initialized with any of the following functions that |
---|
47 | requests them to (respectively) load a library, execute a quoted |
---|
48 | expression, execute the contents of a file, execute the contents of a |
---|
49 | string, or call a function: |
---|
50 | |
---|
51 | threadPoolsLoadLib(threadpool pool, string lib); |
---|
52 | threadPoolExec(threadpool pool, def quote_expr); |
---|
53 | threadPoolExecFile(threadpool pool, string file); |
---|
54 | threadPoolExecString(threadpool pool, string str); |
---|
55 | threadPoolExecFunc(threadpool pool, string funcname); |
---|
56 | |
---|
57 | For a threadpool, this initialization request is passed on to all |
---|
58 | threads in the pool. Initialization happens after any currently running |
---|
59 | jobs have been processed. At program startup, this occurs immediately. |
---|
60 | |
---|
61 | # Creating Jobs |
---|
62 | |
---|
63 | A job is a descriptor for a piece of work that a thread is meant to |
---|
64 | perform. The basic job is |
---|
65 | |
---|
66 | job j = createJob(string func[, def arg1, ..., def argn]); |
---|
67 | |
---|
68 | Here, `func` is the name of the function to be executed. This name |
---|
69 | can include a package name and `"Current"` for the current package. |
---|
70 | Examples are `"foo"`, `"Somepackage::foo"`, or `"Current::foo"`. |
---|
71 | |
---|
72 | The arguments `arg1` through `argn` are any arguments to be passed |
---|
73 | along. The argument list *can* be partial; one does not have to supply |
---|
74 | any arguments at job creation, but the missing arguments have to be |
---|
75 | supplied at startup (see below). |
---|
76 | |
---|
77 | Examples: |
---|
78 | |
---|
79 | proc add(int x, int y) { return (x+y); } |
---|
80 | |
---|
81 | job adder = createJob("add"); |
---|
82 | job increment = createJob("add", 1); |
---|
83 | job add2and2 = createJob("add", 2, 2); |
---|
84 | |
---|
85 | In this example, three different jobs are created. The first one, |
---|
86 | `adder`, will still require two arguments in order to be run. The |
---|
87 | second one, `increment`, requires one additional argument. And the |
---|
88 | `add2and2` job does not require any more arguments. |
---|
89 | |
---|
90 | Jobs can also be created from other jobs by pasing in a job argument |
---|
91 | instead of a function name as the first argument: |
---|
92 | |
---|
93 | job j2 = createJob(job j[, def arg1, ..., def argn]); |
---|
94 | |
---|
95 | Any arguments are appended to the jobs existing argument list. Note that |
---|
96 | the original job will not be modified: the call to `createJob` will |
---|
97 | first create a copy, then add the arguments to the copy. |
---|
98 | |
---|
99 | Finally, one can also supply a quoted expression to `createJob()`: |
---|
100 | |
---|
101 | createJob(def quote_expr); |
---|
102 | |
---|
103 | Example: |
---|
104 | |
---|
105 | job j = createJob(quote(factorial(1000))); |
---|
106 | |
---|
107 | Within the kernel, additionally C/C++ jobs can be created by supplying |
---|
108 | a function pointer (and optional arguments). These jobs can be returned |
---|
109 | |
---|
110 | For debugging purposes, a job can be given a descriptive name with the |
---|
111 | `nameJob()` function: |
---|
112 | |
---|
113 | nameJob(job j, string name); |
---|
114 | |
---|
115 | This has no effect on execution, but can be useful for debugging. The |
---|
116 | name can be queried with the `getJobName()` function: |
---|
117 | |
---|
118 | string name = getJobName(job j); |
---|
119 | |
---|
120 | If no name is explicitly assigned to a job, the name defaults to the |
---|
121 | function name or (for quoted expression) to the fixed string |
---|
122 | `"quote(...)"`. |
---|
123 | |
---|
124 | # Running jobs |
---|
125 | |
---|
126 | The simplest way to execute a job is with the `startJob()` function: |
---|
127 | |
---|
128 | job j2 = startJob([threadpool pool, [int prio,]] |
---|
129 | job|string j[, def arg1, ..., def argn]); |
---|
130 | |
---|
131 | The first argument is the optional threadpool on which to execute the |
---|
132 | job; if absent, it defaults to the current threadpool. It is followed |
---|
133 | (optionally) by a priority argument, which defaults to zero. Higher |
---|
134 | numbers indicate a higher priority. High priority jobs will preempt |
---|
135 | low priority jobs; note that this can lead to low priority jobs being |
---|
136 | starved if there is a constant influx of high priority jobs. |
---|
137 | |
---|
138 | The next argument is the actual job. For simple jobs, a string |
---|
139 | describing a function name can be supplied instead, which follows |
---|
140 | the same conventions as the first argument for `createJob()`. It |
---|
141 | allows bypassing the `createJob()` step for simple jobs. |
---|
142 | |
---|
143 | Finally, the remaining arguments are taken to be the remaining |
---|
144 | arguments for the job. They are appended to any arguments provided |
---|
145 | during `createJob()`. |
---|
146 | |
---|
147 | For convenience, `startJob()` will also return the job it was passed |
---|
148 | in (or the job it created if a function name was supplied). |
---|
149 | |
---|
150 | A job scheduled by `startJob()` will be executed as soon as a worker |
---|
151 | thread becomes available to run it. |
---|
152 | |
---|
153 | Example: |
---|
154 | |
---|
155 | proc add(int x, int y) { return (x+y); } |
---|
156 | |
---|
157 | threadpool pool = createThreadPool(4); |
---|
158 | job inc = createJob("add", 1); |
---|
159 | startJob(pool, inc, 1); |
---|
160 | |
---|
161 | The result of a job can be queried with `waitJob()`: |
---|
162 | |
---|
163 | def result = waitJob(job j); |
---|
164 | |
---|
165 | Note that a job need not return a result; if the underlying function |
---|
166 | does not return a value, the job will not have a result. One can still |
---|
167 | call `waitJob()` on the job, but it likewise will not return a value. |
---|
168 | |
---|
169 | Example: |
---|
170 | |
---|
171 | proc add(int x, int y) { return (x+y); } |
---|
172 | |
---|
173 | threadpool pool = createThreadPool(4); |
---|
174 | job inc = createJob("add", 1); |
---|
175 | startJob(pool, inc, 1); |
---|
176 | int result = waitJob(inc); |
---|
177 | |
---|
178 | A job's execution can be cancelled with `cancelJob()`: |
---|
179 | |
---|
180 | cancelJob(job j); |
---|
181 | |
---|
182 | If the job has not been executed by a worker thread yet, it is removed |
---|
183 | from the scheduler. If it has been executed already, the function will |
---|
184 | not do anything. If it is being executed, nothing will be done, either. |
---|
185 | However, a flag will be set for the job and the job can use the function |
---|
186 | `jobCancelled()` to find out if cancellation has been requested and |
---|
187 | abort early. |
---|
188 | |
---|
189 | Example: |
---|
190 | |
---|
191 | if (jobCancelled()) { return (); } |
---|
192 | |
---|
193 | More interestingly, the execution of jobs can be made contingent on |
---|
194 | the completion of other jobs or on triggers. |
---|
195 | |
---|
196 | scheduleJobs([threadpool pool, [int prio,]] |
---|
197 | list|job|string jobs[, |
---|
198 | list|job|trigger dep1, ..., list|job|trigger depn]); |
---|
199 | |
---|
200 | The first two arguments (`pool` and `prio`) are the same as for |
---|
201 | `startJob()` and are again both optional. The third argument is either a |
---|
202 | job, a function name (with the same semantics as for `startJob()`) or a |
---|
203 | list of jobs. |
---|
204 | |
---|
205 | The remaining arguments list dependencies. The jobs will not be executed |
---|
206 | until all dependencies are met, i.e. the underlying tasks are completed |
---|
207 | or the triggers have been fully activated. |
---|
208 | |
---|
209 | If any of the jobs or triggers `dep1` through `depn` return results, |
---|
210 | then those will be appended as arguments to the scheduled jobs. |
---|
211 | |
---|
212 | Example: |
---|
213 | |
---|
214 | proc add(int x, int y) { return (x+y); } |
---|
215 | |
---|
216 | threadpool pool = createThreadPool(4); |
---|
217 | job j1 = startJob("add", 1, 2); |
---|
218 | job j2 = startJob("add", 3, 4); |
---|
219 | job total = createJob("add"); |
---|
220 | scheduleJob(total, j1, j2); |
---|
221 | int sum = waitJob(total); |
---|
222 | |
---|
223 | # Triggers |
---|
224 | |
---|
225 | Triggers allow the programmer to create more complex interactions |
---|
226 | between jobs. |
---|
227 | |
---|
228 | trigger trig = createTrigger([threadpool pool,] |
---|
229 | string type[, def arg1, ..., argn]); |
---|
230 | |
---|
231 | We currently support three types of triggers, accumulators, counters, and |
---|
232 | sets. |
---|
233 | |
---|
234 | trigger trig = createTrigger("acc", int n); |
---|
235 | trigger trig = createTrigger("count", int n); |
---|
236 | trigger trig = createTrigger("set", int n); |
---|
237 | |
---|
238 | An accumulator trigger will accumulate a set of results. It will fire |
---|
239 | when `n` results have been added; any accumulated results will be passed |
---|
240 | as an additional list argument to triggered jobs. |
---|
241 | |
---|
242 | A counter trigger will simply count up and fire when it has been activated `n` |
---|
243 | times. A set trigger represents the set `{1..n}` and will fire when all |
---|
244 | elements in the set have been added at least once. Neither of the last |
---|
245 | two will pass any additional arguments to triggered jobs. |
---|
246 | |
---|
247 | Adding to a trigger can be done with `updateTrigger()`. |
---|
248 | |
---|
249 | updateTrigger(trigger trig[, def arg]); |
---|
250 | |
---|
251 | Triggers can also be activated when a job finishes or another trigger |
---|
252 | fires via the `chainTrigger()` function: |
---|
253 | |
---|
254 | chainTrigger(trigger trig, job|trigger dep); |
---|
255 | |
---|
256 | The state of a trigger can be tested explicitly with `testTrigger()`, |
---|
257 | which returns 1 or 0, depending on whether it has fired or not. |
---|
258 | |
---|
259 | int success = testTrigger(trigger trig); |
---|
260 | |
---|
261 | Because it is often useful to discard any remaining jobs when a trigger |
---|
262 | fires, the `cancelOnTrigger()` function allows to automate this. |
---|
263 | |
---|
264 | cancelOnTrigger(trigger trig, job|list arg1, ..., job|list argn); |
---|
265 | |
---|
266 | The function takes an arbitrary number of jobs or lists of jobs in addition |
---|
267 | to a trigger argument. When the trigger fires, all those jobs will be |
---|
268 | cancelled if they haven't finished yet. |
---|
269 | |
---|
270 | A simple use of triggers is to wait for the first job in a list to be |
---|
271 | triggered. |
---|