Generic job queue implementation for Zig.
In order to take full advantage of modern multicore CPUs, it is necessary torun much of your application logic on separate threads. ThisJobQueueprovides a simple API to schedule "jobs" to run on a pool of threads, typicallyas many threads as your CPU supports, less 1 (the main thread).
pubfnbuild(b:*std.Build)void {constexe=b.addExecutable(.{... });constzjobs=b.dependency("zjobs", .{});exe.root_module.addImport("zjobs",zjobs.module("root"));}constJobs=JobQueue(.{});// a default-configured `JobQueue` typevarjobs=Jobs.init();// initialize an instance of `Jobs`deferjobs.deinit();// ensure that `jobs` is cleaned up when we're done// First we will define a job that will print "hello " when it runs.// The job must declare a `exec` function, which defines the code that// will be executed on a background thread by the `JobQueue`.// Our `HelloJob` doesn't contain any member variables, but it could.// We will see an example of a job with some member variables below.constHelloJob=struct {pubfnexec(_:*@This())void {print("hello ", .{}); }};// Now we will schedule an instance of `HelloJob` to run on a separate// thread.// The `schedule()` function returns a `JobId`, which is how we can refer// to this job to determine when it is done.// The first argument to `schedule()` is the `prereq`, which// specifies a job that must be completed before this job can run.// Here, we specify the `prereq` of `JobId.none`, which means that// this job does not need to wait for any other jobs to complete.// The second argument to `schedule()` is the `job`, which is a user-// defined struct that declares a `exec` function that will be executed// on a background thread.// Here we are providing an instance of our `HelloJob` defined above.consthello_job_id:JobId=tryjobs.schedule(JobId.none,// does not wait for any other jobHelloJob{},// runs `HelloJob.exec()` on another thread);// Scheduled jobs will not execute until `start()` is called.// The `start()` function spawns the threads that will run the jobs.// Note that we can schedule jobs before and after calling `start()`.jobs.start();// Now we will schedule a second job that will print "world!" when it runs.// We want this job to run after the `HelloJob` completes, so we provide// `hello_job_id` as the `prereq` when scheduling this job.// This ensures that the string "hello " will be printed before we print// the string "world!\n"// This time, we will use an anonymous struct to declare the job directly// within the call to `schedule()`.// Note the trailing empty braces, `{}`, which initialize an instance of// this anonymous struct.constworld_job_id:JobId=tryjobs.schedule(hello_job_id,// waits for `hello_job_id` to be completedstruct {fnexec(_:*@This())void {print("world!\n", .{}); } }{},// trailing `{}` initializes an instance of this anonymous struct);// When we want to shut down all of the background threads, we can call// the `stop()` function.// Here we will schedule a job to call `stop()` after our "world!" job// completes.// This ensures that the string "hello world!\n" will be printed before// we stop running our jobs.// Note that our anonymous "stop job" captures a pointer to `jobs` so that// it can call `stop()`._=tryjobs.schedule(world_job_id,// waits for `world_job_id` to be completedstruct {jobs:*Jobs,// stores a pointer to `jobs`fnexec(self:*@This())void {self.jobs.stop(); } }{// trailing `{}` initializes an instance of this anonymous struct .jobs=&jobs,// and initializes a pointer to `jobs` here },);// Now that we're done, we can call `join()` to wait for all of the// background threads to finish processing scheduled jobs.jobs.join();