[thread] Do we need thread_group now?

Hi, now that we have Boost.Container and Boost.Thread (trunk) uses Boost.Move, boost::thread_group has less sense. I was thinking on replacing it by some generic algorithms as join_all, interrupt_all that work with generic containers so that the user can use the best containers adapted to her own needs. typedef boost::container::vector<boost::thread> thread_vector; { thread_vector threads; for (int i = 0; i < 10; ++i) { threads.push_back(boost::thread(&increment_count)); } join_all(threads); } { thread_vector threads; for (int i = 0; i < 10; ++i) { threads.push_back(boost::thread(&increment_count)); } interrupt_all(threads); } Could boost::thread_group be deprecated once these generic algorithm are released? Or, is there something really useful on the thread_group class that needs to be preserved or even improved? Best, Vicente

Vicente J. Botet wrote:
now that we have Boost.Container and Boost.Thread (trunk) uses Boost.Move, boost::thread_group has less sense.
I was thinking on replacing it by some generic algorithms as join_all, interrupt_all that work with generic containers so that the user can use the best containers adapted to her own needs.
[snip]
Could boost::thread_group be deprecated once these generic algorithm are released? Or, is there something really useful on the thread_group class that needs to be preserved or even improved?
I like your idea. thread_group is an odd thing. If you want access to the threads in the group, you must track the pointers in another container because you can't iterate a thread_group. To remove a thread, you also must retain the pointer versus using its ID, for example. thread_group is an odd container, so I'm in favor of supporting the group functionality using ordinary containers. _____ Rob Stewart robert.stewart@sig.com Software Engineer using std::disclaimer; Dev Tools & Components Susquehanna International Group, LLP http://www.sig.com ________________________________ IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

On Apr 10, 2012, at 1:32 PM, Vicente J. Botet Escriba wrote:
join_all(threads);
Is it worth the electrons to turn this into an algorithm? std::for_each(threads.begin(), threads.end(), [](thread& t) {t.join();}); or even simpler (really I think std::for_each is probably obsolete): for (auto& t : threads) t.join(); Howard

Le 14/04/12 01:12, Howard Hinnant a écrit :
On Apr 10, 2012, at 1:32 PM, Vicente J. Botet Escriba wrote:
join_all(threads);
Is it worth the electrons to turn this into an algorithm?
std::for_each(threads.begin(), threads.end(), [](thread& t) {t.join();});
or even simpler (really I think std::for_each is probably obsolete):
for (auto& t : threads) t.join();
Hi Howard, you are right for join_all and for c++11 compilers. But in order to make the code portable, join_all could help. In addition, Boost.Thread provides timed join functions and I suspect that joining N threads for a given duration is less evident and subject to error on the user side. template <typename TC, typename C, typename D> bool try_join_all_until(TC & cont, chrono::time_point<C,D> const& tp) { for (auto& t : cont) { if (! t.try_join_until(d, tp)) return false; } return true; } Or a better way to do it. template <typename TC, typename R, typename P> bool try_join_all_for(TC & cont, chrono::duration<R,P> const& d) { chrono::steady_clock::time_point tp = chrono::steady_clock::now()+d; return try_join_all_until(cont, tp); } I don't know it adding these simple algorithms would be useful to the Boost community if thread_group is deprecated. I could live without them, my main concern is to deprecate thread_group. What do you and others think? Anthony? Best, Vicente

On Sat, Apr 14, 2012 at 2:28 AM, Vicente J. Botet Escriba < vicente.botet@wanadoo.fr> wrote:
Le 14/04/12 01:12, Howard Hinnant a écrit :
On Apr 10, 2012, at 1:32 PM, Vicente J. Botet Escriba wrote:
join_all(threads);
Is it worth the electrons to turn this into an algorithm?
std::for_each(threads.begin(), threads.end(), [](thread& t) {t.join();});
or even simpler (really I think std::for_each is probably obsolete):
for (auto& t : threads) t.join();
Hi Howard,
you are right for join_all and for c++11 compilers. But in order to make the code portable, join_all could help.
In addition, Boost.Thread provides timed join functions and I suspect that joining N threads for a given duration is less evident and subject to error on the user side.
template <typename TC, typename C, typename D> bool try_join_all_until(TC & cont, chrono::time_point<C,D> const& tp) { for (auto& t : cont) { if (! t.try_join_until(d, tp)) return false; } return true; }
Or a better way to do it.
template <typename TC, typename R, typename P> bool try_join_all_for(TC & cont, chrono::duration<R,P> const& d) { chrono::steady_clock::time_**point tp = chrono::steady_clock::now()+d; return try_join_all_until(cont, tp); }
I don't know it adding these simple algorithms would be useful to the Boost community if thread_group is deprecated. I could live without them, my main concern is to deprecate thread_group.
What do you and others think? Anthony?
If a boost::container::vector (or whatever) of boost::thread's (with a set of algorithms as free functions, e.g., join_all) can function essentially the same as boost::thread_group, then I think that makes sense. - Jeff

