Pool of Tasks
General Algorithm
for each worker
while (true) {
get a task from the bag;
if (no more tasks)
break;
execute task, possibly generating new ones;
}
Method Features
The master generates one task at a time and assigns it to the first
available worker.
The workers wait for a task and execute it while they are not told
to stop.
We use a flag called "none" to signify an empty task. The workers
receiving this task know that they can stop the working loop.
Master() {
do {
task = Generate_task();
if (task != none) {
worker = Collect(result, any_process);
//worker is the source in the Collect
Assign(task, worker);
}
} while (task != none);
Finish_all();
}
// Sends a message to all the workers that they can stop working.
Finish_all() {
for(i=1, i<=no_workers; i++) { // no_workers = no_proc-1
worker = Collect(result, any_process);
Assign(none, worker);
}
}
Worker() {
result = NULL;
do {
Report(result, master);
Get(task, master);
if (task != none)
result = Execute_task(task);
} while (task != none);
}
Shared Memory Models
Simple model: assuming that the tasks are generated faster than they are executed, so the workers don't deplete the pool before all of the tasks have been added. The tasks are stored in a shared global container-type data structure that the functions Store and Get_next_task have access to. If the global container is empty, then the task "none" will be returned.
Master() { do { task = Generate_task(); Store(task); } while (task != none); } Worker() { do { task = Get_next_task(); if (task != none) { result = Execute_task(task); Report(result); } } while (task != none); }
Slower model: applied if the task generation process takes longer than in the simple model. The workers may empty the global task container before the master has finished generating the tasks. A global variable called "global_done", initialized as false, tells us whether the master is still in the process of generating more tasks or not. We assume that the function "more_tasks" will check that the global task container is not empty. The workers will not stop working until the master has finished generating more tasks and the global task container has been emptied.
Master() { global_done = false; do { task = Generate_task(); Store(task); } while (task != none); global_done = true; } Worker() { do { task = Get_next_task(); if (task != none) { result = Execute_task(task); Report(result); } } while (!global_done || more_tasks()); }
Example: Chess
Sum as Pool of Tasks
Version 1: a task is defined as adding one element of the array to the local sum. This is a template that can be applied when the operation to be done on each element of the array takes a significant amount of time.
// global variables int sum=0, a[], size, global_index; Worker() { int local_sum = 0, local_index; do { lock(&mutex); local_index = global_index; global_index++; unlock(&mutex); if (local_index < size) local_sum += a[local_index]; } while (local_index < size); lock(&mutex); sum += local_sum; unlock(&mutex); }
Version 2: a task is defined as adding 10 elements of the array at a time, or however many are left at the end of the array. This reduces the amount of synchronisation necessary and the number of times the mutex is locked and unlocked.
// global variables int sum=0, a[], size, global_index; Worker() { int local_sum = 0, local_index; do { lock(&mutex); local_index = global_index; global_index+=10; unlock(&mutex); for (i=0; i<10 && local_index+i<size; i++) local_sum += a[local_index+i]; } while (local_index < size); lock(&mutex); sum += local_sum; unlock(&mutex); }