在Linux中创建新线程是否会复制文件描述符和套接字描述符?

11

大家都知道典型的进程模型,即在套接字上监听连接并为每个新连接派生一个新进程来处理。通常的做法是父进程立即调用 close 关闭新创建的套接字,减少句柄计数器,以便只有子进程有新套接字的句柄。

我读到过在Linux中进程和线程之间唯一的区别是线程共享同一块内存。在这种情况下,我假设为了处理新连接而生成一个新线程也会复制文件描述符,并且还需要"父"线程关闭它自己的套接字副本吗?


“我读过在Linux中进程和线程之间唯一的区别是线程共享相同的内存。” 实际上,进程和线程之间还有很多其他的区别。例如,进程可以包含多个线程。 - David Schwartz
3个回答

11

不会。线程共享同一块内存,因此它们共享相同的变量。如果您在父线程中关闭套接字,则子线程中也将关闭该套接字。

编辑:

  • man fork:子进程继承父进程的打开文件描述符的副本。

  • man pthreads:线程共享其他范围属性(即这些属性是进程范围而不是每个线程):[...]打开的文件描述符

还有一些代码:

#include <cstring>
#include <iostream>
using namespace std;

#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>

// global variable
int fd = -1;

void * threadProc(void * param) {
    cout << "thread: begin" << endl;
    sleep(2);
    int rc = close(fd);
    if (rc == -1) {
        int errsv = errno;
        cout << "thread: close() failed: " << strerror(errsv) << endl;
    }
    else {
        cout << "thread: file is closed" << endl;
    }
    cout << "thread: end" << endl;
}

int main() {
    int rc = open("/etc/passwd", O_RDONLY);
    fd = rc;

    pthread_t threadId;
    rc = pthread_create(&threadId, NULL, &threadProc, NULL);

    sleep(1);

    rc = close(fd);
    if (rc == -1) {
        int errsv = errno;
        cout << "main: close() failed: " << strerror(errsv) << endl;
        return 0;
    }
    else {
        cout << "main: file is closed" << endl;
    }

    sleep(2);
}

输出结果为:

thread: begin
main: file is closed
thread: close() failed: Bad file descriptor
thread: end

我在工作的地方没有它,但是我回家后可以查一下我的Stevens' UNPv2副本。 - Harper Shelby
@Shelby - 谢谢,我有一本UNP的副本,但只读了大约三分之一。 - Robert S. Barnes

11

从原理上讲,Linux的clone()函数不仅可以实现新进程(与fork()类似),也可以实现介于进程和线程之间的任何操作。

但实际上,它只被用来实现其中一种。使用pthread_create创建的线程会与进程中所有其他线程共享文件描述符(而不仅仅是父线程)。这是不可谈判的。

共享文件描述符和拥有副本是不同的。如果你拥有一个副本(例如fork()),则在文件句柄消失之前必须关闭所有副本。如果你在线程中共享FD,则一旦有一个线程关闭它,它就会消失。


9
在Linux中,线程是通过使用CLONE_FILES标志的clone 系统调用来实现的:
如果设置了CLONE_FILES,则调用进程和子进程共享相同的文件描述符表。由调用进程或子进程创建的任何文件描述符也在另一个进程中有效。类似地,如果其中一个进程关闭文件描述符或更改其关联标志(使用fcntl(2)F_SETFD操作),则另一个进程也会受到影响。
此外,还要查看glibc源代码以了解它在createthread.c中的使用细节:
  int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL
             | CLONE_SETTLS | CLONE_PARENT_SETTID
             | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM
#if __ASSUME_NO_CLONE_DETACHED == 0
             | CLONE_DETACHED
#endif
             | 0);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接