Path: csiph.com!news.swapon.de!fu-berlin.de!uni-berlin.de!individual.net!not-for-mail From: Ian Collins Newsgroups: comp.lang.c++ Subject: Re: std::thread does not follow RAII principles Date: Fri, 28 May 2021 16:50:48 +1200 Lines: 110 Message-ID: References: <20210527190000.513390cffd366ff30deb7a13@cvine--nospam--.freeserve.co.uk> Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 8bit X-Trace: individual.net z/pjBmdCMB9zEOUuuYtSxwT12Gm+YypAc07ErcWUFG1foNC7VQ Cancel-Lock: sha1:z9E0TmK+eJc4DcJW/Ix6Xlul3rs= User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.8.1 In-Reply-To: Content-Language: en-US Xref: csiph.com comp.lang.c++:79841 On 28/05/2021 15:37, Öö Tiib wrote: > On Friday, 28 May 2021 at 05:59:41 UTC+3, Ian Collins wrote: >> On 28/05/2021 12:52, Öö Tiib wrote: >>> On Thursday, 27 May 2021 at 21:00:13 UTC+3, Chris Vine wrote: >>>> >>>> One other thing POSIX provides and C++11 doesn't is thread cancellation. >>>> Some programmers who don't have relevant experience think thread >>>> cancellation is a bad idea because it allows arbitrary cancellation >>>> which cannot be adequately controlled (the "combing your hair with a >>>> fork" jibe). Whilst that is true with windows it absolutely isn't with >>>> POSIX. What you do is use deferred cancellation (the default in POSIX) >>>> and block cancellation as normal policy, and only enable cancellation >>>> at defined points in the code which are able to deal with it (normally >>>> where a wait is to occur). Done properly, thread cancellation is far >>>> easier to use than exceptions, which can jump out of anywhere if you >>>> include std::bad_alloc in the mix. C++11 threads don't deal with >>>> cancellation and will never be able to do so because they require OS >>>> support - in this case POSIX support. >>> >>> With that thread cancellation I'm always in doubt. Especially if discussed >>> in context of RAII and destructor of thing like std::thread. >>> >>> One of major reasons making a thread is to turn synchronous work into >>> asynchronous. For that we put it into thread so then synchronous work is >>> going on in thread doing its blocking operations (potentially checking if >>> the work has become obsolete and/or reporting progress between those) >>> and once complete (or failed) handing the results back over to caller thread. >>> That way the caller thread has all benefits of asynchronous works going on. >>> It is fully responsive, so can take care of other responsibilities, entertain >>> users, report progresses, mark ongoing works obsolete and/or put out the >>> results (or failures) once something becomes complete. >>> >>> But what is that cancellation? It is like telling to worker thread to die in >>> middle of unknown state potentially in kernel with unknown amount of >>> locks taken, files open or mapped to virtual memory, that fragile external >>> hardware device produced by random morons in middle of something >>> and what not? std::terminate feels better and more honest than to cancel >>> the thread ... at least our users will see that we screwed all up in major >>> way. It all gets of course lost in examples because these talk about "foo()". >> Cancellation is well defined in POSIX. Cancellation points are defined >> by the standard and threads can manage their own cancellation type and >> state, >> >> If you want to manage resources in a thread that may be cancelled, you >> can use cleanup functions. I haven't tried it in Linux, but certainly >> in Solaris, the C++ runtime will destroy objects if the creating thread >> is cancelled. This is one of those annoying behaviours not covered by >> either the C++ or POSIX standards... >> >> A quick check on Linux shows that yes, destructors are called when a >> thread is cancelled. > > Very interesting ... but magical solutions make me even more worried. > Is there some kind of secret exception thrown or some kind of alternative > stack unwinding used or what? If secret exception then does > catch(...) { mopup(); throw; } work or has it to be full RAII? If alternative > stack unwinding then what it costs and do noexcept(true) functions > in call stack compile still into that rainbow table because of it? I believe (at lease on Solaris), the "magic" is the runtime using pthread_cleanup_push/pthread_cleanup_pop to manage the destructor calling, there's no need for exceptions. There's nothing to stop you doing this by hand... There a a number of behaviours which fall between two standards and we have to rely on the quality of the implementation, this just happens to be one. A simple test case: #include #include #include struct foo { foo() { std::cout << "constructor" << std::endl; } ~foo() { std::cout << "destructor" << std::endl; } }; void* thread(void*) { foo f; while (true) { sleep(5); } return nullptr; } int main() { pthread_t t; pthread_create(&t, 0, thread, 0); sleep(1); pthread_cancel(t); pthread_join(t, 0); } -- Ian.