I've been using pipe() lately (along with poll()) to tell threads to quit, and other simple inter-thread signalling. Super easy, very little code needed. I guess socketpair() would work just as well.
After writing the same kind of code involving pipes semaphores or whatnot dozen of times, I really enjoy c++20's std::jthread taking care of the cancellation entirely automatically