Disclaimer: If you are using threads in serious projects look at boost::threads or std::thread, the following is just a little test so use if you want but you have been warned
The Dashee project required me to implement threading so tasks such as reading from the sensor or reading from the server could be threaded. Now I know the actual Pi is single core but it still can take advantage of threads in its main loop sleep time.
To do the Thread class I decided to encapsulate the pthread_ API in it's own little class wrapper. Im not going to go in detail of the source code of the wrapper but the following examples shows the usage.
For example sakes, we assume all programs are compiled with -ldashee
using libdashee.so
from the Dashee project and -pthread
for pthreads to work. We also imply that include <iostream>
is above every example.
Lets start simple, lets get a thread to add 1 to a passed variable 100 times, so at the end of the thread the value of parameter value will be value + 100
#include <dashee/Threads/Thread.h>
void * doWork(void * v)
{
for (int i = 0; i < 100; i++)
{
(*v)++;
}
}
int main()
{
int x = 0
dashee::Threads::Thread t1(doWork);
t1.start(static_cast<void *>(&x));
t1.join();
std::cout << "Value of x: " << x << std::endl;
return 0;
}
A threading class would not be complete without locking. The Dashee interface provides an encapsulation of locking.
#include <dashee/Threads/Thread.h>
#include <dashee/Threads/Lock/Mutex.h>
dashee::Threads::Lock * l1;
void * doWork(void * v)
{
for (int i = 0; i < 100; i++)
{
li->lock();
(*v)++;
li->unlock();
}
}
int main()
{
int x = 0;
l1 = new dashee::Threads::LockMutex();
dashee::Threads::Thread t1(doWork);
dashee::Threads::Thread t2(doWork);
t1.start(static_cast<void *>(&x));
t2.start(static_cast<void *>(&x));
t1.join();
t2.join();
// Prints 200
std::cout << "Value of x: " << x << std::endl;
delete l1;
return 0;
}
What happens if your inner loop throws an exception your thread will lock for ever. You can try putting an unlock in each catch block (There is no finally concept in C++) but that is messy. So what we can do is use RAII concept implemented in our Scope class. Because a destructor of an initialized object is called at the end of a scope.
We can use this concept to unlock the lock as soon as it gets to the end of the scope or an exception is thrown.
#include <dashee/Threads/Thread.h>
#include <dashee/Threads/Scope.h>
#include <dashee/Threads/Lock/Mutex.h>
dashee::Threads::Lock * l1;
void * doWork(void * v)
{
for (int i = 0; i < 100; i++)
{
dashee::Threads::Scope s1(l1);
(*v)++;
// s1 destructor is called, which calls unlock of l1 :D
}
dashee::Threads::Thread::exit();
return NULL;
}
int main()
{
int x = 0;
l1 = new dashee::Threads::LockMutex();
dashee::Threads::Thread t1(doWork);
dashee::Threads::Thread t2(doWork);
t1.start(static_cast<void *>(&x));
t2.start(static_cast<void *>(&x));
t1.join();
t2.join();
// Prints 200
std::cout << "Value of x: " << x << std::endl;
delete l1;
return 0;
}
Not only we have lock safety but a little more cleaner code. Also if you look closely the doWork function calls the exit static function, which is also the part of the dashee Thread interface.
You can also use read and write locks by using the following syntax:
dashee::Threads::LockReadWrite l1;
l1.lock(dashee::Threads::Lock::LOCKTYPE_READ)
l1.unlock();
l1.lock(dashee::Threads::Lock::LOCKTYPE_DEFAULT) // Same as li.lock();
l1.unlock();
Try playing around with it, download the source code and plug the library in the above examples.
Post if you find improvements. Or comment if you need help