Sign in to follow this  
Bombshell93

[C++] A few Boost Thread questions

Recommended Posts

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:
[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
};[/code]

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
[code]while (Mutex_A is unlocked) { }[/code] 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

Share this post


Link to post
Share on other sites
[quote name='bombshell93' timestamp='1311111803' post='4837637']
[code]

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
};[/code]
[/quote]

My first advice would be to not use .lock / .unlock and use one of the RAII based [url="http://www.boost.org/doc/libs/1_47_0/doc/html/thread/synchronization.html#thread.synchronization.locks"]lock-guards[/url] 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 t[url="http://www.gamedev.net/topic/582320-c-threads-are-standard-so-where-is-the-mutex-class/page__view__findpost__p__4706379"]his thread[/url] for a more detailed example of what I was describing. Edited by Rattrap

Share this post


Link to post
Share on other sites
[quote name='bombshell93' timestamp='1311111803' post='4837637']
will this constant creation and destruction of threads cause too much overhead?
[/quote]
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.


[quote]
how would I run something along the lines of
[code]while (Mutex_A is unlocked) { }[/code]
[/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++:

[code]
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"
}
[/code]

Share this post


Link to post
Share on other sites
thanks,
I'm just setting up a worker class
[code]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();
};[/code]

how would I list arguments for queued functions?
I've been googling for about an hour XD

Share this post


Link to post
Share on other sites
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.

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

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

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

EDIT: typo in use of bind().

Share this post


Link to post
Share on other sites
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.

[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[i] = Func;
Mutex.unlock();
}
else
{
F[i] = Func;
}

back = i;
}[/code]
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)

Share this post


Link to post
Share on other sites
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 [url="http://www.cplusplus.com/reference/stl/queue/"]std::queue[/url]. 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 [url="http://www.boost.org/doc/libs/1_47_0/doc/html/boost_tr1/subject_list.html#boost_tr1.subject_list.function"]function[/url] 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 [url="http://www.boost.org/doc/libs/1_47_0/doc/html/thread/thread_management.html#thread.thread_management.thread.sleep"]sleep()[/url] or [url="http://www.boost.org/doc/libs/1_47_0/doc/html/thread/thread_management.html#thread.thread_management.thread.yield"]yield()[/url] to let the thread not completely eat the cpu when it has nothing to do.
[code]

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);
}
[/code]

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

Share this post


Link to post
Share on other sites
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.

[quote name='Rattrap' timestamp='1311195047' post='4838130']
[code]
__declspec(dllexport) Indigo::Worker::~Worker()
{
lock_t Lock(mutex);
[/code]
[/quote]
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!

Share this post


Link to post
Share on other sites
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
[img]http://www.syncfusion.com/content/en-US/products/feature/user-interface-edition/wpf/diagram/img/wpf-diagram-BusinessObjectDataBinding.jpg[/img]
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.

Share this post


Link to post
Share on other sites
[quote name='bombshell93' timestamp='1311211548' post='4838228']
I've been realizing just how much stuff will go into this library.
[/quote]

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...)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this