Library initialization
Once the build system has been set up correctly (see Compiling) there are still a few things to be taken care of at the C++ level; lets write a quick 'Hello World'.
In fact, a lot of prerequisites are due to libcwd:
-
Each source file must start with #include "sys.h". By default this will use the one provided by cwds, or you can override that by providing your own sys.h in the root of the project.
-
Each source or header file that uses debugging, so that certainly includes main.cpp, needs to #include "debug.h" somewhere. However, main.cpp also needs to #include "evio/EventLoop.h" (which includes threadpool/AIThreadPool.h), or at the very least threadpool/AIThreadPool.h if you don't use an EventLoop, which already include(s) debug.h, so including just the former is sufficient.
-
At the beginning of main you need to add the line:
Debug(NAMESPACE_DEBUG::init());
This calls a function provided by cwds that reads your ~/.libcwdrc file turning on debug output for the specified debug channels.
To enable debug output of AIStatefulTask you should turn on the debug channel statefultask. You could add, below the previous one, the line Debug(if (!dc::statefultask.is_on()) dc::statefultask.on()); but prefered is to simply turn on and off debug channels in your ~/.libcwdrc. Mine looks like this:
silent = on
channels_default = off
channels_on = warning,notice,system,statefultask,threadpool
#channels_on = action,semaphore,semaphorestats
gdb = /usr/bin/gdb $GDBCOMMANDFILE
xterm = konsole --nofork --workdir "$PWD" --geometry 165x24-0-0 -e %s
AIStatefulTask requires multiple initializations at the beginning of main. The reason for that is to make it easier to customize the initialization; although each have sensible defaults you can pass (different) arguments to fine tune the initialization. See their respective documentation for the details. For now don't worry about this and just use the defaults.
The following objects should be created at the start of main:
-
An AIMemoryPagePool object. Causes a singleton object to be initialized that can only be used after this AIMemoryPagePool object is created. You must create it at the start of main however and not as a global variable, so that upon leaving main the singleton is destructed and allocated memory is cleanly deallocated. This is a general approach of the library, and should be of your code that uses the library: do not use the library from anything that can be executed before or after main.
-
An AIThreadPool object. Even though the thread pool has its own separate git submodule, it is the work horse of AIStatefulTask. Like with AIMemoryPagePool, creating an AIThreadPool object at the start of main causes a singleton object to be initialized that can only be used while this object exists. Do not use AIThreadPool::instance() before or after main.
-
One or more AIThreadPool::PriorityQueue objects, refered to by AIQueueHandle`s, to be used as default handler for your tasks. But even if you don't need a handler, you must created at least one queue.
-
Create zero or more AIEngine objects and use those as default handler for your tasks; and have a main loop calling mainloop(); on each of those engines on a regular basis; for example as part of the 'idle' cycle of the main loop of gtkmm/glibmm.
See the helloworld.cxx file of the ai-statefultask-testsuite project for a working "Hello World" example. As another example have a look at the fibonacci.cxx file in the same project.
If you are new to AIStatefulTask then I urge you to first clone ai-statefultask-testsuite and get that working; as that will require you to have all the bits and pieces for building a project that uses AIStatefulTask installed (note: if you use autotools then start with getting, compiling and installing libcwd. Configure libcwd (for example) with --enable-maintainer-mode --enable-optimize --disable-alloc --disable-location --disable-nonthreading because, respectivily, you got it with git, but you're not debugging libcwd itself, you don't need memory allocation debugging (slow), or printing source_file:line_number locations, and we only need the multithreaded version. This is not necessary when you use cmake and set the environment variable GITACHE_ROOT
(see Compiling)).
If you run into any problems configuring, compiling and/or installing libcwd using GNU autotools, please watch this tutorial. If you run into any problems with setting up the environment related to the git submodules (cwm4 and cwds), please watch this tutorial.
The minimal program to print debug output looks something like this:
#include "sys.h"
#include "debug.h"
int main()
{
Debug(NAMESPACE_DEBUG::init());
Dout(dc::notice, "Entering main()");
Dout(dc::notice, "Leaving main()");
}
which requires (linking with object code from) submodule cwds (which in turn requires libcwd_r).
A minimal program that has a thread pool, looks something like this:
#include "sys.h"
#include "threadpool/AIThreadPool.h"
int main()
{
Debug(NAMESPACE_DEBUG::init());
Dout(dc::notice, "Entering main()");
AIThreadPool thread_pool;
AIQueueHandle high_priority_queue =
thread_pool.new_queue(8);
AIQueueHandle medium_priority_queue = thread_pool.new_queue(8);
AIQueueHandle low_priority_queue = thread_pool.new_queue(8);
Dout(dc::notice, "Leaving main()");
}
which requires, additionally, submodules threadpool threadsafe and utils.
A minimal program that has an event loop (for I/O), looks something like this:
#include "sys.h"
#include "evio/EventLoop.h"
#include "utils/debug_ostream_operators.h"
int main()
{
Debug(NAMESPACE_DEBUG::init());
Dout(dc::notice, "Entering main()");
AIThreadPool thread_pool;
AIQueueHandle low_priority_queue = thread_pool.new_queue(8);
try
{
evio::EventLoop event_loop(low_priority_queue);
event_loop.join();
}
catch (AIAlert::Error const& error)
{
Dout(dc::warning, error);
}
Dout(dc::notice, "Leaving main()");
}
which requires, additionally, submodule evio.
When you actually use a task, so far we only used submodules other than statefultask, it is a good idea to also create the AIMemoryPagePool object, just in case (although this is really only required when you use AIStatefulTaskMutex (indirectly)).
Assume we have a task task::HelloWorld and want to run that, after initialization. Then we can run this task in the thread pool:
#include "sys.h"
#include "helloworld-task/HelloWorld.h"
#include "threadpool/AIThreadPool.h"
#include "utils/AIAlert.h"
#include "utils/debug_ostream_operators.h"
int main()
{
Debug(NAMESPACE_DEBUG::init());
Dout(dc::notice, "Entering main()");
AIThreadPool thread_pool;
AIQueueHandle low_priority_queue = thread_pool.new_queue(8);
try
{
auto task = statefultask::create<task::HelloWorld>();
task->initialize(42);
task->run(low_priority_queue);
}
catch (AIAlert::Error const& error)
{
Dout(dc::warning, error);
}
Dout(dc::notice, "Leaving main()");
}
Declaration of class DefaultMemoryPagePool.
Definition: DefaultMemoryPagePool.h:116
which requires, additionally, submodules statefultask (and helloworld-task).
Note that AIMemoryPagePool is created before AIThreadPool. This is important because tasks can continue running until the thread pool is destructed, so mpp must be destructed after thread_pool!
Also note that the task is run by passing a handler. Try what happens when you don't pass anything and just call run().
It is also possible to run a task in an AIEngine. An AIEngine requires a main loop somewhere to be run from, which we don't have in a small Hello World type program, so we have to add one for demonstration purposes. I'm also adding the event loop back, pretending we're doing I/O.
#include "sys.h"
#include "helloworld-task/HelloWorld.h"
#include "evio/EventLoop.h"
#include "utils/debug_ostream_operators.h"
int main()
{
Debug(NAMESPACE_DEBUG::init());
Dout(dc::notice, "Entering main()");
AIThreadPool thread_pool;
AIQueueHandle low_priority_queue =
thread_pool.new_queue(8);
try
{
evio::EventLoop event_loop(low_priority_queue);
bool test_finished = false;
auto task = statefultask::create<task::HelloWorld>();
task->initialize(42);
task->run(&engine, [&](bool CWDEBUG_ONLY(success)){
test_finished = true;
Dout(dc::notice, "Inside the call-back (" <<
(success ? "success" : "failure") << ").");
});
while (!test_finished)
{
engine.mainloop();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
event_loop.join();
}
catch (AIAlert::Error const& error)
{
Dout(dc::warning, error);
}
Dout(dc::notice, "Leaving main()");
}
Declaration of class AIEngine.
Definition: AIEngine.h:110
Linking
Using cmake
, each submodule is an OBJECT library and needs to be added to target_link_libraries
of the final executable (or shared library if you'd make that instead). To make that easier, they all automatically add themselves to the cmake variable AICXX_OBJECTS_LIST
, so you only have to link with that. Task submodules are not added at the moment, so those have to be added specifically.
For example the above would have the following entry in CMakeLists.txt
:
add_executable(helloworld helloworld.cxx)
target_link_libraries(helloworld PRIVATE AICxx::helloworld-task ${AICXX_OBJECTS_LIST})
while the src/Makefile.am
when using GNU autotools would contain:
helloworld_SOURCES = helloworld.cxx
helloworld_CXXFLAGS = @LIBCWD_R_FLAGS@
helloworld_LDADD = ../helloworld-task/libhelloworldtask.la ../statefultask/libstatefultask.la ../evio/libevio.la ../threadpool/libthreadpool.la ../threadsafe/libthreadsafe.la ../utils/libutils_r.la ../cwds/libcwds_r.la