In general, it is not sound to kill a thread, because that thread might be holding locks or other state which would become "stuck" at the moment it's terminated without unwinding. Your options are:
Use a non-blocking IO mechanism.
Run the thing you need to be able to stop as a subprocess. The address space separation ensures that the entire subprocess going away can't harm your main process.
The complications of (synchronous) threads was a motivation for me to look at async Rust. Of course async also has some other advantages (as well as some disadvantages).
For a task in Tokio, for example, there is an abort method on the JoinHandle.
This might seem naive, but what about closing the other end of the pipe? That should result in a read error which will unblock the blocked thread so you can kill it.
Closing the other pipe of the end sounds like a good idea. Note, however, that when writing to a pipe that has been closed on the other end, certain measures must be taken as by default some operating systems will kill the whole process (e.g. with SIGPIPE on Linux or BSD). That behavior can be changed (e.g. by ignoring the signal).