-
Notifications
You must be signed in to change notification settings - Fork 5.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Renaming CP.25 raii_thread, and fixing CP.26 detached_thread #925
Comments
this would also close the old To-do issue that says "is there a good reason to detach threads?" |
Per our editor's discussion, people don't consistently write namespace names. The same name in different namespaces might lead to confusion. When we talk about it, we're often not precise about which we are talking about. If it's something different, it should have a different name. Possibly @jwakely, can you comment on the detached thread issue? Does this work in the light of a long POSIX tradition? |
I'm sure "never detach threads" will upset someone, but I don't expect strong enough objections that it would prevent that guideline. |
TC++PL4 says "do not detach a thread unless you absolutely have to". |
(re)naming of raii_thread: |
If recycling a name from |
The only reason to detatch threads is probaly to execute coroutines in a separate thread. The way python does it is that it uses an event loop and itterators as ways to scedule coroutines as However even with coroutines, there is no garrentee that functions can run without possible race conditions. However most race contitions come from things like: // Example loop till 255 is reached.
for (int i = 0; i < 255; ++i) {
// do stuff.
// they accidentally do this. Do not do this as it can make an race condition.
for (int i = 0; i > 0; ++i) {
// other things.
}
} Sadly the compiler does allow this too. |
What about |
It's not local if you return it from a function, or have it as a data member of a class, or a global, or a member of a global container. |
true, anyway the following files can help implement coroutines (I think) into C++ v. next:
But yeah this can at most give hints on how to do it (However you guys might not want an event loop for exectuting coroutines, but you never know). sometimes you just love having to source dive into things. But their C code is so much cancer code. (makes me really want to make my own rewrite to latest python master branch to be total C++ to be a lot easier on my eyes) |
I don't see how coroutines are on-topic here. |
@jwakely wrote:
Yes, but I think that P0206 is evidence in favor of GSL using the name So the name BTW, I don't think we want to set a precedent that an RAII type should be named "raii_"... that would be a huge number of types. :) |
It gives me pause to push the notion of the proposed std::joining_thread being what std::thread should've always been, and I am concerned about the confusion of different libraries giving different semantics to one name, namespaced or not. Chalk this up as a +1 for gsl::joining_thread. |
Note that scoped_thread has some downsides; the proposed joining_thread can move across scopes, it's not bound to a particular scope. |
Good votes and reasons for joining_thread; I'll try that. |
As far as detach goes, Howard has showed an interesting use case for it. It looks like this: thread{date::get_tzdb}.detach(); There, get_tzdb has a magic-static singleton in it. Initializing that magic-static is a heavyweight operation. Wrapping that operation into a detached thread makes it run in a non-blocking fashion so that it's likely ready when it's needed. |
And there's more such valid uses for detach: http://stackoverflow.com/a/43617125/576911 |
@villevoutilainen: These seem to be great examples against Re Howard's example:
Or that it's likely to crash a short-lived program that never asks for the value and returns from What I suggested earlier in the discussion is to have something like a global @BjarneStroustrup: This could be a good example to capture in the writeup.
Is that the link you intended? I only see one example showing "detach" on that page, and it is immediately followed by this reply:
|
I gave my opinion in TC++PL4 and I'm unlikely to depart far from that. My best example of detach is a heartbeat. However, the basic counterargument to all suggestions for detach is that you can transfer ownership into a scope that's external to the scope we want to detach from - that's a variant of the ownership argument that permeate much of the guidelines. Unless you have a handle to a task somewhere, you can't ask if it has completed, is making progress, etc. |
I should explain. In the thread{date::get_tzdb}.detach(); example, the assumption is that the program is long running unless something very bad happens. In the case that you are shutting down main very quickly, something bad has happened, and you are crashing. It doesn't really matter if you are crashing because of whatever bad happened in main, or because get_tzdb() touched something already destructed. You're crashing. If you need a early terminate environment more graceful than that, then by all means use it. My arguments for detach() aren't that they are always the best. It is only that they are sometimes useful and preferable. I personally use join() far more than I use detach(). My only point is that my use of detach() is nonzero and useful. join() is a convenience function (one I find very useful). It can be reproduced by a skillful combination of detach(), mutex and condition_variable. If join() doesn't meet your needs, it is highly likely that a careful combination of mutex, condition_variable and detach can create a customized join that does meet your needs. My only point is that detach is not evil. It is a part of a lower-level API that exists and is useful to build other higher-level API's with useful semantics different than join. |
By all means put a "black diamond" next to detach. But when you do, realize that you should also put "black diamonds" on mutex and condition_variable. And by all means, put "double black diamonds" on atomics. And for those using memory_order or launder, put quintuple black diamonds! There is no question detach is a sharp tool. But it by no means even comes close to the sharpest tools in our tool box. |
For the Core Guidelines, I believe a focus is what to recommend to the majority of programmers, most of whom aren't experts in fine details of lower-level facilities provided by the language. It is expected that an expert would look at a specific situation and say "nah, I think this situation can run counter to the general guidelines and here is why." In other words, things not recommended by the Guidelines aren't necessary so because they are judged "not useful". Look at |
If "Or that it's likely to crash a short-lived program that never asks for the value and returns from main and enters static destruction before the initialization is complete." is true, we have a serious defect in the standard. [basic.start.term]/1 says that such destruction is done only for objects that have begun their lifetime, aka have completed their initialization. We don't seem to say that if the initialization is ongoing, the destruction should be pending that initialization. But I digress; I think it's fine to recommend against using detach, because the whole point of the guidelines is indeed to put the sharpest knives further away from innocent users. I believe my and Howard's concern is what it means to "flag" such uses, and how users can control whether a tool will tell them their code is wrong or not. |
I do a bit of thinking about consequences of rules. There are often unintended consequences, but my main question is who does it affect and how. If we help a million programmers avoid a bug by forcing a few hundred professionals to write two lines instead of one, I usually deem it a clear win. I don't think detach is quite that clear-cut, but that's one way to look at it. A rule in the standard is in some sense absolute and universal: If the standard bans something, nobody can do it (except, of course, that we are rather clever bypassing the intent of the wording). A rule in the guideline is just a guideline you can - usually at your peril - bypass it without leaving ISO C++. |
Nobody's suggesting removing |
I agree with all of the above, thank you -- and @HowardHinnant my apologies that I neglected to mention your further comments on that SO solution right after Pete's, I only saw Pete's reply at first because I had highlighted "detach" on the page and was focused on the four hits. |
Could we at least not tell people falsehoods?
This is just patently untrue. |
Suggested language might go along the lines of: If you detach a thread, you become responsible for interacting with that thread. This is typically done with mutex-protected state or atomics, and signaling using condition_variables. Recommended practice is to use join() instead. |
Thanks, howard |
Thanks Howard. I can live with that weaker wording for the Guidelines, except I would prefer not to even mention those techniques because I don't think they're supported by the standard. I don't think that what I wrote at top is a falsehood, and I've heard many stronger statements made over multiple years of arguments about this in SG1. You should expect prominent SG1 participants (not me) to continue to argue that it is inherently unsafe to safely interact with a detached thread during static destruction in any way -- joining it, signaling a mutex to communicate with it, etc. AFAIK those techniques are not guaranteed to work in ISO C++. For example, even joining with a detached thread during static destruction can be problematic is if the thread has a I agree with your point that the Guidelines don't need to include extended commentary on these things, but I don't agree that they're not real problems. AFAIK, the techniques you mention are not guaranteed to work in C++11/14/17 in the case I mentioned, "during static destruction." -- I would be happy to be corrected! But if it's still true that they don't work, I don't think we should encourage them, and I would rather not even mention them. |
In this discussion of detach(), I think we forgot the primary reason for the existence of detached_thread: to allow static analysis of ownership and lifetime patterns. One problem with std::thread is that we cannot know whether it outlives its scope, so we don't know if we can pass a pointer to it or receive a pointer from it. For that reason, I sort of still like detached_thread. Comments? |
Here is portable, standard-conforming C++11 that detaches 10 threads, waits with an "or" for those to finish under 1s that can, then cancels the remaining threads, and then joins with the canceled threads before exiting main: https://wandbox.org/permlink/5RjOy09nY89zqTwv You are either arguing that this code doesn't exist, or that it is non-conforming. Can you be more specific? Admittedly, I'm not playing around with trying to join with detached threads after main returns (during static destruction time). I have no motivation to bring that aspect into this discussion. I'm talking about safely, portably and effectively controlling detached threads before main exits. I'm sure I could make some example code to play with detached threads during the atexit run, but I don't really see the point. There's no reason to discuss it for this guideline. |
For example, it would not be too difficult to take my example and wrap it up in a |
My #1 comment on detach() is that instead vest ownership of the (otherwise detached) thread in a scope that lives for long enough. "Potentially forever" can be done with a global or a bunch of shared_ptrs. Agreed? I would like to keep most programmers out of subtle discussions and standards concerns like the one above. |
@BjarneStroustrup: Agreed. The high-order bit for this thread is what the Guidelines should encourage -- I think we're all in violent agreement that the Guidelines do not need to get into debates about how sharp the knife is? (Though I think SG1 should and does.) @HowardHinnant asked:
I took a quick look and there seem to be race conditions on the "done" flags, so it's undefined behavior before we get to detaching. For detaching, as you mentioned this example doesn't illustrate the case I highlighted of 'during static destruction.'
I'm not sure that would address the issues, but then I'm just reporting the concerns. The key SG1 experts to answer "does the standard support a given example of detached threads during static destruction" aren't on this thread, so I encourage you to ask SG1 about examples -- we'll both be interested in the current answers. I know you and I were both in at the beginning of it over a decade ago (aside: for those who don't know, Howard primarily is Mr. Std Thread for C++11!), but it hasn't gone away and the experts with the strongest recent opinions aren't here -- Hans, Detlef, Lawrence, and I know I'm forgetting one or two others. |
Hans has said many times that detached threads can't be safely handled. For every time he has said it within my earshot, I show code that safely handles detached threads. In my example every single read or write access to all Oh, except line 65 where the done flags are constructed. They are constructed outside the protection of the mutex. This is also prior to any thread being created, much less any thread having access to the flags. Therefore line 65 need not be protected by |
I took a good look at Howard's code and couldn't see any races. The threads exit after unlocking the mutex, so don't access anything after the |
Right. This is a good example of a case where it is important to do the cv notify prior to unlocking the mutex. There is no way for Had the mutex been unlocked, and then the cv notified, it would be theoretically possible for main to spuriously wake up and destruct the cv before the thread called |
Now that I think of it, THAT would be a good guideline! When notifying a condition_variable, default to doing it before you unlock the mutex instead of after. Rationale: It is never wrong to unlock after the notify, and it is sometimes wrong to unlock before the notify. And there is no performance penalty in unlocking after, as good implementations take this coding pattern into account. |
Howard, thanks for the correction -- my "quick look" was too quick and I missed the careful use of the mutex and cv. I think we're agreeing not to teach detaching as a default design choice. I like your suggested |
@MikeGitb: We have half of it with the {
unique_lock hold{mut};
while ( ! /* the protected state is what we are waiting for */ )
cv.wait(hold); // unlock and reacquire
/* main work */
} With the predicate form, we have this much: {
unique_lock hold{mut};
cv.wait(hold, []{ return /* the protected state is what we are waiting for */; });
/* main work */
} That's not too bad. But I was thinking of a way to avoid repeating {
conditional_lock hold{mutwithcv, []{ return /* the protected state is what we are waiting for */; }};
/* main work */
} so that the mutex and cv are bundled and we can't make the mistake of using the wrong one. This doesn't cover all cv cases, but it covers common ones. But I admit I haven't tried very hard to define this (or look for it -- I'd love to be told to just use something that already exists that I should already know about but don't). |
Building that on top of Olivier's http://wg21.link/p0126 would be optimal, but it's still going through redesign in SG1. |
I'll make a note to include it in the CV guidelines, writing them up has been on my TODO list for ages |
@hsutter: Sorry, I deleted my original post - I thought no one had seen it ;) I made the mistake of only thinking about the signaling part, for which I still think a semaphore would be the best solution in 99% of the cases. |
I hope I have resolved this one. It's obviously tricky. Please review carefully. |
I see you've discussed a lot and there are different views, but one major issue with Other N cents: Therefore I saw quite opposite recommendation: use only detached thread. Besides the system does proper cleanup it makes programmer think not in term of 'threads' but rather about 'communication'. Some reasoning:
Guideline give example of I've looked at examples in Guidelines and it feels that wherever thread is used with plain function and then joined, the CP.4 rule and Note on shared ownership: I find it very useful because this nicely solves 2 issues: 'CP.22 Never call unknown code while holding a lock' and memory management. So I had shared_ptr also attached to every callback to be dispatched, which kept the object alive while callback was in the queue or being executed, thus message loop could be safely destroyed even from thread it owns, e.g. if callback releases last 'public' reference to the message loop. |
CP.25 raii_thread
The PR for this is now ready to merge into GSL here: microsoft/GSL#504
IMO the design looks fine. But do we really want to use the name "raii_thread"? Yes, that's technically what it is (for those who like using that acronym), but why not "joining_thread" or "scoped_thread"? ... or, best of all, "thread"!
Proposed resolution: Rename "raii_thread" to just
gsl::thread
. This type is whatstd::thread
ought to have been, and it's identical tostd::thread
except thatgsl::thread
has a properly-joining destructor and does not havedetach
. And this is what namespaces are for. :)CP.26 detached_thread
The GSL PR has split out "raii_thread" because I suggested "detached_thread" was problematic and less recommendable. So we should also do something about CP.26 which recommends "detached_thread" -- I think detaching is an antipattern we should not encourage, as Hans and others have pointed out many times (e.g., orderly shutdown is at best problematic and usually impossible).
Proposed resolution:
std::thread::detach
. What to do instead: Just stick a normalgsl::thread
in a global object or container and then ignore it if you want… but it means better more-orderly shutdown by default plus the ability to actually join with it if you do want to refer to it later.The text was updated successfully, but these errors were encountered: