在 socketcan 上使用多线程时的类析构函数调用

Class Destructor call when using multithreading on socketcan

提问人:rebelliousconformist 提问时间:10/17/2023 更新时间:10/17/2023 访问量:21

问:

我试图理解为什么在程序启动后一开始就调用类析构函数!?

这是我的程序片段:

#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <mutex>
#include <atomic>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <linux/can.h>
#include <linux/can/raw.h>

class Canloop {
private:
    int socketFileDescriptor;
    struct sockaddr_can socketAdressConfig;
    struct ifreq interfaceRequestConfig;
    struct can_frame canFrameRx;
    struct can_frame canFrameTx;
    int sendCounter{0};
    std::thread receive_;
    std::thread transmit_;
    std::mutex readWriteMutex;
    std::atomic<bool> running{true};

    void receiveThread();
    void transmitThread();

public:
    Canloop(std::string &canInterface);
    void setCanData(can_frame &);
    void start();
    void stop();
    ~Canloop();
};

Canloop::Canloop(std::string &canInterface) {
    if ((socketFileDescriptor = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
        perror("Socket");
    }

    strcpy(interfaceRequestConfig.ifr_name, canInterface.c_str());
    ioctl(socketFileDescriptor, SIOCGIFINDEX, &interfaceRequestConfig);

    memset(&socketAdressConfig, 0, sizeof(socketAdressConfig));
    socketAdressConfig.can_family = AF_CAN;
    socketAdressConfig.can_ifindex = interfaceRequestConfig.ifr_ifindex;

    if (bind(socketFileDescriptor, (struct sockaddr *)&socketAdressConfig, sizeof(socketAdressConfig)) < 0) {
        perror("Bind");
    }

    int flags = fcntl(socketFileDescriptor, F_GETFL, 0);
    if (flags == -1) {
        perror("fcntl non blocking set error");
    }

    flags != O_NONBLOCK;
    if (fcntl(socketFileDescriptor, F_SETFL, flags) == -1) {
        perror("fcntl");
    }

    int bufferSize = 2;
    if (setsockopt(socketFileDescriptor, SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize))) {
        perror("setsocketopt");
    }
    if (setsockopt(socketFileDescriptor, SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize))) {
        perror("setsocketopt");
    }

    struct timeval timeout;

    timeout.tv_sec = 0;
    timeout.tv_usec = 1000;

    if (setsockopt(socketFileDescriptor, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout))) {
        perror("time set error");
    }

    if (setsockopt(socketFileDescriptor, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout))) {
        perror("time set error");
    }
}

void Canloop::start() {
    receive_ = std::thread([=] { receiveThread(); });
    transmit_ = std::thread([=] { transmitThread(); });
}

void Canloop::setCanData(can_frame &data) {
    this->canFrameTx = data;
}

void Canloop::stop() {
    if (close(socketFileDescriptor) < 0) {
        perror("Close");
    }

    running = false;
    if (receive_.joinable()) {
        receive_.join();
    }

    if (transmit_.joinable()) {
        transmit_.join();
    }
}

void Canloop::receiveThread() {
    std::cout << "Starting Rx_ " << std::endl;

    while (running) {
        ssize_t bytesRead;
        {
            // Mutex to guard the socket for Read access
            std::lock_guard<std::mutex> lock(readWriteMutex);
            bytesRead = read(socketFileDescriptor, &canFrameRx, sizeof(struct can_frame));
        }
        if (running) {
            if (bytesRead == -1) {
                if (errno == EAGAIN || errno == EWOULDBLOCK) {
                    // Handle non-blocking read
                } else {
                    perror("read error");
                }
            } else if (bytesRead == sizeof(struct can_frame)) {
                // Print data
                printf("0x%03X [%d] ", canFrameRx.can_id, canFrameRx.can_dlc);
                for (int i = 0; i < canFrameRx.can_dlc; i++)
                    printf("%02X ", canFrameRx.data[i]);
                printf("\r\n");
            } else {
                std::cout << "Incomplete data" << std::endl;
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
}

void Canloop::transmitThread() {
    std::cout << "Starting Tx_" << std::endl;

    while (running) {
        ssize_t nbytes ;
        {
            // Lock for write
            std::lock_guard<std::mutex> lock(readWriteMutex);
            nbytes = write(socketFileDescriptor, &canFrameTx, sizeof(struct can_frame));
        }
            if (running && nbytes == -1) {
                if (errno == EAGAIN || errno == EWOULDBLOCK) {
                    std::cout << "Would block" << std::endl;
                } else if (errno == ENOBUFS) {
                    perror("Buffer fail: ");
                } else {
                    perror("Write fail");
                }
            }
        
        std::cout << "Data written " << sendCounter++ << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

Canloop::~Canloop() {

    std::cout << "Destructor called" << std::endl;
    if (transmit_.joinable()) {
            transmit_.join();
    }

    if(receive_.joinable()) {
        receive_.join();
    }

    
}

int main() {
    std::string canInterface("can0");
    Canloop cantester(canInterface);

    can_frame localCanData;
    char hello[5] = "Hell";
    localCanData.can_id = 0x123;
    localCanData.can_dlc = sizeof(hello) / sizeof(hello[0]);
    sprintf((char *)localCanData.data, hello);
    cantester.setCanData(localCanData);
    cantester.start();
    return 0;
}

由于我从不关闭程序,也没有显式销毁类,因此不应调用析构函数。然而,我看到以下输出。

Starting Rx_
Starting Tx_
Destructor called
Data written 0
Data written 1
Data written 2
Data written 3
Data written 4
Data written 5
Data written 6
Data written 7
Data written 8
Data written 9

为什么叫它?我不希望调用!?

C++ Linux 多线程析 构函数 Socketcan

评论

0赞 Ben Voigt 10/17/2023
尝试删除复制和移动构造函数,以排除有两个实例,其中一个会提前销毁。
0赞 Ben Voigt 10/17/2023
此外,是一个具有自动存储持续时间的变量,因此当您从其作用域返回时,其析构函数将自动运行。cantester
0赞 Ben Voigt 10/18/2023
你说的“我从不关闭程序”是什么意思,为什么你认为不算数?return 0;main()
0赞 rebelliousconformist 10/18/2023
@BenVoigt 多线程期间的上下文切换是否计入超出范围的变量?是的,但是变量的作用域是 main,所以在它保持在 main 中之前,它不应该被销毁?
0赞 Ben Voigt 10/18/2023
线程的全部意义在于它们都独立地取得进展。如此继续,直到它命中,离开作用域,调用局部变量的析构函数。唯一阻止程序退出的是析构函数内部的程序。main()return 0;join()

答: 暂无答案