[C++] A few Boost Thread questions

Started by
8 comments, last by Trienco 12 years, 9 months ago
I'm becoming quite familiar with Boost Threads quite fast, their simple enough to use and have the ability to mega-speed code if used right.
Thing is I'm not to sure on how to "use them right" (in quotes because I'm sure there are many situationally beneficial ways to use threads)

I just got past a mild deadlock problem and realized the way I was using threads was causing frequent creation and destruction of threads.
Just for clarification here's a snip of the code:

void Update()
{
IndigoProgram::Update(); //base method

//TODO: Add update logic / Ready Update A, B & C resources

Update_A_Thread = boost::thread(&Program::Update_A, this); //start A
Update_B_Thread = boost::thread(&Program::Update_B, this); //start B
Update_C_Thread = boost::thread(&Program::Update_C, this); //start C
Mutex_A.lock(); //don't continue until A is finished
Mutex_B.lock(); //don't continue until B is finished
Mutex_C.lock(); //don't continue until C is finished

//TODO: Finish with Update A, B & C resources

Mutex_A.unlock(); //free A for next loop
Mutex_B.unlock(); //free B for next loop
Mutex_C.unlock(); //free C for next loop
};


will this constant creation and destruction of threads cause too much overhead?
and I've also noticed, should B for any reason overtake A before A has a chance to lock its mutex there could be deadlocks,
how would I run something along the lines of
while (Mutex_A is unlocked) { } More so would it be necessary or would A always lock before B starts? (A's first line is locking Mutex_A)

Thank you for any and all information,
Bombshell
Advertisement



Mutex_A.lock(); //don't continue until A is finished
Mutex_B.lock(); //don't continue until B is finished
Mutex_C.lock(); //don't continue until C is finished

//TODO: Finish with Update A, B & C resources

Mutex_A.unlock(); //free A for next loop
Mutex_B.unlock(); //free B for next loop
Mutex_C.unlock(); //free C for next loop
};



My first advice would be to not use .lock / .unlock and use one of the RAII based lock-guards that boost provides

As far as frequent creation and destruction, that can get expensive. If you can, you might look at creating a couple of worker threads that you keep running all of the time. These threads should each have a queue of functions to execute (like a functor). If there is nothing in queue, just have the thread sleep then do another check.

[edit]
See this thread for a more detailed example of what I was describing.

"I can't believe I'm defending logic to a turing machine." - Kent Woolworth [Other Space]


will this constant creation and destruction of threads cause too much overhead?

Yeah, it's relatively pricey and should be avoided if you're doing this often. Typically you'll want to have long-running threads and set up queues to send work items and/or messages between them.

Google "consumer producer queue" for some examples.



how would I run something along the lines of
while (Mutex_A is unlocked) { }
[/quote]
Don't do that. You probably want to use condition variables or maybe a boost::barrier. Practicing with those will also help with the creation of producer/consumer queues :)

Quick example in pseudo C++:


void other_thread(bool &stuff_done, mutex &m, condvar &cv)
{
{
lock_guard lk(m);
// do some stuff
// ...
stuff_done = true;
}

// tell the other thread we're done
cv.notify(); // or maybe notify_all() if multiple threads are waiting
}

void main_thread()
{
mutex m;
condvar cv;
bool stuff_done = false;

// Create other thread.
thread other(other_thread, ref(stuff_done), ref(m), ref(cv));

// Wait for other thread to do "the stuff"
lock_guard lk(m);
while (!stuff_done)
cv.wait(lk);

// other thread did "the stuff"
}
thanks,
I'm just setting up a worker class
class Worker
{
boost::thread Thread;
boost::mutex Mutex;

bool active;

void (*F[10])(void);

UINT front;
UINT back;

public:
Worker();
~Worker();
void Work();
void Add();
};


how would I list arguments for queued functions?
I've been googling for about an hour XD
Typically, you'll know what the arguments are at the point where you put work in to your Worker. Therefore your worker can hold boost::function<void ()> objects, which can be created with boost::bind e.g.


void do_something(int, double, const std::string &);

boost::function<void ()> f = boost::bind(do_something, 42, 0.7, "abc");


There are other, less-heavyweight approaches, but that should get you up and running.

EDIT: typo in use of bind().
Okay then!
I've been working on the worker thread and all seems to be going well, I only have 1 issue which judging by the cause is my final hurdle and is only a looping problem... it seems.
The function (the ones held in the queue) is throwing an exception when I instruct it to run, claiming the function is empty.

