Intel® oneAPI Threading Building Blocks
Ask questions and share information about adding parallelism to your applications when using this threading library.

Trying Out tbb_thread: What's Wrong?

AJ13
New Contributor I
720 Views
Hey,

I've started to explore TBB 2.1.

The following code doesn't work (no output generated), although strace -f shows a new thread is created. What's wrong here? Is stdout not shared with the parent process or something?

AJ


#include
#include

#include

class Tester
{
public:
void operator()()
{
std::cout<< "Hello" << std::endl;
}
};

int main(int argc, char** argv)
{

// Is this neeed?
tbb::task_scheduler_init scheduler;

tbb::tbb_thread(Tester());

}

0 Kudos
19 Replies
robert-reed
Valued Contributor II
720 Views

The technology and syntax may get newer but the old problems just don't go away.

What do you think might be the lifetime of the Tester thread object? How might it compare with the lifetime of main?

I found I had to make two changes to get this code to work. The first was to make the main last long enough to see any results: I added a sleep(10) statement after the tbb_thread call. But that wasn't enough. Now the program hung around for 10 seconds NOT saying "Hello"

It took a second change: lengthening the lifetime of the Tester object by giving it some scope. I rewrote main thusly:

int main(int argc, char ** argv)
{
Tester t;
tbb::tbb_thread my_t(t);
sleep(10);
}

This works just fine on my test. And since tbb_thread, the wrapper, is orthogonal to the TBB task pool, no task_scheduler_init object is required.

0 Kudos
AJ13
New Contributor I
720 Views
Aha, thanks... I thought main() would automatically wait for the spawned thread. I'm using this to do some simple thread spawning and benchmarking. I recently presented a poster at a mini-conference, and a few people really didn't believe that TBB tasks could be faster than threads... no matter how much I tried to explain. The thread interface will let me do a benchmark. In particular I'm currently testing the throughput of atomic operations, just as a mini experiment and this requires threading.

Here's the final code that works:

#include

#include

class Tester
{
public:
void operator()()
{
std::cout<< "Hello" << std::endl;
}
};

int main(int argc, char** argv)
{

Tester t;

tbb::tbb_thread my_thread(t);
my_thread.join();

return 0;

}

0 Kudos
RafSchietekat
Valued Contributor III
720 Views
Why would you expect to see any improvement in the throughput of atomic operations?
0 Kudos
AJ13
New Contributor I
720 Views
It has nothing to do with expecting a performance increase in atomic operations, it has to do with looking at how efficiently atomic operations function when all available cores are attempting to perform them. I'm doing this to understand if wait-free algorithms that depend upon atomic operations are worth the hassle of their implementation.
0 Kudos
xdj159
Beginner
720 Views

I write a piece of code like this:

#include
#include "tbb/concurrent_queue.h"
#include
using namespace std;

class Test2
{
tbb::concurrent_queue& m_queue;
public:
Test2(tbb::concurrent_queue& queue) :
m_queue(queue)
{
}
void operator()()
{
while(true)
{
int i;
if (m_queue.pop_if_present(i))
{
std::cout << i << endl;
}
else
{
tbb::this_tbb_thread::sleep(tbb::tick_count::interval_t(1.0));
}
}
}
};

void CreatThread(tbb::concurrent_queue& queue)
{
Test2 t(queue);
tbb::tbb_thread my_thread(t);
}

int main()
{
tbb::concurrent_queue queue;
for( int i=0; i<10; ++i )
queue.push(i);

CreatThread(queue);
int count = 20;
while(count--)
{
queue.push(count);
Sleep(1000);
}

return 0;
}

It seems that when the main thread terminates, the tbb_thread will terminated. I just want to know when the tbb_thread object my_thread destruct. IMP, the object destructed when the function CreatThread ended. The constructor of tbb_thread will copy a Test2 object t'from t, and start a thread to execute the t'(). Here the object t' is newed object. Is that true?

If it is true, how to make the tbb_thread terminate? And also, when the copied object t' destruct?

0 Kudos
Alexey-Kukanov
Employee
720 Views

The tbb_thread class resembles as closely as possible the std::thread proposal for C++200x standard. And there is no means to cancel/terminate a thread proposed there, thus no way to do that for tbb_thread as well. And this is good in fact; read Herb Sutter's "Interrupt Politely" article to learn why terminating a thread is usually bad idea.

Your code should be extended with a global flag that the thread polls instead of the current endless loop; i.e. replace while(true) with while(!end_flag). Then in main() you should set end_flag:
while(count--)
{
queue.push(count);
Sleep(1000);
}
end_flag = true;
return 0;
}

