views:

469

answers:

3

Everyone knows the classic model of a process listening for connections on a socket and forking a new process to handle each new connection. Normal practice is for the parent process to immediately call close on the newly created socket, decrementing the handle count so that only the child has a handle to the new socket.

I've read that the only difference between a process and a thread in Linux is that threads share the same memory. In this case I'm assuming spawning a new thread to handle a new connection also duplicates file descriptors and would also require the 'parent' thread to close it's copy of the socket?

+5  A: 

No. Threads share the same memory, so they share the same variables. If you close socket in parent thread, it will be also closed in child thread.

EDIT:

  • man fork: The child inherits copies of the parent’s set of open file descriptors.

  • man pthreads: threads share a range of other attributes (i.e., these attributes are process-wide rather than per-thread): [...] open file descriptors

And some code:

#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);
}

Output is:

thread: begin
main: file is closed
thread: close() failed: Bad file descriptor
thread: end
danadam
Do you have a reference?
Robert S. Barnes
I don't have it with me here at work, but I can check my copy of Stevens' UNPv2 once I get home.
Harper Shelby
@Shelby - Thanks, I have a copy of UNP but have only gotten to read about a third of it.
Robert S. Barnes
+5  A: 

On Linux threads are implemented via the clone syscall using the CLONE_FILES flag:

If CLONE_FILES is set, the calling process and the child processes share the same file descriptor table. Any file descriptor created by the calling process or by the child process is also valid in the other process. Similarly, if one of the processes closes a file descriptor, or changes its associated flags (using the fcntl(2) F_SETFD operation), the other process is also affected.

Also have a look at the glibc source code for the details of how it is used in 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);
cmeerw
+4  A: 

In principle, Linux clone() can implement not only a new process (like fork()), or a new thread (like pthread_create perhaps), but also anything in between.

In practice, it is only ever used for one or the other. Threads created with pthread_create share the file descriptors with all other threads in the process (not just the parent). This is non-negotiable.

Sharing a file descriptor and having a copy is different. If you have a copy (like fork()) then all copies must be closed before the file handle goes away. If you share the FD in a thread, once one closes it, it's gone.

MarkR