On Sat, Apr 14, 2012 at 11:28 AM, Vicente J. Botet Escriba <vicente.botet@wanadoo.fr> wrote:
I don't know it adding these simple algorithms would be useful to the Boost community if thread_group is deprecated. I could live without them, my main concern is to deprecate thread_group.
What do you and others think? Anthony?
Does Boost have guidelines for deprecation / backwards compatiblity? What's the point of deprecating this stuff? -- Olaf

Le 14/04/12 22:09, Olaf van der Spek a écrit :
On Sat, Apr 14, 2012 at 11:28 AM, Vicente J. Botet Escriba <vicente.botet@wanadoo.fr> wrote:
I don't know it adding these simple algorithms would be useful to the Boost community if thread_group is deprecated. I could live without them, my main concern is to deprecate thread_group.
What do you and others think? Anthony? Does Boost have guidelines for deprecation / backwards compatiblity? What's the point of deprecating this stuff?
Hi, there are no official guidelines. You can look at https://svn.boost.org/trac/boost/wiki/Guidelines/MaintenanceGuidelines#Depre... (last modified 2 years ago) that gives some ideas. My idea of deprecating a feature goes on 3 stages: * 1st. The feature is announced to be deprecated. The features is available by default. If possible the deprecated features are moved to a new file so that a warning is reported when this file is use. Users can configure the library at the header level so that the deprecated feature is not available and the warning removed or just request the warning to be removed but let the feature available. This allows preventive users to switch to the alternative path. Maintenance continue. * 2nd. The feature is announced as deprecated. The features is NOT available by default. Users can configure the library at the header level so that the deprecated feature IS available. This alert not preventive users that the feature will be removed soon, as the program doesn't compile without a change on the configuration. Maintenance is stopped. * 3rd. The feature is removed so NOT available at all. I think that a period of 9 month/1 year between each stage is a good compromise. That lets 18 months/2 years to switch between the announce and the removal. This let also the time to see if the alternative is a real one. The period could be extended if the change is large. The thread_group class could be in this case and two periods of 9 month could be enough. A different history occurs when the feature is not removed but changed, the library is not header only and the feature is implemented on the .cpp files. Here two more stages can be considered and not always possible * 0th. Move the feature out of the .cpp files, so that it could be configured without rebuilding the library. Note that the .cpp files could not use this feature. * 3rdbis. The old behavior of the feature is removed so NOT available at all. * 4th. The new feature could be moved to the .cpp files. E.g. I have started to implement some breaking changes on Boost.Thread as e.g. an incompatibility with the C++11 semantics of the thread destructor. Boost.Thread detach the thread at destruction time, C++11 calls std::terminate if the thread is joinable at destruction time. I have already started the stage 0th and I would like to pass to the stage 1st for the next release. Of course before deprecating a feature we need to be sure that an alternative is widely available. An example of this case are the time related functions of Boost.thread. Next release will include the chrono-based interface that will be the alternative. But there is at least a compiler that don't support Boost.Chrono at all (vacpp). So deprecating the old time based functions could not be acceptable for people that uses this compiler as they can not replace it. When the change has a deep impact on the interface, design and/or behavior, a new version with a different namespace should be considered. I'm sure others have more ideas on how to deprecate a feature. Best, Vicente

On Sunday, April 15, 2012 08:22 AM, Vicente J. Botet Escriba wrote:
E.g. I have started to implement some breaking changes on Boost.Thread as e.g. an incompatibility with the C++11 semantics of the thread destructor. Boost.Thread detach the thread at destruction time, C++11 calls std::terminate if the thread is joinable at destruction time. I have already started the stage 0th and I would like to pass to the stage 1st for the next release.
In this case, it would be useful to have RAII classes for joining and detaching to prevent std::terminate from being called. In fact, I think Anthony writes in his book that scope based thread_joiner should be implemented by the user since it didn't make the standard. Ben