As your my_thread object was destroyed in CreatThread, the thread got detached and the main thread has no mean to control it. If you want to be sure the thread ended, you should keep the thread object and call join() for it before returning from main().

The copied t' object is destroyed at the end of thread execution, provided that the thread was not interrupted.

0 Kudos
RafSchietekat
Valued Contributor III
720 Views
What difference does this make? The thread is brutally slaughtered either way, without a join from the main thread (this is not Java), and will probably never have the chance to see it coming by inspecting end_flag.

(Added) Sorry, I just had to vent some aggression. Angry smiley [:@] Still, Alexey should have been a bit more assertive about that need to use join(). Perhaps more interesting is the question what need there still is for using tbb_thread in user code, and, if there is, a proposal for a standard way to juggle an arbitrary number of threads and join them all atexit().
0 Kudos
Alexey-Kukanov
Employee
720 Views
Raf, you are right of course that it makes little difference. I was just lazy to re-write the code to e.g. allocate the tbb_thread object inside CreatThread and return a pointer to it to be used later for join. Or I could assert that despite a thread was signalled to end the loop, it might not get chance to finish correctly because it is terminated without a notice when the process ends. So my piece of advice was of limited use. Sorry for that.
0 Kudos
RafSchietekat
Valued Contributor III
720 Views
You just weren't lazy enough (that incomplete piece of code might have been omitted). smiley [:-)]

But what is tbb_thread doing in TBB as a non-internal object without added value (just a stand-in for the future std::thread)? For example, in Java, a Thread by default is not isDaemon(), and a program will continue executing while any non-daemon threads remain. The only basic feature I see Thread lacking is a direct implementation of the preferred pattern to stop a thread, requiring the use of an external variable instead.

And isn't TBB all about not using raw threads? Just publishing tbb_thread seems enough validation for some people to want to use it. Shouldn't anyone wanting to use legacy multithreading code also be required to use legacy means to do so? Who is really afraid of: "CAUTION: Threads are heavy weight on most systems, and running too many threads on a system can seriously degrade performance. Consider using a task based solution instead if practical." (buried somewhere at the end of the introduction in the reference)?

Where I see a remaining need for raw threads is to get around worker thread unavailability, but that should be solved another way, for example:

/* somewhere inside execute() */ {
 tbb::task::scoped_disruption anonymous;
 // with default constructor argument tbb::task::self()
 /* something that may block */ {
 ...
 }
}

This would be differentiatable to provide hints about expected total duration and likelihood and granularity of the disruption, so that the scheduler can do something smart about reusing that hardware thread, possibly based on gathered statistics, so maybe the disruption should be identified somehow for use in a hash table (a macro could pass __FILE__ and __LINE__). scoped_disruption might decide that the arguments are not significant enough to offset the cost of the associated administration, and maybe task affinity could be leveraged to schedule a group of tasks locally to avoid administration bottlenecks.

These were 3 questions (why no added value, why there at all, what else).

0 Kudos
xdj159
Beginner
720 Views

Thanks for your detailed explaination.

I have another question that is there any way to set the priority of the tbb_thread. I mean the tbb_thread maybe have the lower priority than the main thread as the example I posted above. The tbb_thread would not grab the time costing from the main thread to some extent. How to implement that?

Thanks. :)

0 Kudos
Alexey-Kukanov
Employee
720 Views

Raf_Schietekat:
But what is tbb_thread doing in TBB as a non-internal object without added value (just a stand-in for the future std::thread)? <...>
And isn't TBB all about not using raw threads? <...>
Where I see a remaining need for raw threads is to get around worker thread unavailability, but that should be solved another way <...>
These were 3 questions (why no added value, why there at all, what else).

All are good questions.

Since TBB 1.0, we had got constant feedback from customers like "TBB is great, but how to use it in a GUI application?" and similar. A recent blog from Arch Robison "Tasks for Doing and Threads for Waiting"also explains where threads might be more useful. Our customers wanted a more complete cross-platform solution, andthat answers "why at all".

We considered (and still do!)adding support for blocking operations into the tasks. But there were no good, non-contradictorydesign suggested to be implemented in TBB 2.1 timeframe. At some moment, we recognized that we were discussing a form of thread wrappers, and desided that there is no sense to invent the bicycle with yet another interface. We quickly reacheda consensus that implementing the C++ proposal for threads makes most sense at the moment. The closer it follows the future standard, the easier users could switch to std::thread once it is available. That answers "why no added value".

