Worker Pools in Prolog

Our example demonstrates how to implement a worker pool using threads and message passing in Prolog.

:- use_module(library(threads)).

% Worker predicate
worker(Id, Jobs, Results) :-
    thread_get_message(Jobs, Job),
    (   Job == end_of_jobs
    ->  true
    ;   format('worker ~w started  job ~w~n', [Id, Job]),
        sleep(1),
        format('worker ~w finished job ~w~n', [Id, Job]),
        Result is Job * 2,
        thread_send_message(Results, Result),
        worker(Id, Jobs, Results)
    ).

% Main predicate
main :-
    NumJobs = 5,
    message_queue_create(Jobs),
    message_queue_create(Results),

    % Start 3 worker threads
    forall(between(1, 3, W),
           thread_create(worker(W, Jobs, Results), _, [])),

    % Send jobs
    forall(between(1, NumJobs, J),
           thread_send_message(Jobs, J)),
    
    % Send end-of-jobs messages
    forall(between(1, 3, _),
           thread_send_message(Jobs, end_of_jobs)),

    % Collect results
    forall(between(1, NumJobs, _),
           thread_get_message(Results, _)),

    % Clean up
    message_queue_destroy(Jobs),
    message_queue_destroy(Results).

% Run the main predicate
:- main, halt.

In this Prolog implementation:

  1. We use the threads library for concurrent programming.

  2. The worker predicate represents a worker. It receives jobs from the Jobs queue, processes them (simulated by a 1-second sleep), and sends results to the Results queue.

  3. The main predicate sets up the job and result queues, creates worker threads, sends jobs, and collects results.

  4. We use message_queue_create to create message queues for jobs and results.

  5. Worker threads are created using thread_create.

  6. Jobs are sent to workers using thread_send_message.

  7. Results are collected using thread_get_message.

  8. We use forall for iteration, which is idiomatic in Prolog.

To run the program, save it as worker_pools.pl and use:

$ swipl worker_pools.pl
worker 1 started  job 1
worker 2 started  job 2
worker 3 started  job 3
worker 1 finished job 1
worker 1 started  job 4
worker 2 finished job 2
worker 2 started  job 5
worker 3 finished job 3
worker 1 finished job 4
worker 2 finished job 5

This program demonstrates concurrent execution of jobs by multiple workers in Prolog. Despite doing about 5 seconds of total work, the program completes in about 2 seconds due to the concurrent execution by 3 workers.

Note that Prolog’s approach to concurrency is different from imperative languages. It uses message passing between threads rather than shared memory, which aligns well with Prolog’s logical programming paradigm.