You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 20:25:54