![]() |
|
This thread is locked; no one can reply to it.
![]() ![]() |
1
2
|
std::thread and std::vector confusion? |
Chris Katko
Member #1,881
January 2002
![]() |
1
2void task1(std::string msg)
3{
4 printf("task1 says: %s\n", msg.c_str());
5}
6
7void main()
8 {
9 std::vector<std::thread> threads;
10
11 for(int i = 0; i < 5; ++i)
12 {
13 std::thread t1(task1, "Hello");
14 threads.push_back(t1); //does not COMPILE
15
16 threads.push_back(std::thread(task1, "Hello")); //WORKS
17 }
18 printf("Started.\n");
19
20 for(auto& t : threads)
21 {
22 t.join();
23 }
24
25 printf("Finished.\n");
26 }
Any ideas? Also, I thought you had to "join" the thread to actually run it yet oddly enough they seem to run before join (they print before it prints "Started.") and I also thought join meant "pause main, run task until completion and then continue here." which would also imply they should be after started, and in order. [edit] It should be noted I'm using GCC 4.9.2 on Linux which may have faulty support for std::threads. I learned that the damn hard way after their regex was completely broken while I tried to learn it. -----sig: |
Edgar Reynaldo
Major Reynaldo
May 2007
![]() |
I am making a wild guess here but it may be because the first std::thread is created on the stack and has to be copied but the second thread is a temporary object. It may be using move semantics in the push_back function when there is an rvalue instead of an lvalue. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Chris Katko
Member #1,881
January 2002
![]() |
Please see my note, I posted before I fixed it. The first one^H^H^HLINE doesn't compile with a huge template error about allocater. 1In file included from /usr/include/x86_64-linux-gnu/c++/4.9/bits/c++allocator.h:33:0,
2 from /usr/include/c++/4.9/bits/allocator.h:46,
3 from /usr/include/c++/4.9/string:41,
4 from main.cpp:4:
5/usr/include/c++/4.9/ext/new_allocator.h: In instantiation of void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = std::thread; _Args = {const std::thread&}; _Tp = std::thread]:
6/usr/include/c++/4.9/bits/alloc_traits.h:253:4: required from static std::_Require<typename std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::type> std::allocator_traits<_Alloc>::_S_construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = std::thread; _Args = {const std::thread&}; _Alloc = std::allocator<std::thread>; std::_Require<typename std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::type> = void]
7/usr/include/c++/4.9/bits/alloc_traits.h:399:57: required from static decltype (_S_construct(__a, __p, (forward<_Args>)(std::allocator_traits::construct::__args)...)) std::allocator_traits<_Alloc>::construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = std::thread; _Args = {const std::thread&}; _Alloc = std::allocator<std::thread>; decltype (_S_construct(__a, __p, (forward<_Args>)(std::allocator_traits::construct::__args)...)) = <type error>]
8/usr/include/c++/4.9/bits/stl_vector.h:918:34: required from void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::thread; _Alloc = std::allocator<std::thread>; std::vector<_Tp, _Alloc>::value_type = std::thread]
9main.cpp:33:23: required from here
10/usr/include/c++/4.9/ext/new_allocator.h:120:4: error: use of deleted function std::thread::thread(const std::thread&)
11 { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
12 ^
13In file included from main.cpp:5:0:
14/usr/include/c++/4.9/thread:126:5: note: declared here
15 thread(const thread&) = delete;
16 ^
And per my thoughts, this SO post confirms what join does. But I'll look into your comments. I'm not used to people replying so fast! -----sig: |
Edgar Reynaldo
Major Reynaldo
May 2007
![]() |
You don't have to join a thread to run, but you do have to start it (in this case perhaps automatically). If the threads have already finished though, join won't have any effect. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
SiegeLord
Member #7,827
October 2006
![]() |
Yeah, it's what Edgar said. The copy constructor of std::thread is deleted (as it makes no sense to copy a thread, I suppose), so you need to move the variable using std::move. I.e. you'd do threads.push_back(std::move(t1));. "For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18 |
Thomas Fjellstrom
Member #476
June 2000
![]() |
I suppose it's helpful that c++11 does that move stuff, but I'd still avoid storing a static std::thread in a container. I generally store non trivial objects in std containers as pointers. -- |
SiegeLord
Member #7,827
October 2006
![]() |
The idiomatic pointer type for this purpose would be std::unique_ptr which also needs to be moved "For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18 |
beoran
Member #12,636
March 2011
|
Why not use Allegro's threads in stead? |
Thomas Fjellstrom
Member #476
June 2000
![]() |
SiegeLord said: The idiomatic pointer type for this purpose would be std::unique_ptr which also needs to be moved Eh. I also tend not to mess with std::unique_ptr much either. -- |
l j
Member #10,584
January 2009
![]() |
It might also be useful to note that moving a variable will destroy the original and should not be used afterwards, much like in Rust, except Rust enforces it and C++ should but doesn't. Afaik, all std::threads must end before they go out of scope, so you should call thread::join before they get destroyed. for(int i = 0; i < 5; ++i) { std::thread t1(task1, "Hello"); threads.push_back(t1); // Tries to invoke the deleted copy constructor. threads.push_back(std::thread(task1, "Hello")); // Returns a rvalue. // Gets moved implicitly, you might want to use emplace_back to construct the thread directly in the vector and avoid any unnecessary copies. std::thread t2(task1, "Hello"); threads.push_back(std::move(t2)); // Explicitly move t2 // t2 should be considered 'consumed' and no longer be used. } Took me a little while to figure these new move semantics out, but once you understand them they'll seem very elegant, at least that was my experience.
|
Kitty Cat
Member #2,815
October 2002
![]() |
taron said: It might also be useful to note that moving a variable will destroy the original and should not be used afterwards IIRC, moving a variable puts the original into a cleared/destructible state. The object is destructed at the end of its scope as normal, so it is still valid after a move. What its actual state is, though, is left undefined (you may or may not be able to do anything with it except let it destruct). -- |
l j
Member #10,584
January 2009
![]() |
I realize my use of destroy might have been a little unclear, especially when I've used it with 2 different implied meanings. But what I meant with the first instance of destroy is that it effectively can end up destroying the contents of the original and leaves it in an unspecified state, not that it calls the destructor, so you aren't supposed to use it in that state. Apparently an object you move from is called an xvalue (expiring value) now.
|
Chris Katko
Member #1,881
January 2002
![]() |
I guess I understand std::move now. std::forward is confusing. But geez. Everything keeps getting more and more complex. I mean... std::namespace names for actual language semantics now? I mean, what's next, std::addition? I was going to joke and say std::cast, std::reference, std::pointer or std::const, but those actually exist with slight variations in spelling. Which means you have to use the std::namespace to use the most basic functionality of C++. I guess that's not automatically a "bad" thing if you're "all in" for C++, but it calls into question the philosophy of "only pay for what you use" if you're including more and more complex parts of the STL just to run a program. -----sig: |
SiegeLord
Member #7,827
October 2006
![]() |
Nothing stops you from just doing what std::move does manually: threads.push_back((std::thread&&)t1);
"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18 |
Chris Katko
Member #1,881
January 2002
![]() |
Okay, I think I finally get what join means. A std::thread will start execution the moment you create it. However, if you want to pause the calling thread until a child thread is finished, you would call join to ensure execution of the parent doesn't continue until after the child finishes. That is, if I have asset_manager.load_data() dispatching a bunch of worker tasks representing the loading of game assets, I certainly don't want to have asset_manager.load_data() returning or making decisions until that data has finished loading. So when I'm playing around with tiny functions, they're very likely to finish before main() even makes a decision about them. -----sig: |
Peter Hull
Member #1,136
March 2001
|
Apologies if you already know this, but you can put using namespace std; just under your #includes to bring everything from std:: into scope (so you can put vector instead of std::vector) (link) - but bear in mind your definitions might clash with something from std::. You can also do it on a name-by-name basis, e.g using std::vector; using std::thread;
And apologies again for thread hijack, but when iterating for(auto& t : threads) { t.join(); } I'm sure I saw somewhere it should be auto&& but I don't know why or what this means. Any ideas? Pete
|
Thomas Fjellstrom
Member #476
June 2000
![]() |
auto detects the type from the rhs, and && tells it to be an rvalue reference. Which can let you take a reference of a rvalue, or temporary, it's used in implementing move semantics and such. -- |
SiegeLord
Member #7,827
October 2006
![]() |
You don't want an auto&& in a for loop unless you're planning on moving out those elements. "For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18 |
beoran
Member #12,636
March 2011
|
No really, what kind of an insane, cryptic mess is this all? I really can't see how the C++ way with all those problems is easier than just a C array with malloc, realloc, and pointers to Allegro threads... |
SiegeLord
Member #7,827
October 2006
![]() |
The only reason C++ needs this nonsense is because it has overloadable assignment operator. For C, you could say it moves by default! "For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18 |
beoran
Member #12,636
March 2011
|
Yeah, I see, now that you mention it... That's why most languages that allow operator overloading still don' t allow the assignment operator to be overloaded. Ruby comes to mind as such a language... And yes, C assignment copies the data by default, which is the correct thing to do for a low level language. |
Chris Katko
Member #1,881
January 2002
![]() |
Every time I think I'm getting a grasp on modern C++, it opens up a new steaming pile of complexity. -----sig: |
Thomas Fjellstrom
Member #476
June 2000
![]() |
I've still not looked deep into move semantics. So far haven't needed it -- |
Peter Hull
Member #1,136
March 2001
|
SiegeLord said: You don't want an auto&& in a for loop unless you're planning on moving out those elements. I think maybe it moves only if you give it an rvalue, otherwise it collapses `&&` to `&` and just takes a reference. Have a look at beoran said: No really, what kind of an insane, cryptic mess is this all? Indeed. If you look into `<memory>` or any of the standard headers it's all very complicated stuff - unfortunately for C++ (IMO) you do need to understand a little of how it works to use it efficiently - you can't just leave it all to the library writers. Thomas Fjellstrom said: I've still not looked deep into move semantics. So far haven't needed it I mostly use it for things like MyClass inst; //... do something with inst ... myvector.push_back(std::move(inst)); without the move it'll make a copy of inst to put into the vector, then inst itself will be destroyed some time later. If inst is big/complicated that could be expensive.
|
l j
Member #10,584
January 2009
![]() |
I personally don't use auto&& outside of template or generic lambdas.
|
|
1
2
|