tag:blogger.com,1999:blog-7101933101966798446.post2939816210857481217..comments2024-03-28T10:33:06.910-07:00Comments on The View from Aristeia: ThreadRAII + Thread Suspension = Trouble?Scott Meyershttp://www.blogger.com/profile/05280964633768289328noreply@blogger.comBlogger34125tag:blogger.com,1999:blog-7101933101966798446.post-88782595133722543562014-01-06T12:30:54.172-08:002014-01-06T12:30:54.172-08:00I mean exception safety, of course (not thread).I mean <b>exception</b> safety, of course (not thread).bert Dvorniknoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-53843329360099771502014-01-06T12:26:02.797-08:002014-01-06T12:26:02.797-08:00@Scott Meyers: yes, I think that's spot on.
I...@Scott Meyers: yes, I think that's spot on.<br /><br />I should have thought through my last post better. =) I realized after I posted that in all but the most benign cases, the library/language don't have a good way to do the right thing. (If you don't believe this, think about the case where a thread is blocking on a join of another thread.)<br /><br />That said, it does feel like a really solid set of programming practices to deal with prevention of termination may need better tools and paradigms than have emerged thus far. The comparison with thread safety seems very apt, though I guess only time will tell. Given how fast and how well C++ has been evolving, I'm feeling pretty hopeful.bert Dvorniknoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-82650243441849737942014-01-02T12:21:19.100-08:002014-01-02T12:21:19.100-08:00@bert Dvornik: I think the general problem of what...@bert Dvornik: I think the general problem of what to do about threads that prevent termination (due to blocking calls or otherwise) is likely to be bigger than just the library or even the language. Well-behaved software will have to be written in ways that permit orderly thread shutdown. I suspect it will turn out to be akin to writing exception-safe code: achieving the desired behavior will require particular programming techniques, and while the language or the library can provide useful building blocks, it will still be up to software developers to use those building blocks in a correct fashion.Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-23034114009780634352014-01-02T10:48:51.588-08:002014-01-02T10:48:51.588-08:00I see two interesting problems here. The first is...I see two interesting problems here. The first is the specific problem of starting the thread suspended, and I like the direction Tiago and Scott have been taking.<br /><br />But this is of course just a special case of the much broader problem of coping with long-blocking threads interfering with thread termination. A truly satisfying RAII thread wrapper would have some way of controlling interaction with <i>all sorts</i> of resources that may block for a long time, not just limited to futures used in the thread.<br /><br />Maybe I'm a pessimist, but this seems like an impossible nut to tackle outside of the C++ standard library itself (which is not to say that a C++ library based solution would be easy). In C++2x, maybe? =)bert Dvorniknoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-11277968894969752292013-12-31T12:12:32.826-08:002013-12-31T12:12:32.826-08:00@Tiago Gomes: Right now, I'm thinking that a b...@Tiago Gomes: Right now, I'm thinking that a better name for delayed_thread is latched_thread, and the corresponding class using a shared_future would be something like shared_latch_thread. latched_thread would offer a share member function, thus making it possible to create a shared_latch_thread from a latched_thread, just like you can currently create a shared_future from a future. Internally, the shared_latch_thread would contain a shared_ptr to a promise.<br /><br />I don't care for the idea of having clients pass in a shared_future, because the promise/future implementation should be hidden from clients, just like the different kinds of shared states are hidden from clients using promises and futures/shared_futures.<br /><br />I'm hoping to work on this later today, but I may not have time to get to it.<br /><br />ScottScott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-76601279399187960602013-12-31T11:54:34.839-08:002013-12-31T11:54:34.839-08:00@Scott Meyers: I agree with your point about not o...@Scott Meyers: I agree with your point about not offering "dismiss" for the user, it would probably cause more harm than good.<br /><br />After giving it some though, I do think we should push strongly towards std::thread interface and behavior, not only to work nicely in contexts where std::thread-like objects are required, as with ThreadRAII, but also for consistency sake. Providing behavior as close to std::thread's behavior as possible would make using delayed_thread simpler for users of the former.<br /><br />With that in mind, I modified the code I was using to strip ThreadRAII behavior from delayed_thread and still keep it std::thread-like.<br />Here is a live demo code of this implementation which also shows ThreadRAII compatibility:<br />http://rextester.com/AUUTS48533<br /><br />About your concern of std::thread-like interface making it hard to implement the possibility of triggering/executing multiple delayed_thread with a single call, I don't think that would be the case.<br /><br />Keeping the interface as it is, I think all we would have to do is add to some constructors the possibility of passing in a std::shared_future, maybe wrapped in some meaningful class such as trigger_receiver. This way a user can provide shared_futures from a single promise, maybe wrapped as something like delayed_thread_trigger, to multiple delayed_thread and trigger/execute all at once by setting the promise's value.<br /><br />The only problem I'm still somewhat unable to solve, with c++11, is how to offer both this possibilities of triggering/executing at the same time, as we can't just do "if ( m_shared_future.get( ) || should_execute.get( ) ) { op(); }" because it would block until first value or both values are set.<br /><br />I would envision something like using shared_future::then, if/when the proposal actually passes, to call "trigger"/"execute" upon definition of outer promise's value and this way not changing our current lambda definition.<br /><br />What are your thoughts on those subjects ?Anonymoushttps://www.blogger.com/profile/09018205426659606367noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-43789440356739936942013-12-30T21:48:41.443-08:002013-12-30T21:48:41.443-08:00@Tiago Gomes: I don't think you want to offer ...@Tiago Gomes: I don't think you want to offer "dismiss" as an option of what to do to the std::thread in the delayed_thread destructor, because by the time that destructor is running, the std::thread may have been "unsuspended" (via a call to trigger/execute), and in that case, your only available actions are the ones offered by std::thread, i.e, join or detach. <br /><br />The notion of dismissal makes sense only before a call to trigger/execute, and before such a call, the notion of joining or detaching isn't really meaningful, because the thread is blocked waiting for a call to trigger/execute.I think the current semantics are the proper ones: dismissal of the function to be run on the std::thread if the delayed_thread is destroyed before trigger/execute has been called, and either join or detach (at the client's choice) if the delayed_thread is destroyed after trigger/execute has been called.<br /><br />In the meantime, on further reflection, I think it makes sense to replicate the std::thread API in delayed_thread. The other option would be to provide just enough API to let delayed_thread do its work (notably including the special member functions), plus offer a "get" member function to access the underlying std::thread, but then it would be harder to use a delayed_thread in a context where a std::thread-like object was required. <br /><br />ScottScott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-26034993504837898292013-12-30T10:27:21.692-08:002013-12-30T10:27:21.692-08:00@Tiago Gomes: The standard specifies the behavior ...@Tiago Gomes: The standard specifies the behavior of the variadic std::thread constructor more or less this way,<br /><br /> template <class F, class ...Args> explicit thread(F&& f, Args&&... args);<br /><br /> The new thread of execution executes decay_copy(std::forward<F>(f)), decay_copy(std::forward<Args>(args))...).<br /><br />so I think that's how you'd have to write the implementation. The tricky part is coming up with a good calling API. Your design interposes on_destruction between f and args, which I find odd. The simplest solution would be to put on_destruction first. <br /><br />I like your idea of separating delayed_thread from ThreadRAII. It would be nice if ThreadRAII would work with anything supporting a std::thread-like API, and of course the general idea of separating concerns argues for it, too.<br /><br />I'm less enthusiastic about having to replicate the std::thread API for delayed_thread, especially because one thing I didn't mention in the post is that I'd ultimately like to make it possible for a single promise to control multiple delayed_threads, i.e., for a client to create a bunch of delayed_threads, then trigger them all with a single call to what Christopher Hayden called "trigger" and what your code calls "execute". Implementing this behavior is easy (just use a std::shared_future), but I'm afraid it will lead to two versions of delayed_thread, much like we currently have both std::future and std::shared_future. We'd then have std::thread, std::delayed_thread, and std::shared_delayed_thread, and that doesn't seem very pretty. <br /><br />I'll play around with this more today and post again. I encourage you to do the same.Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-28260317204992890602013-12-29T17:39:36.279-08:002013-12-29T17:39:36.279-08:00@Scott Meyers: I've being toying with this cla...@Scott Meyers: I've being toying with this class throughout the day and I think now it's covering all the problems mentioned before.<br /><br />Here is the live code demo: http://rextester.com/EXCC9898<br /><br />I tried to mimic std::thread as much as possible, including behavior definition for moved-from objects.<br /><br />I think the way the constructor which takes a function is setup right now makes it impossible for std::promise::get_future to throw an exception, as it's just being created and the call to get_future is being done synchronously before any other call.<br /><br />This change also fixed a problem where moving from a recently created delayed_thread would throw sometimes because the lambda was executed after the move and it was holding a reference to the moved-from promise.<br /><br />One thing that I wanted to add but was not satisfied with my solution was the possibility of creating delayed_thread with function that take parameters, as std::thread does. Here is the code I used, which compiles under gcc on http://rextester.com, but it feels wrong to me:<br /><br />template< typename op_t, typename... args_t ><br />delayed_thread( op_t&& op, destruction_action on_destruction, args_t&&... args )<br />: m_thread(<br /> [ op = decay_copy(std::forward<op_t>(op)),<br /> should_execute = m_promise.get_future( ) ]<br /> ( typename std::decay<args_t>::type... args ) mutable<br /> {<br /> if( should_execute.get( ) )<br /> {<br /> op( args... );<br /> }<br /> }, std::forward< args_t >( args )...<br /> )<br />, m_on_destruction( on_destruction )<br />{}<br /><br />Any insight on that ?<br /><br />Also, working with this code made me wonder, do you think this class should really behave as a mix of ThreadRAII and std::thread with suspension or would it be better to implement it solely as a std::thread and use your original RAII code with it ?<br /><br />ThreadRAII tr( delayed_thread( funcToRun ), &delayed_thread::join ) // Maybe provide delayed_thread::dismiss for this ?<br /><br />or<br /><br />delayed_thread( funcToRun, delayed_thread::action::join )Anonymoushttps://www.blogger.com/profile/09018205426659606367noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-32337266306413378862013-12-28T18:22:00.606-08:002013-12-28T18:22:00.606-08:00@Tiago Gomes: This looks like a great suggestion! ...@Tiago Gomes: This looks like a great suggestion! We'd also have to modify the lambda to handle the possibility that get_future throws, but that's something we have to deal with in any case. <br /><br />Thanks for this idea!Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-75985310490019399892013-12-28T17:53:54.512-08:002013-12-28T17:53:54.512-08:00@Scott Meyers: Would it be any worse if we used pr...@Scott Meyers: Would it be any worse if we used promise/future of bool instead of void ?<br />If we use the bool version, we may use the value to indicate if op shall be executed or not.Then we change the lambda to:<br /><br />if(m_promise.get_future().get()) {<br />op();<br />}<br /><br />Change trigger to use m_promise.set_value(true) and the destructor would call m_promise.set_value(false).<br /><br />Would that be ok ?Anonymoushttps://www.blogger.com/profile/09018205426659606367noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-37072406603112112052013-12-28T17:04:42.934-08:002013-12-28T17:04:42.934-08:00@Scott Meyers: You are actually right, I overlooke...@Scott Meyers: You are actually right, I overlooked this part of the original problem.Anonymoushttps://www.blogger.com/profile/09018205426659606367noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-16533749157795227002013-12-28T16:49:13.354-08:002013-12-28T16:49:13.354-08:00@Tiago Gomes: My concern with your approach is tha...@Tiago Gomes: My concern with your approach is that it doesn't achieve the effect of starting a thread in a suspended state. Instead, your version of trigger creates a brand new thread object. One of the original goals was to pay all the overhead for creating a new thread, then suspend execution of that thread until some event occurs. I don't think your code does that. Am I overlooking something?Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-72550263762454777812013-12-28T11:25:31.063-08:002013-12-28T11:25:31.063-08:00@Scott Meyers After giving it some thought I think...@Scott Meyers After giving it some thought I think we are overengineering this problem.<br />From the point where we decided to create a class to wrap all the logic, we can stop using the future/promise trick and just delay the thread creation to achieve the desired behavior and avoid most of the problems we were facing.<br /><br />This is the code I've being testing ( http://rextester.com/runcode gcc with -pthread ) and seems ok:<br /><br />#include <thread><br />#include <chrono><br />#include <iostream><br />#include <future><br /><br />template<class func_t><br />class delayed_thread<br />{<br /> using thread_t = std::thread;<br /><br /> enum execution_state {<br /> WAITING, TRIGGERED, DISMISSED<br /> };<br /><br /> func_t m_op;<br /> thread_t m_thread;<br /> execution_state m_state = WAITING;<br /><br /> public:<br /> // Construction.<br /> delayed_thread() = delete;<br /> delayed_thread(delayed_thread const &) = delete;<br /> <br /> template<typename op_t><br /> delayed_thread(op_t &&op)<br /> : m_op(std::forward<op_t>(op))<br /> { }<br /><br /> delayed_thread(delayed_thread &&other)<br /> : m_op (std::move(other.m_op ))<br /> , m_thread(std::move(other.m_thread))<br /> , m_state (std::move(other.m_state ))<br /> {<br /> other.m_state = DISMISSED;<br /> }<br /> <br /> // Destruction.<br /> ~delayed_thread() {<br /> if(m_state == DISMISSED) {<br /> return;<br /> }<br /><br /> if(m_thread.joinable()) {<br /> m_thread.join();<br /> }<br /> }<br /> <br /> // Assignment.<br /> delayed_thread &operator=(delayed_thread const &rhs) = delete;<br /> <br /> delayed_thread &operator=(delayed_thread &&rhs) {<br /> m_op = std::move(rhs.m_op );<br /> m_thread = std::move(rhs.m_thread);<br /> m_state = std::move(rhs.m_state );<br /> <br /> rhs.m_state = DISMISSED;<br /> <br /> return *this;<br /> }<br /> <br /> // Execution.<br /> void trigger() {<br /> if(m_state == TRIGGERED || m_state == DISMISSED) {<br /> return;<br /> }<br /> <br /> m_state = TRIGGERED;<br /> m_thread = std::thread( m_op );<br /> }<br /> <br />}; // end class delayed_thread <br /><br />template<class func_t><br />delayed_thread<func_t> make_delayed_thread(func_t&& func)<br />{<br /> return delayed_thread<func_t>(std::forward<func_t>(func));<br />}<br /><br />void funcToRun()<br />{<br /> std::cout << "In funcToRun...\n";<br /> return;<br />}<br /><br />int main()<br />{<br /> auto t = make_delayed_thread(funcToRun);<br /> auto t1 = std::move(t);<br /> std::cout << "Working while thread 'suspended'...\n";<br /> t.trigger();<br /> t1.trigger();<br /> std::cout << "Working while thread runs asynchronously...\n";<br /> std::this_thread::sleep_for(std::chrono::milliseconds(500));<br /> std::cout << "Done!\n";<br />}Anonymoushttps://www.blogger.com/profile/09018205426659606367noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-54409115075882551242013-12-28T11:20:17.110-08:002013-12-28T11:20:17.110-08:00This comment has been removed by the author.Anonymoushttps://www.blogger.com/profile/09018205426659606367noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-3571220394585143122013-12-28T09:30:00.789-08:002013-12-28T09:30:00.789-08:00@Tiago Gomes: I agree. But if we don't trigger...@Tiago Gomes: I agree. But if we don't trigger, we can't do a join, because the join call will never return. (The lambda will never finish running.) Your suggestion to skip the call to op when we're destroying a delayed_thread in a WAITING state is reasonable, but how can we communicate the state information to the thread running the lambda? In the current version of delayed_thread, we could let the lambda capture m_state by reference, but that would work only because we do a join in delayed_thread. In the more general version of ThreadRAII that I discussed in my Going Native talk (delayed_thread is an RAII class like ThreadRAII), ThreadRAII could be configured to do either a join or a detach, and if we add that generalization to delayed_thread, there is no guarantee that the lifetime of m_state would extend to the point where the lambda could check it. <br /><br />So I agree that we need to find a way to avoid running op if trigger isn't called, but it's not clear to me this morning what the best way to do that is.<br /><br />Suggestions?Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-38751050808061783412013-12-28T07:31:32.996-08:002013-12-28T07:31:32.996-08:00@Scott Meyers I don't think triggering on dest...@Scott Meyers I don't think triggering on destruction is actually a good idea as it may create gotchas and problems that are hard to solve for the end user.<br />If we take into account, as you pointed earlier, that the thread may be waiting on environment setup, triggering on destruction may lead to undesired behaviors where op is called without the proper settings when an exception is thrown while setting up.<br />That being said, I would consider skipping the op call when destructing a delayed_thread on WAITING state.Anonymoushttps://www.blogger.com/profile/09018205426659606367noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-49274688136763703762013-12-27T21:39:39.800-08:002013-12-27T21:39:39.800-08:00@ Christopher Hayden: With a little tinkering, I g...@ Christopher Hayden: With a little tinkering, I got your code to work, at least on simple test cases. There are still some refinements that could be added (e.g., dealing with the possibility of an exception being thrown by std::promise::get_future), but you certainly addressed everything in my original question. Thanks very much for that!<br /><br />Here's the code I was able to get to compile, link, and run using gcc at http://rextester.com/runcode. (I had to add -pthread to the default command line.)<br /><br />#include <thread><br />#include <chrono><br />#include <iostream><br />#include <future><br /><br />// Per C++11 30.2.6<br />template <class T> typename std::decay<T>::type decay_copy(T&& v)<br />{ return std::forward<T>(v); }<br /><br /><br />class delayed_thread {<br /> using promise_t = std::promise<void>;<br /> using thread_t = std::thread;<br /><br /> enum execution_state {<br /> WAITING, TRIGGERED, DISMISSED<br /> };<br /><br /> promise_t m_promise;<br /> thread_t m_thread;<br /><br /> execution_state m_state = WAITING;<br /><br />public:<br /><br /> // Construction.<br /><br /> delayed_thread() = delete;<br /> delayed_thread(delayed_thread const &) = delete;<br /><br /> delayed_thread(delayed_thread &&other)<br /> : m_promise(std::move(other.m_promise))<br /> , m_thread (std::move(other.m_thread ))<br /> , m_state (std::move(other.m_state ))<br /> {<br /> other.m_state = DISMISSED;<br /> }<br /><br /> template<typename op_t><br /> delayed_thread(op_t &&op)<br /> : m_thread([op = decay_copy(std::forward<op_t>(op)), &m_promise = m_promise]() {<br /> m_promise.get_future().wait();<br /> op();<br /> })<br /> {}<br /><br /> // Destruction.<br /><br /> ~delayed_thread() {<br /> if(m_state == DISMISSED) {<br /> return;<br /> }<br /><br /> trigger();<br /><br /> if(m_thread.joinable()) {<br /> m_thread.join();<br /> }<br /> }<br /><br /> // Assignment.<br /><br /> delayed_thread &operator = (delayed_thread &&rhs) {<br /> m_promise = std::move(rhs.m_promise);<br /> m_thread = std::move(rhs.m_thread );<br /> m_state = std::move(rhs.m_state );<br /><br /> rhs.m_state = DISMISSED;<br /> return *this;<br /> }<br /><br /> delayed_thread &operator = (delayed_thread const &rhs) = delete;<br /><br /> // Execution.<br /><br /> void trigger() {<br /> if(m_state == TRIGGERED || m_state == DISMISSED) {<br /> return;<br /> }<br /><br /> m_state = TRIGGERED;<br /> m_promise.set_value();<br /> }<br /><br />}; // end class delayed_thread <br /><br />void funcToRun()<br />{<br /> std::cout << "In funcToRun...\n";<br /> return;<br />}<br /><br />int main()<br />{<br /> delayed_thread t(funcToRun);<br /> std::cout << "Working while thread 'suspended'...\n";<br /> t.trigger();<br /> std::cout << "Working while thread runs asynchronously...\n";<br /> std::this_thread::sleep_for(std::chrono::milliseconds(500));<br /> std::cout << "Done!\n";<br /><br />}Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-20888791688436809002013-12-27T14:45:39.298-08:002013-12-27T14:45:39.298-08:00@Zahir: Your idea is interesting. We can think of ...@Zahir: Your idea is interesting. We can think of the work in tripleDots as setting up a context in which the new thread should do its work, and if an exception is thrown during this setup, the code I originally posted would propagate that exception instead of starting funcToRun. I don't see a simple way to get that same behavior from your approach. It's easy to avoid running funcToRun if tripleDots throws, but propagating that exception back to the thread's creator is less straightforward.<br /><br />ScottScott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-28028420454116799542013-12-27T12:21:22.511-08:002013-12-27T12:21:22.511-08:00I have missed that I should be careful writing les...I have missed that I should be careful writing less-than, greater-than signs, my post is missing "packaged_task" template argument<br />which is "int()" regarding the return code. And I forgot to put a return statement (e.g. return OK;) just after "..."Zahirhttps://www.blogger.com/profile/04915673855138700742noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-42400791962946669952013-12-27T12:14:24.523-08:002013-12-27T12:14:24.523-08:00Regarding Herb Sutter's "never use naked ...Regarding Herb Sutter's "never use naked threads" idiom one may use some active objects (namely some task execution loops of their own threads) via passing "packaged_task" for getting the job done.<br /><br />Other than that, tt seems to me if "triple-dots" throws an exception or includes a return statement, this mostly effects the "funcToRun" too, it is waiting for "triple-dots" is a serious clue for me. So, if there is an exception/exit it should be handled inside this context or inside the thread.<br /><br />I can think of a solution including "packaged_task" like this:<br /><br />std::packaged_task tripleDots([&]{<br /> ...<br />});<br /><br />std::thread t([&tripleDots]{<br /> //handle here or ignore here<br /> tripleDots.get_future().wait();<br /> funcToRun();<br />}); <br /> <br />ThreadRAII tr(std::move(t));<br />tripleDots();<br /><br /><br />Herb Sutter's related post: http://herbsutter.com/2010/07/12/effective-concurrency-prefer-using-active-objects-instead-of-naked-threads/<br /><br />Zahirhttps://www.blogger.com/profile/04915673855138700742noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-81527499672268576892013-12-26T19:35:10.131-08:002013-12-26T19:35:10.131-08:00@Christopher Hayden: I agree, this seems to be a p...@Christopher Hayden: I agree, this seems to be a promising approach. There are a couple of problems with your code (e.g., std::promise is a template, not a class), so I'm going to play around with it before I reply more thoroughly, but the idea of creating a class to represent a thread that starts suspended seems sound, and it should (I hope) avoid things like destructor order problems.Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-55626621386513666392013-12-26T17:48:33.147-08:002013-12-26T17:48:33.147-08:00I think Rein is on the right track here. Since th...I think Rein is on the right track here. Since the behavior of the promise and the thread are so tightly coupled and we want to enforce invariants it might make sense to manage them using a class that automatically guarantees proper behavior. For example (disclaimer that I haven't compiled this):<br /><br />class delayed_thread {<br /> using promise_t = std::promise;<br /> using thread_t = std::thread;<br /><br /> enum class execution_state {<br /> WAITING, TRIGGERED, DISMISSED<br /> };<br /><br /> promise_t m_promise;<br /> thread_t m_thread;<br /><br /> execution_state m_state = WAITING;<br /><br />public:<br /><br /> // Construction.<br /><br /> delayed_thread() = delete;<br /> delayed_thread(delayed_thread const &) = delete;<br /><br /> delayed_thread(delayed_thread &&other)<br /> : m_promise(std::move(other.m_promise))<br /> , m_thread (std::move(other.m_thread ))<br /> , m_state (std::move(other.m_state ))<br /> {<br /> other.m_state = DISMISSED;<br /> }<br /><br /> template<br /> delayed_thread(op_t &&op)<br /> : m_thread([op = std::forward(op), &m_promise]() {<br /> m_promise.get_future().wait();<br /> op();<br /> })<br /> {}<br /><br /> // Destruction.<br /><br /> ~delayed_thread() {<br /> if(m_state == DISMISSED) {<br /> return;<br /> }<br /> <br /> trigger();<br /><br /> if(m_thread.joinable()) {<br /> m_thread.join();<br /> }<br /> }<br /><br /> // Assignment.<br /><br /> delayed_thread &operator = (delayed_thread &&rhs) {<br /> m_promise = std::move(rhs.m_promise);<br /> m_thread = std::move(rhs.m_thread );<br /> m_state = std::move(rhs.m_state );<br /><br /> rhs.m_state = DISMISSED;<br /> return *this;<br /> }<br /><br /> delayed_thread &operator = (delayed_thread const &rhs) = delete;<br /><br /> // Execution.<br /><br /> void trigger() {<br /> if(m_state == TRIGGERED || m_state == DISMISSED) {<br /> return;<br /> }<br /><br /> m_state = TRIGGERED;<br /> m_promise.set_value();<br /> }<br /><br />}; // end class delayed_threadAnonymoushttps://www.blogger.com/profile/10102659583666044815noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-77498329390555457932013-12-26T14:16:51.523-08:002013-12-26T14:16:51.523-08:00This comment has been removed by a blog administrator.Jamienoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-14634248006386968062013-12-26T14:16:48.932-08:002013-12-26T14:16:48.932-08:00IMO, std::promise is an implementation detail of t...IMO, std::promise is an implementation detail of the specific construct you are creating and as such should be hidden away from the client.Jamienoreply@blogger.com