当 pollfd 在另一个线程上更改时,Linux 和 OS X 之间 poll() 的差异

hei*_*ine 4 sockets linux macos multithreading libwebsockets

我试图让 libwebsockets 在 OS X 上的多线程环境中运行。我无法触发从与主服务线程不同的线程发送数据。在 libwebsocket 文档上暗示这应该是可能的(演示代码邮件列表)。于是我深入研究代码,发现问题出在 poll() 函数中。

struct pollfd对于作为参数给出的参数,poll() 的行为似乎有所不同。libwebsockets 依赖于在 poll() 处于活动状态时更改 fds.event 字段的可能性。这在 Linux 上运行良好,但在 OS X 上不起作用。

我编写了一个小测试程序来演示该行为:

#include <unistd.h>
#include <netdb.h>
#include <poll.h>
#include <iostream>
#include <thread>

#define PORT "3490"

struct pollfd    fds[1];
bool connected = false;

void main_loop() {
    int sockfd, new_fd; 
    struct addrinfo hints, *servinfo, *p;
    socklen_t sin_size;
    int yes=1;
    char s[INET6_ADDRSTRLEN];
    int rv;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE; 

    if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return;
    }

    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            perror("server: socket");
            continue;
        }

        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
            perror("setsockopt");
            exit(1);
        }

        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("server: bind");
            continue;
        }

        break;
    }

    freeaddrinfo(servinfo);

    if (p == NULL)  {
        fprintf(stderr, "server: failed to bind\n");
        exit(1);
    }

    if (listen(sockfd, 10) == -1) {
        perror("listen");
        exit(1);
    }

    printf("server: waiting for connections...\n");

    new_fd = accept(sockfd, NULL, &sin_size);
    if (new_fd == -1) {
        perror("accept");
        return;
    }

    fds[0].fd = new_fd;
    fds[0].events = POLLIN;
    connected = true;

    printf("event is %i\n", fds[0].events);
    int ret = poll(fds, 1, 5000);
    printf("event is %i\n", fds[0].events); //expecting 1 on Mac and 5 on Linux

    if (send(new_fd, "Hello, world!\n", 14, 0) == -1)
        perror("send");

    close(new_fd); 
    close(sockfd);
}

void second_thread()
{
    while(connected == false){}
    sleep(1);
    fds[0].events = POLLIN|POLLOUT;
    printf("set event to %i\n", fds[0].events);
}

int main() {

    std::thread t1(main_loop);
    std::thread t2(second_thread);

    t1.join();
    t2.join();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

在 OS X 上使用编译clang++ -std=c++11 -stdlib=libc++ -o poll poll.cpp ,在 Linux 上使用使用g++ -std=c++11 -pthread -o poll poll.cpp

该程序开始侦听端口 3490。如果您连接到它(例如使用netcat localhost 3490),它将轮询主线程上的输入并尝试更改第二个线程中的事件标志。5 秒后将退出。

OS X 上的输出:

server: waiting for connections...
event is 1
set event to 5
event is 1
Run Code Online (Sandbox Code Playgroud)

Linux 上的输出:

server: waiting for connections...
event is 1
set event to 5
event is 5
Run Code Online (Sandbox Code Playgroud)

所以我的问题是:是否有任何文档可以解释这种行为?libwebsockets 期望在 poll 处于活动状态时更改 fds.events 是合法的,这安全吗?我在联机帮助页( OS XLinux )中找不到有关它的任何详细信息。

Sam*_*hik 6

您似乎首先说,您发现了一些文档,声称这是受支持和定义的行为。我很想知道你在哪里读到这篇文章,因为我在 Linux 手册页中找不到任何内容poll(2)的 Linux 手册页中找到任何内容,也无法在poll() 的 POSIX 手册页中找到任何记录不同线程实际上可以更改的内容另一个线程传递给 poll() 的事件数组参数中的值,并且不同线程的更改实际上在原始线程的 poll() 调用中生效,而不管与内存屏障等相关的任何问题。

对我来说,这两个手册页似乎对这个主题完全保持沉默。它们不表明这是否是预期的、支持的或定义的行为;或者这是否不是受支持或定义的行为。

提议不同的线程可以修改另一个线程发出的系统调用的参数,之后——似乎相当违反直觉。如果这是受支持的行为,我希望它能被明确记录,并且我在 Linux 或 POSIX 手册页中找不到任何对它的引用。

话虽如此:即使我将我的软件范围限制在Linux,即使我不需要关心其他平台;鉴于没有任何相关文档,即使我的测试显示 Linux 内核以这种方式实现 poll(2),我也不希望得到任何保证,某些未来的内核版本将继续以这种方式运行。我无法依赖这种行为,除非我测试它的特定内核版本。

因此,回答您的问题:关于此主题的唯一权威文档是相关的手册页。他们没有明确将此记录为合法行为;尽管他们也没有明确表示这是非法行为,但出于上述原因,我认为这是不受支持的、未定义的行为。