The thread-based I/O example with the compute bound poll loop is kind of strange.
"Join" isn't really that useful when you have unrelated threads running finite tasks. Usually, you let the thread do its thing,finish, put results on a queue, and let the thread exit. Then it doesn't matter who finishes first. Rust join is actually optional. You don't have to join to close out a thread and reap the thread resources. It's not like zombies in Unix/Linux, where some resources are tied up until the parent process calls wait().
Loops where you join all the threads that are supposedly finished are usually troublesome. If somebody gets stuck, the joiner stalls. Clean exits from programs with lots of channels are troublesome. Channels with multiple senders don't close until all senders exit, which can be hard to arrange when something detects an error.
In Rust, the main thread is special. (I consider this unfortunate, but web people like it, because inside browsers, the main thread is very special.) If the main thread exits, all the other threads are silently killed.