pFactory: A generic library for designing parallel solvers, the 16th International Conference on Applied Computing (AC 2019). Gilles Audemard, Gael Glorian, Jean-Marie Lagniez, Valentin Montmirail and Nicolas Szczepanski
This libary needs:
- a C++11 compiler
- the pthread library
After unzipping the release archive, go into the source directory and enter:
./configure
make
The created library libpFactory.a
is in the directory lib/
Note to developers: editing pFactory may require the autotools. After downloading the source code, execute bootstrap
to initialize the project.
The main method of pFactory is the constructor pFactory::Group(unsigned int nbThreads)
which create a Group object.
An instance of the class group represents:
- a set of threads
std::thread
- a set of tasks
std::function<int()>
Tasks are added thanks to the method Group::add(std::function<int()> function)
using C++11 lambdas and are launched by the method Group::start()
.
Of course, we can have more tasks than threads and in this case, a queue of work is
created and all tasks are executed. To finish, the method Group::wait()
waits
that all tasks are completed (only one if the concurrent mode is activated in the method start
)
and join all threads.
The library contains an efficient sharing mechanism. Once the group object created, you can link a communicator that is in charge of the communication between threads. To this end:
- Create a communicator (in this example, the communicator can share integers between threads):
pFactory::Communicator<int>* integerCommunicator(&group);
. Variablegroup
is an instance ofFactory::Group
object defined above. - Send int to other threads using the method
send(int)
: - Receive integers from other threads. Two methods achieve this task:
- using the method
void recvAll(std::vector<int> &data)
. In this case, the vector data receives all data. - using the method
std::pair<bool, int> recv();
. In this case, one can receive data one by one. The first element of the pair becomes true if there is no more data to receive.
- using the method
#include "pFactory.h"
// In this example, we create a group of thread saying hello world
int main(){
// A group of nbCores threads
pFactory::Group group(pFactory::getNbCores());
// Add as many tasks as threads in the group
for(unsigned int i = 0; i < pFactory::getNbCores();i++){
group.add([&](){
// pFactory::cout() provides a special critical section for displaying information
pFactory::cout() << group.getTask() << " says Hello World" << std::endl;
return 0;
});
}
// Start the computation of all tasks
group.start();
// Wait until all threads are performed all tasks
group.wait();
}
#include "pFactory.h"
// In this example, each thread share an integer to the others.
int main() {
// A group of nbCores threads
pFactory::Group group(pFactory::getNbCores());
pFactory::Communicator<int> integerCommunicator(group);
for(unsigned int i = 0; i < pFactory::getNbCores(); i++) {
// Add as many tasks as threads in the group
group.add([&]() {
// group.getTask() return the task in progress
pFactory::cout() << group.getTask() << " sends: " << group.getTask().getId() << std::endl;
integerCommunicator.send(group.getTask().getId());
// A group has a barrier to wait all tasks at the same moment of the execution
// Here, this barrier is used to wait that all tasks are sent their data
group.barrier.wait();
/* With recvAll function */
std::stringstream msg;
std::vector<int> data;
integerCommunicator.recvAll(data);
msg << group.getTask() << " receives:";
for(unsigned int j = 0; j < data.size(); ++j)
msg << data[j] << ' ';
pFactory::cout() << msg.str() << std::endl;
return 0;
});
}
// Start the computation of all tasks
group.start();
// Wait until all threads are performed all tasks
group.wait();
}
#include "pFactory.h"
// In this example, we create a group of threads with a lot of tasks.
// It is a model for a dynamic divide and conquer (DC) strategy.
// Firstly create tasks that represent subproblems (divide phase) and next calculate all theses tasks (conquer phase)
// In addition, some others tasks can be added during the conquer phase (thus called dynamic)
int algorithm(pFactory::Group& group, bool dynamic){
// To simulate the task calculation
for(unsigned int j = 0; j < 100;j++){
if (group.isStopped()){
group.getTask().setDescription("stopped during its computation");
return (int)group.getTask().getId();
} // To stop this task during its calculation if the group have to be stopped
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// Dynamic divide phase
if (group.getTask().getId() < nbTasks)
group.add([&](){return algorithm(group, true);});
// The return code of the task that has finished
group.getTask().setDescription(dynamic == true? "dynamic expected end": "static expected end");
pFactory::cout() << group.getTask() << std::endl;
return (int)group.getTask().getId();
}
int main(){
// A group of nbCores threads
pFactory::Group group(pFactory::getNbCores());
// First divide phase : add firstly 20 tasks
for(unsigned int i = 0; i < 20;i++)
// A task is represented by a C++11 lambda function
group.add([&](){return algorithm(group, false);});
//By default, a group takes the latest tasks added (is set to group.popBack())
group.popFront(); //To prioritize the first tasks added (in the order of group.add() methods)
pFactory::Controller controller(group);
controller.start();// Conquer phase : start the computation of all tasks
controller.wait();// Wait until all threads are performed all tasks
for(auto &task: group.getTasks()) std::cout << task << std::endl;
}
You can also download an implementation of the SAT solver glucose in parallel mode (aka named syrup) using the library pFactory. Such implementation integrates clauses sharing mechanism.
Main author
- Nicolas Szczepanski - [email protected]
Other contributors
- Gilles Audemard - [email protected]
- Jean-Marie Lagniez - [email protected]
Do not hesitate to contact [email protected] if you encounter any problems with pFactory.