Le 15/04/12 09:46, Ben Pope a écrit :
On Sunday, April 15, 2012 08:22 AM, Vicente J. Botet Escriba wrote:
E.g. I have started to implement some breaking changes on Boost.Thread as e.g. an incompatibility with the C++11 semantics of the thread destructor. Boost.Thread detach the thread at destruction time, C++11 calls std::terminate if the thread is joinable at destruction time. I have already started the stage 0th and I would like to pass to the stage 1st for the next release.
In this case, it would be useful to have RAII classes for joining and detaching to prevent std::terminate from being called. In fact, I think Anthony writes in his book that scope based thread_joiner should be implemented by the user since it didn't make the standard.
Hi, The breaking change is due to an incompatibility with the C++ standard. I think it is better to behave like the standard and add something on top of it. Kevlin Henney proposed a different design for C++ threads http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1883.pdf "Preliminary Threading Library Proposal for TR2" that was based on the idea that the creation of a thread return always a RAII movable joiner. { joiner j = thread(...); } The standard use of futures and async is quite close { future<T> ft = async(...); } but here the thread is no more managed by the user. Long time ago, I implemented something like that on top of Boost.Thread on TBoost.Async that is a mix of a RAII joiner and a future. The class had a parameter to control the destruction behavior enum type { do_terminate, do_join, do_detach }; ( See <https://svn.boost.org/svn/boost/sandbox/async/libs/async/doc/html/toward_boost_async/reference/ae_act_models_reference.html#toward_boost_async.reference.ae_act_models_reference.threader_hpp.template_class__shared_joiner___>). Unfortunately this has not been updated even tested since a long time also and the documentation need some work also :( IMO, TBoost.Async provides an interesting framework for asynchronous calls. Let me know if there is an interest to resurecting this project. I will comeback to as soon as possible to check it works yet and adapt it to the new standard interface. Best, Vicente

Le 15/04/12 11:12, Vicente J. Botet Escriba a écrit :
Let me know if there is an interest to resurecting this project. I will comeback to as soon as possible to check it works yet and adapt it to the new standard interface.
I have made some minor changes to make it work (partially tp doesn't works yet). I have found some bug on the new macros i introduced in Boost.Thread when Boost.Move is not used. I guess I will need to add some regression tests to Boost.Thread to check backward compatibility when Boost.Move is not used :( Committed revision 77985. Committed revision 77986. Best, Vicente

On 15.04.2012 13:12, Vicente J. Botet Escriba wrote:
Let me know if there is an interest to resurecting this project. I will comeback to as soon as possible to check it works yet and adapt it to the new standard interface.
Usually joining a thread is not enough. Sometimes, you need to tell the thread that it should quit. As long as we don't have standard way to tell the thread that it should quit (something like PostThreadMessage(WM_QUIT)), RAII-joiner is only applicable for threads that do some computations and quit. Maybe we could have RAII-wrapper that destroys asio::io_service::work and then joins the thread?

On Sun, Apr 15, 2012 at 2:22 AM, Vicente J. Botet Escriba <vicente.botet@wanadoo.fr> wrote:
I think that a period of 9 month/1 year between each stage is a good compromise. That lets 18 months/2 years to switch between the announce and the removal. This let also the time to see if the alternative is a real one.
I think that's way too short. There should be at least 3 years between the new alternative being widely available (so not just Boost having released it, but it being part of major Linux distro's too) and the old feature being deprecated. Olaf

Le 14/04/12 22:09, Olaf van der Spek a écrit :
On Sat, Apr 14, 2012 at 11:28 AM, Vicente J. Botet Escriba <vicente.botet@wanadoo.fr> wrote:
I don't know it adding these simple algorithms would be useful to the Boost community if thread_group is deprecated. I could live without them, my main concern is to deprecate thread_group.
What do you and others think? Anthony? Does Boost have guidelines for deprecation / backwards compatiblity? What's the point of deprecating this stuff?
Oh, I forgot to respond to your second question. Very good question. thread_group was not designed with move semantics in mind, neither with shared ownership. Now that we have move semantics in Boost the user can do better designs. If thread_group stay there, there will be always someone that will request to improve this class while using a specific container will provide whatever she needs. Perhaps some algorithms could be added to make the user life easier. In addition, the fact the class is there let think beginners that this is the way to go, while using standard containers let them control better what they can do. Of course, the documentation could help on this without deprecating the feature. Best, Vicente

Vicente J. Botet wrote:
Le 14/04/12 01:12, Howard Hinnant a écrit :
On Apr 10, 2012, at 1:32 PM, Vicente J. Botet Escriba wrote:
join_all(threads);
Is it worth the electrons to turn this into an algorithm?
std::for_each(threads.begin(), threads.end(), [](thread& t) {t.join();});
or even simpler (really I think std::for_each is probably obsolete):
for (auto& t : threads) t.join();
you are right for join_all and for c++11 compilers. But in order to make the code portable, join_all could help.
A newcomer to the library will not know what the correct formulation is to join all threads, or even that such a thing is possible or appropriate. There will be other variations than those you've shown for joining all threads in a container, which makes joining code less obvious. An algorithm provides a means to readable code, without variation, that immediately conveys the caller's intent. Add portability to the mix and providing join_all makes a lot of sense.
In addition, Boost.Thread provides timed join functions and I suspect that joining N threads for a given duration is less evident and subject to error on the user side.
Yet another reason to provide join_all: symmetry. _____ Rob Stewart robert.stewart@sig.com Software Engineer using std::disclaimer; Dev Tools & Components Susquehanna International Group, LLP http://www.sig.com ________________________________ IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.
participants (7)
-
Ben Pope
-
Howard Hinnant
-
Ivan Sorokin
-
Jeffrey Lee Hellrung, Jr.
-
Olaf van der Spek
-
Stewart, Robert
-
Vicente J. Botet Escriba