pthread_cancel无法终止阻塞读线程问题求助
macOS下pthread_cancel无法终止阻塞在标准输入读取的线程
在macOS环境中,使用G++/Clang编译运行你提供的两段代码时,尝试通过pthread_cancel终止阻塞在scanf或cin >>操作的线程会导致程序永久挂起——线程无法被终止,pthread_join会一直阻塞。
问题原因
macOS基于BSD内核,其系统调用对线程异步取消的支持存在局限性:
- 当线程阻塞在终端标准输入的读取操作(如
scanf、cin >>)时,线程处于内核态阻塞状态,此时pthread_cancel的异步取消请求无法触发线程退出。 - 即使设置了
PTHREAD_CANCEL_ASYNCHRONOUS,这类阻塞I/O操作并不属于POSIX标准定义的取消点,内核不会在该阻塞状态下检查线程的取消请求。
解决方案
方案1:用信号中断阻塞I/O,配合线程取消
给目标线程发送信号(如SIGUSR1)中断其阻塞的I/O操作,此时线程会从I/O调用中返回错误(errno设为EINTR),之后线程会到达取消点(或手动调用pthread_testcancel),从而响应pthread_cancel请求。
C版本修改示例
#include <csignal> #include <pthread.h> #include <stdio.h> #include <unistd.h> #include <errno.h> // 信号处理函数仅用于中断阻塞 void sig_handler(int sig) {} void *routine(void *arg) { // 注册信号处理函数 struct sigaction sa = {0}; sa.sa_handler = sig_handler; sigaction(SIGUSR1, &sa, nullptr); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, nullptr); pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, nullptr); int value; printf("Reading\n"); // 读取被信号中断后返回-1,errno为EINTR int ret = scanf("%d", &value); if (ret == -1 && errno == EINTR) { printf("Read interrupted by signal\n"); // 手动触发取消检查 pthread_testcancel(); } printf("Reading done\n"); return nullptr; } int main() { pthread_t th; pthread_create(&th, nullptr, routine, nullptr); usleep(1000000); printf("Cancelling thread\n"); // 先发送信号中断阻塞读取 pthread_kill(th, SIGUSR1); // 再发起取消请求 pthread_cancel(th); printf("Cancelled\n"); pthread_join(th, nullptr); printf("Joined\n"); }
C++版本修改示例
#include <csignal> #include <pthread.h> #include <iostream> #include <unistd.h> void sig_handler(int sig) {} void *routine(void *arg) { struct sigaction sa = {0}; sa.sa_handler = sig_handler; sigaction(SIGUSR1, &sa, nullptr); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, nullptr); pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, nullptr); int value; std::cout << "Reading" << std::endl; std::cin >> value; if (std::cin.fail()) { std::cout << "Read interrupted by signal" << std::endl; pthread_testcancel(); } std::cout << "Reading done" << std::endl; return nullptr; } int main() { pthread_t th; pthread_create(&th, nullptr, routine, nullptr); usleep(1000000); std::cout << "Cancelling thread" << std::endl; pthread_kill(th, SIGUSR1); pthread_cancel(th); std::cout << "Cancelled" << std::endl; pthread_join(th, nullptr); std::cout << "Joined" << std::endl; }
方案2:使用非阻塞式标准输入
将标准输入设置为非阻塞模式,线程轮询读取的同时定期检查取消请求:
#include <pthread.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> void *routine(void *arg) { pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, nullptr); pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, nullptr); // 将stdin设置为非阻塞 int flags = fcntl(STDIN_FILENO, F_GETFL); fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); int value; printf("Reading (non-blocking)\n"); while (1) { int ret = scanf("%d", &value); if (ret == 1) { printf("Read value: %d\n", value); break; } else if (ret == -1 && errno != EAGAIN) { // 非资源不可用的错误,退出循环 break; } // 轮询间隔,同时触发取消检查 usleep(100000); pthread_testcancel(); } printf("Reading done\n"); return nullptr; } int main() { pthread_t th; pthread_create(&th, nullptr, routine, nullptr); usleep(3000000); printf("Cancelling thread\n"); pthread_cancel(th); printf("Cancelled\n"); pthread_join(th, nullptr); printf("Joined\n"); }
注意事项
- 异步取消(
PTHREAD_CANCEL_ASYNCHRONOUS)风险较高,可能导致资源泄漏(如未释放的锁、未关闭的文件描述符),建议优先使用同步取消(PTHREAD_CANCEL_DEFERRED),并在代码中显式设置取消点。 - macOS的线程取消行为与Linux存在差异,跨平台移植代码时需注意兼容性。
内容的提问来源于stack exchange,提问作者Nikolay Mikhaylov
相关产品推荐
相关产品推荐