And as I said, we continue thinking about how to add support for blocking operations to the tasking model. We thought of the scoped object approach you outlined above, but did not elaborate it in enough detail. So "what else" is an open question, and you are welcome with ideas.

0 Kudos
Alexey-Kukanov
Employee
720 Views

xdj159,

The std::thread proposal does not describe any way of playing with priority, thus tbb_thread doesn't have it either. You use system-specific API for that.

0 Kudos
James_Grimplin
Beginner
720 Views
Quoting - xdj159

I write a piece of code like this:

#include
#include "tbb/concurrent_queue.h"
#include
using namespace std;

class Test2
{
tbb::concurrent_queue& m_queue;
public:
Test2(tbb::concurrent_queue& queue) :
m_queue(queue)
{
}
void operator()()
{
while(true)
{
int i;
if (m_queue.pop_if_present(i))
{
std::cout << i << endl;
}
else
{
tbb::this_tbb_thread::sleep(tbb::tick_count::interval_t(1.0));
}
}
}
};

void CreatThread(tbb::concurrent_queue& queue)
{
Test2 t(queue);
tbb::tbb_thread my_thread(t);
}

int main()
{
tbb::concurrent_queue queue;
for( int i=0; i<10; ++i )
queue.push(i);

CreatThread(queue);
int count = 20;
while(count--)
{
queue.push(count);
Sleep(1000);
}

return 0;
}

It seems that when the main thread terminates, the tbb_thread will terminated. I just want to know when the tbb_thread object my_thread destruct. IMP, the object destructed when the function CreatThread ended. The constructor of tbb_thread will copy a Test2 object t'from t, and start a thread to execute the t'(). Here the object t' is newed object. Is that true?

If it is true, how to make the tbb_thread terminate? And also, when the copied object t' destruct?


If one slightly modifies the code for the Test2 class as follows:

Test2(tbb::concurrent_queue& queue) :
m_queue(queue)
{
std::cout << "Test2: ctor" << endl;
}
~Test2(void)
{
std::cout << "Test2: dtor" << endl;
}

Then build and run the program you will notice that when the tbb::tbb_thread my_thread(t) statement is executed in CreatThread, the destructor for Test2 is invoked twice, but the constructor is only ever invoked once.

Why is this?

I'm trying to figure out the proper use the tbb_thread class, but this behavior does not appear to be correct.

Jim
0 Kudos
RafSchietekat
Valued Contributor III
720 Views
How about tracing the now implicitly defined copy constructor?
0 Kudos
James_Grimplin
Beginner
720 Views
Quoting - Raf Schietekat
How about tracing the now implicitly defined copy constructor?

Yep...the implicitly defined copy constructor was being invoked twice during the construction and spawning of the tbb_thread, corresponding to the number of invocations of the destructor.
0 Kudos
bez
Beginner
720 Views
I've got question about tbb_thread. How to get thrad id? I've read in Reference manula that there is a method
tbb_thread::id get_id() but i can't use it there are always compilation errors.
And another question How to wait for thread ? I want to wait for all threads to finish they work.
Default created threads are joinable or i must set this myself?
Because when i use tbb::tbb_thread join(mythread) my application finishes work immidiately ( it's not waiting for other threads )?
Please help
0 Kudos
Alexandr_K_Intel1
720 Views
bez,

test_tbb_thread.cpp is a good source of usage examples. Say, in

for (int i=0; i uniq_ids = thrs.get_id();
// skip
for (int i=0; i thrs.join();

we1stgetting ids, and then waiting for the threads to finish. Yes, threads are joinable by default.
0 Kudos
Bartlomiej
New Contributor I
720 Views
Quoting - AJ
I recently presented a poster at a mini-conference, and a few people really didn't believe that TBB tasks could be faster than threads... no matter how much I tried to explain.

I'm sorry to spoil this discussion - I tried to send a private message, but aparently you don't accept them.
I have a question - you claim that tbb tasks can be faster than threads.
What do you mean, exactly ?
Tasks, AFAIK are implemented on top of OS-specific threads. So, literally they cannot be faster than them. But possibly some mechanizms are implemented in TBB in a more efficient way than default API for other libraries. And here goes my question - where have you noticed it ?

Thanks a lot for any information/comment and best regards
Bartlomiej Kubica

0 Kudos
bez
Beginner
720 Views
bez,

test_tbb_thread.cpp is a good source of usage examples. Say, in

for (int i=0; i uniq_ids = thrs.get_id();
// skip
for (int i=0; i thrs.join();

we1stgetting ids, and then waiting for the threads to finish. Yes, threads are joinable by default.
Thank You. This file helped a lot :)


0 Kudos
Reply