Next: , Previous: , Up: Utilities   [Contents][Index]


4.3 Task

Functions to run multiple tasks (such as mutations and fitness tests) on multiple threads.

This module makes use of Bordeaux Threads. See the documentation here: https://trac.common-lisp.net/bordeaux-threads/wiki/ApiDocumentation

4.3.1 Description

A task is an operation to be performed by the multi-threaded task-runner. A task can be customized by the client to generate a job (child series of tasks) by implementing the task-job method, and the code to be performed when processing a task is defined by the process-task method.

A job is a Lisp function, which takes no arguments, and which will produce a task each time it is called, or nil when all of its tasks are complete. Think of a job as a lazy sequence of task.

task-runner-jobs is a stack of jobs. Worker threads will call the first job on the stack, and process the task returned.

A task may add 1 or more jobs to the top of the stack, causing worker threads to immediately start processing those jobs since they are now higher on the stack and therefore have priority over other tasks.

When the jobs stack is empty/NIL, then all worker threads will exit.

4.3.2 Example use

    (setf *runner* (run-task (make-instance 'single-cut-all :object *orig*)
                             10))
     ;; When (task-runner-worker-count *runner*) = 0,
     ;; it means all threads are finished.

4.3.3 Complex Example use

    (defmacro task-map (num-threads function sequence)
      "Run FUNCTION over SEQUENCE using a `simple-job' `task-job'."
      (with-gensyms (task-map task-item)
        `(if (<= ,num-threads 1)
             (mapcar ,function ,sequence) ; No threading.
             (progn                     ; Multi-threaded implementation.
               (defclass ,task-map (task) ())  ; Task to map over SEQUENCE.
               (defclass ,task-item (task) ()) ; Task to process elements.
               (defmethod task-job ((task ,task-map) runner)
                 (declare (ignore runner))
                 (let ((objs (task-object task))) ; Enclose SEQUENCE for fn.
                   (lambda () (when objs ; Return nil when SEQUENCE is empty.
                               ;; Return a task-item whose task-object run
                               ;; FUNCTION on the next element of SEQUENCE.
                           (make-instance ',task-item :object
                                          (curry ,function (pop objs)))))))
               (defmethod process-task ((task ,task-item) runner)
                 ;; Evaluate the task-object for this item as created in
                 ;; the task-job method above.  Save the results.
                 (task-save-result runner (funcall (task-object task))))
               (task-runner-results ; Return results from the results obj.
                ;; Create the task-map object, and run until exhausted.
                (run-task-and-block
                 (make-instance ',task-map :object ,sequence)
                 ,num-threads))))))

The above example uses the tasks API to implement a simple parallel map spread across a configurable number of workers. When more than one worker thread is requested the following objects and methods are created to implement the parallel map.

Finally, with the above objects and methods defined, the task-map macro wraps the sequence into a task-map task object and passes this to the run-task-and-block function yielding a runner and the contents of that runner are extracted and returned using the task-runner-results accessor.

See the actual implementation of task-map in the sel/utility package for a more efficient implementation which doesn’t use a macro or require new objects and methods to be defined on the fly.


Next: , Previous: , Up: Utilities   [Contents][Index]