在recvfrom系统调用期间取消C 11 std :: thread?

我正在使用C 11 std :: thread.它的主循环包括一个阻塞的recvfrom()调用,它监听到达DATAGRAM套接字的UDP数据包,以及一些复杂的代码,用于解析消息并在进程中处理大量的STL容器.

该线程属于一个类(helloexchange),由构造函数启动,应该在析构函数中取消.
出于显而易见的原因,我不想强​​行终止线程,因为这可能会破坏数据结构,这些数据结构部分位于类之外.

当使用pthread而不是std :: thread时,有pthread_cancel方法,它与pthread_setcancelstate一起提供了我需要的所有功能:它只会在某些系统调用阻塞时取消该线程,并且取消可以完全取消对某些部分禁用.一旦再次启用取消,则执行取消.
这是工作pthread代码的完整示例:

#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <cstdio>
#include <pthread.h>
#include <net/if.h>
#include <ifaddrs.h>

int sock;

void *tfun(void *arg) {
    std::cout << "Thread running" << std::endl;
    while(true) {
        char buf[256];
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        socklen_t addrlen = sizeof(addr);
        //allow cancelling the thread
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);

        //perform the blocking recvfrom syscall
        int size = recvfrom(sock,(void *) buf,sizeof(buf),(struct sockaddr *) &addr,&addrlen);

        //disallow cancelling the thread
        pthread_setcancelstate(PTHREAD_CANCEL_disABLE,NULL);

        if(size < 0) {
            perror("Could not receive packet");
            return NULL;
        } else {
            //process the packet in the most complex ways
            //you Could imagine.
            std::cout << "Packet received: " << size << " bytes";
            std::cout << std::endl;
        }
    }
    return NULL;
}


int main() {
    //open datagram socket
    sock = socket(AF_INET,SOCK_DGRAM,0);
    if(sock < 0) {
        perror("Could not open socket");
        return 1;
    }
    //bind socket to port
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(1337);
    addr.sin_addr.s_addr = 0;
    if(bind(sock,sizeof(addr)) < 0) {
        perror("Could not bind datagram socket");
        return 2;
    }
    //create the listener thread
    pthread_t t;
    if(pthread_create(&t,NULL,tfun,NULL) != 0) {
        perror("Could not thread");
        return 3;
    };
    //wait
    std::cin.get();
    //cancel the listener thread. pthread_cancel does not block.
    std::cout << "Cancelling thread" << std::endl;
    if(pthread_cancel(t) != 0) {
        perror("Could not cancel thread");
    }
    //join (blocks until the thread has actually cancelled).
    std::cout << "Joining thread" << std::endl;
    if(pthread_join(t,NULL) != 0) {
        perror("Could not join thread");
    } else {
        std::cout << "Join successful" << std::endl;
    }
    //close socket
    if(close(sock) != 0) {
        perror("Could not close socket");
    };
}

但是,std :: thread不支持cancel,std :: this_thread也不支持setcancelstate(你会找到一个引用here).但它确实支持native_handle,它返回内部使用的pthread_t id.
然而,将pthread_cancel()发送到线程的本机句柄的明显方法会导致分段错误

#include <iostream>
#include <thread>
#include <cstdio>

#include <arpa/inet.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <pthread.h>
#include <net/if.h>
#include <ifaddrs.h>

int sock;

void tfun() {
    std::cout << "Thread running" << std::endl;
    while(true) {
        char buf[256];
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        socklen_t addrlen = sizeof(addr);

        //perform the blocking recvfrom syscall
        int size = recvfrom(sock,&addrlen);

        if(size < 0) {
            perror("Could not receive packet");
            return;
        } else {
            //process the packet in the most complex ways
            //you Could imagine.
            std::cout << "Packet received: " << size << " bytes";
            std::cout << std::endl;
        }
    }
    return;
}

int main() {
    //open datagram socket
    sock = socket(AF_INET,sizeof(addr)) < 0) {
        perror("Could not bind datagram socket");
        return 2;
    }
    //the listener thread
    std::thread *t = new std::thread(&tfun);
    //wait
    std::cin.get();
    //cancel the listener thread. pthread_cancel does not block.
    std::cout << "Cancelling thread" << std::endl;
    if(pthread_cancel(t->native_handle()) != 0) {
        perror("Could not cancel thread");
    }
    //join (blocks until the thread has actually cancelled).
    std::cout << "Joining thread" << std::endl;
    t->join();
    delete t;
    //close socket
    if(close(sock) != 0) {
        perror("Could not close socket");
    };
}

结果是:

(gdb) run
Starting program: /tmp/test/test-dbg 
warning: Could not load shared library symbols for linux-vdso.so.1.
Do you need "set solib-search-path" or "set sysroot"?
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
[New Thread 0x7ffff6550700 (LWP 11329)]
Thread running

Cancelling thread
Joining thread

Program received signal SIGSEGV,Segmentation fault.
[Switching to Thread 0x7ffff6550700 (LWP 11329)]
0x00007ffff6e67b45 in __gnu_cxx::__verbose_terminate_handler() () from /usr/lib/libstdc++.so.6

有什么方法可以在系统调用中阻止std :: thread吗?

编辑

我不是要求跨平台的解决方案;符合POSIX的解决方案就足够了.

解决方法

我将根据着名的 self-pipe trick建议解决方法,以解锁 select(2)并避开整个凌乱的线程取消业务.

既然你知道你的线程阻塞的socket(7)的IP地址和端口,只需要sendto(2)从你的主线程中获得一些众所周知的哨兵包,这表明它是时候打破了循环.

这样您就不必破坏std :: thread抽象,并且可以保持合理的可移植性.

编辑0:

如果您不喜欢变通方法,请将其称为技术:​​)

相关文章

本程序的编译和运行环境如下(如果有运行方面的问题欢迎在评...
水了一学期的院选修,万万没想到期末考试还有比较硬核的编程...
补充一下,先前文章末尾给出的下载链接的完整代码含有部分C&...
思路如标题所说采用模N取余法,难点是这个除法过程如何实现。...
本篇博客有更新!!!更新后效果图如下: 文章末尾的完整代码...
刚开始学习模块化程序设计时,估计大家都被形参和实参搞迷糊...