Crate jobsys[][src]

Lockless Work Stealing Job System.

Features

  • Lockless work stealing queue
  • 0 runtime allocations
  • Jobs are executed from closures
  • Chaining and grouping of jobs
  • Parallel for_each abstraction

General Notes

This crate provides job system with 0 allocation overhead at runtime. What this means in practice is, that once the instance is created, there are no more memory allocations required to execute jobs.

Jobs are allocated from a Job Pool with a fixed capacity. Each thread gets their own pool and queue. The system uses a fixed storage space where it stores the closure for each job. The default storage is set to 64 bytes. This can be extended to 128 by enabling the feature “job_storage_128”.

When a Job finishes execution there is a possibility to start other jobs. See JobScope::chain() for more details.

Additionally, it’s recommended that, in order to avoid running out of jobs during execution, you regularly ensure that you all your previous jobs have finished executing.

Finally, this crate runs on rust stable and has been tested with rust 1.50.0 with the 2018 edition.

Safety

Due to the implementation performing type erasure to store the closures in the pre-allocated space, I have not been able to figure out a way to ensure the compiler is aware of this so it can enforce lifetime and ownership checks. Until that is addressed JobScope::create, JobScope::create_with_parent and JobScope::run are marked as unsafe and is up to caller to guarantee that they can enforce their safty requirements.

Panics

Due to the queues having a fixed size, if we start filling all the queues up to full capacity there is a small chance that a worker thread may not be able to queue chained jobs. When this happens the system will panic as there is no way to recover from this. The queues come with debug asserts which will detect this situation.

Examples

Safe

These example provide safe variants that uphold the safety requirements.

Parallel

See for_each() and for_each_with_result() for more details.

let mut job_sys = jobsys::JobSystem::new(4, 512).unwrap();
let job_scope = jobsys::JobScope::new_from_system(&job_sys);
let mut array = [0_u32; 100];
job_scope.for_each(&mut array, |slice: &mut [u32], start, _end| {
        for i in 0..slice.len() {
            slice[i] = (start + i) as u32;
        }
    }).expect("Failed to start jobs");

Single Work Item

See JobInstance for more details.

let job_sys = jobsys::JobSystem::new(2, 128).expect("Failed to init job system");
let job_scope = jobsys::JobScope::new_from_system(&job_sys);
let job_instance = jobsys::JobInstance::create(&job_scope, || {
    println!("Hello from Job Instance");
    }).unwrap();
job_instance.wait_with(|| println!("Waiting on Job to Finish")).expect("Failed to wait on job");

Unsafe

The following example are unsafe due to certain requirements that need to be upheld by the caller but can be used as building block for safer alternatives.

Start and Wait

let job_sys = jobsys::JobSystem::new(4, 512).unwrap();
let job_scope = jobsys::JobScope::new_from_system(&job_sys);
let mut handle = unsafe{job_scope.create(|| { println!("Hello World!");}).unwrap()};
unsafe{job_scope.run(&mut handle).expect("Failed to run job");}
job_scope.wait(&mut handle);

Grouping

Ensure one job does not complete until other jobs have finished as well.

let job_sys= jobsys::JobSystem::new(4, 512).unwrap();
let job_scope = jobsys::JobScope::new_from_system(&job_sys);
let mut parent = job_scope.create_noop().unwrap(); // Create a job that does nothing
let mut child = unsafe{job_scope.create_with_parent(&mut parent, || { println!("Hello World!");}).unwrap()};
unsafe{job_scope.run(&child).expect("Failed to start child");}
unsafe{job_scope.run(&parent).expect("Failed to start parent");}
job_scope.wait(&parent); // Parent will only finish when both it and its child have finished

Chaining

Launch new jobs as soon as one job completes.

let job_sys = jobsys::JobSystem::new(4, 512).unwrap();
let job_scope = jobsys::JobScope::new_from_system(&job_sys);
let mut first = unsafe{job_scope.create(|| { println!("Hello World Chained!");})}.unwrap();
let mut second = unsafe{job_scope.create(|| { println!("Hello World Chained!");})}.unwrap();
job_scope.chain(&mut first, &second).expect("Failed to chain job, maximum chain count exceeded");
unsafe{job_scope.run(&first).expect("Failed to start job");}
job_scope.wait(&second); // Second will only be executed after first completes

Creating jobs on job threads

let job_sys = jobsys::JobSystem::new(4, 512).unwrap();
let job_scope = jobsys::JobScope::new_from_system(&job_sys);
let mut handle = unsafe{job_scope.create(|| {
    let thread_job_scope = jobsys::JobScope::new_from_thread().unwrap();
    let thread_job_handle = thread_job_scope.create(|| {
        println!("Created on job thread");
    }).unwrap();
    thread_job_scope.run(&thread_job_handle).expect("Failed to run job");
}).unwrap()};
unsafe{job_scope.run(&mut handle).expect("Failed to run job");}
job_scope.wait(&mut handle);

Structs

JobInstance

JobInstance provides a safe interface to schedule a Job and ensures waits until the job has finished execution before it goes out of scope.

JobScope

Manages the creation and scheduling of jobs. This type can either be created from a JobSystem instance or retrieved from a worker thread. If you tried to use this with a thread that is not being tracked by the job system, all the functions will fail with Error::InvalidThread.

JobSystem

Work Stealing JobSystem.

ScopedJobHandle

Handle which represents an allocated job.

Enums

Error