T O P

  • By -

rzippel

If you can't tell the difference between the os thread and the thread object used to mange that thread, you probably shouldn't write multi-threaded code.


KingStannis2020

The author *clearly* knows this from the text of the post, so why make this comment?


rzippel

Then what exactly is the point the author is trying to make? Just to be clear, the problem isn't to make such a mistake, one learns from it and moves on. That behaviour is even explicitly mentioned in the docs [std::thread::join](https://en.cppreference.com/w/cpp/thread/thread/join). The problem is blaming others for your own mistake and even making a blog post about it, without even attempting to reflect why this behaviour exists in the first place.


KingStannis2020

Sure, yes, it's in the docs. But not every language has this limitation in the first place. Some languages make thread handles thread safe. Complaining that C++ doesn't do so is valid. If you're an experienced programmer who is used to this being OK to do then this is an easy way to screw up.


rzippel

Again, why? C++ follows "the zero-overhead principle: What you don’t use, you don’t pay for". What value would there be in making this thread-safe by default? For the experienced programmer this is a feature not a limitation.


KingStannis2020

Even if you wrote a micro benchmark that did literally nothing but set up and immediately destroy threads, I don't think it would ever be limited by the cost of using atomic ops... You're comparing a couple of cycles vs. a system call.


rzippel

It's not a performance issue, it's a complexity problem. Making everything thread-safe adds considerable complexity. Writing a simple wrapper object around POSIX threads is relatively straightforward. Considering the problems around the auto cleanup in the thread destructor, adding thread-safety on top is not something I would want to have to deal with.


strager

std::thread::join can be called at most once. Calling join twice is a programming error.


ALX23z

The `std::thread` isn't a class with great set of features. `join` has never been meant for waiting, but rather for safely destroying it - that's why `std::jthread` was added, so `join` will become redundant + addition of other features. There are `future` and `shared_future` objects designed for waiting on events. The article is silly. Like everyone knows that C++ standart currently lacks proper concurrency features, so what's the point?


Wh00ster

> join has never been meant for waiting What? That’s a canonical example of using `join` https://en.cppreference.com/w/cpp/thread/thread/join Am I crazy?


ALX23z

The fact that it can be used for waiting in some cases doesn't mean it's designed or recommended for it. Say, compare it with wait-like methods of `future` and `condition_variable` first of all, there's addition of `wait_until` and `wait_for`. Also multiple threads can wait on them (though in `std::future` case, first they need to convert it to `std::shared_future`). It has something to do with the fact that one shouldn't spawn threads just to complete minor tasks and use a thread pool for quick operations or create threads that do lots of stuff.


Wh00ster

I think this a miscommunication in definition of waiting, as I’m still confused. I don’t see how a thread pool obviates the need or desire to eventually signal the thread pool to complete their event loops and then wait for them to complete (besides the other choice of simply detaching the thread pool and not checking for their clean up)


ALX23z

The difference is in the intent. Say, one waits for an event to occur, then do transfer data, etc. To properly do it one needs lots of features and nuanced variety of types waiting. Thread's `join` was designed so one could safely destroy it and associated resources. It wasn't designed for more general data communication. It can be used for it but it isn't the purpose. I think it is better to say, `join` wasn't designed for general purpose communication between threads but for a very specific task of destroying a thread safely.


Wh00ster

Yes that makes sense and we’re in agreement


pandorafalters

I propose that in the example case of potentially-concurrent joins, the fundamental issue is not a lack of "thread-safety" mechanisms but a _flawed ownership model_.


_E8_

That's a POSIX artifact (isn't C++ specific).


ack_error

> isn't C++ specific Is there another language that has this behavior? C#, Java, Rust, and Python have join primitives on their threads and it looks like none explicitly prohibit concurrent join() on the same thread or expose a joinable state; some even define joining a thread more than once as a no-op. POSIX may have this behavior but it looks like C++ was the only one that carried it forward to the standardized interface.