here is a direct copy and paste of the relevant code.

__declspec(dllexport) Indigo::Worker::Worker()
{
front = 0;
back = 0;
active = true;
Thread = boost::thread(&Worker::Work, this);
}

__declspec(dllexport) Indigo::Worker::~Worker()
{
active = false;
}

__declspec(dllexport) void Indigo::Worker::Work()
{
while (active)
{
if (front != back)
{
Mutex.lock();

F[front]();

front += 1;

if (front >= 10)
{
front -= 10;
}

Mutex.unlock();
}
}
}

__declspec(dllexport) void Indigo::Worker::Add(boost::function<void ()> Func)
{
UINT i = back + 1;
if (i >= 10)
{
i = i - 10;
}

if (i == front)
{
Mutex.lock();
F = Func;
Mutex.unlock();
}
else
{
F = Func;
}

back = i;
}

Just basic information,
active is a bool so that the thread can be ended.
front and back are self explanatory.
F[] is the function array.
Thread and Mutex are workers personal Thread and Mutex.

atm once per program loop Update_A is being added (only as a test, Update_A doesn't do anything with anything)
First, replace those lock and unlock calls with one of the RAII locking structures provided by boost::threads (see example below, I made a typedef for declaring the mutex and a lock that uses a typedef exposed by boost::mutex)

Next, take a look at std::queue. This will be a much better structure for storing the jobs, instead of the static sized array you've got.

Next, instead of storing as a function pointer, use boost or tr1 function to store the functions to process. They have a little more "weight" to them behind the scenes, but should be a little safer to use.

Using the queue, it will be much easier to test if there is a job availble to run, else you can call sleep() or yield() to let the thread not completely eat the cpu when it has nothing to do.


typedef boost::mutex mutex_t;
typedef mutex_t::scoped_lock lock_t;

std::queue<std::tr1::function<void ()> > Jobs;

__declspec(dllexport) Indigo::Worker::Worker()
{
active = true;
Thread = boost::thread(&Worker::Work, this);
}

__declspec(dllexport) Indigo::Worker::~Worker()
{
lock_t Lock(mutex);
active = false;
// Ensure there are no remaining jobs in the queue (this is probably overkill)
while(Jobs.empty() == false)
{
Jobs.pop();
}
}

__declspec(dllexport) void Indigo::Worker::Work()
{
// Checking active will probably need to be atomic
lock_t Lock(mutex);
while (active)
{
if(Jobs.empty() == false)
{
// Execute the first job in the queue
Jobs.front();
// Remove the job from the queue
Jobs.pop();
}
else
{
boost::thread::yield();
}
}
}

__declspec(dllexport) void Indigo::Worker::Add(boost::function<void ()> Func)
{
lock_t Lock(mutex);
Jobs.push(Func);
}


[edit]
Fixed formatting in code and added yield condition.

"I can't believe I'm defending logic to a turing machine." - Kent Woolworth [Other Space]

To avoid busy looping, use a condition variable in your Worker to have it wait for work.

See the "monitor" example in the boost distribution (it's at <boostroot>\libs\thread\example\monitor.cpp), at least in the 1.45 release. It's very close to what you're doing.



__declspec(dllexport) Indigo::Worker::~Worker()
{
lock_t Lock(mutex);


FWIW, if you're ever in a situation where you need lock your internals in a destructor, you're in deep trouble :) ... just think what happens when another thread manages to acquire the lock shortly after the destructor body has exited!
thanks for all the help guys :)
I'll post again should I get anything I can't get my head around.

Until then,
I've been realizing just how much stuff will go into this library.
Any tips on planning out namespaces and cpp files and etc?
if I don't get some idea of how the code will be organized now I'll end up with tangled tough to work with code.
Basicly I'm making a game engine, codenamed "Indigo" so it will be a very very full library.
do you know any programs or anything that would let me make a tree chart like this
wpf-diagram-BusinessObjectDataBinding.jpg
to help plot this all out.
It'd save me huge amounts of time later seeing as I'm barely building the basics and I'm already getting annoyed and lost in my own code.

I've been realizing just how much stuff will go into this library.


One reason to simply use tbb, unless you're just writing it to have done it at least once in your life. A task system, pipelines, thread safe containers, the usual parallel-loops... unless it definitely doesn't fit your needs there is little reason to suffer through it yourself (face it, making it seem to work is mildly tough, making it safe and robust for all corner cases...)
f@dzhttp://festini.device-zero.de

This topic is closed to new replies.

Advertisement