I want to wait on both a file descriptor and a mutex, what's the recommended way to do this?

后端 未结 8 1218
-上瘾入骨i
-上瘾入骨i 2020-12-31 03:36

I would like to spawn off threads to perform certain tasks, and use a thread-safe queue to communicate with them. I would also like to be doing IO to a variety of file descr

相关标签:
8条回答
  • 2020-12-31 04:08

    This is a very common seen problem, especially when you are developing network server-side program. Most Linux server-side program's main look will loop like this:

    epoll_add(serv_sock);
    while(1){
        ret = epoll_wait();
        foreach(ret as fd){
            req = fd.read();
            resp = proc(req);
            fd.send(resp);
        }
    }
    

    It is single threaded(the main thread), epoll based server framework. The problem is, it is single threaded, not multi-threaded. It requires that proc() should never blocks or runs for a significant time(say 10 ms for common cases).

    If proc() will ever runs for a long time, WE NEED MULTI THREADS, and executes proc() in a separated thread(the worker thread).

    We can submit task to the worker thread without blocking the main thread, using a mutex based message queue, it is fast enough.

    epoll_add(serv_sock);
    while(1){
        ret = epoll_wait();
        foreach(ret as fd){
            req = fd.read();
            queue.add_job(req); // fast, non blockable
        }
    }
    

    Then we need a way to obtain the task result from a worker thread. How? If we just check the message queue directly, before or after epoll_wait().

    epoll_add(serv_sock);
    while(1){
        ret = epoll_wait(); // may blocks for 10ms
        resp = queue.check_result(); // fast, non blockable
        foreach(ret as fd){
            req = fd.read();
            queue.add_job(req); // fast, non blockable
        }
    }
    

    However, the checking action will execute after epoll_wait() to end, and epoll_wait() usually blocks for 10 micro seconds(common cases) if all file descriptors it waits are not active.

    For a server, 10 ms is quite a long time! Can we signal epoll_wait() to end immediately when task result is generated?

    Yes! I will describe how it is done in one of my open source project:

    Create a pipe for all worker threads, and epoll waits on that pipe as well. Once a task result is generated, the worker thread writes one byte into the pipe, then epoll_wait() will end in nearly the same time! - Linux pipe has 5 us to 20 us latency.


    In my project SSDB(a Redis protocol compatible in-disk NoSQL database), I create a SelectableQueue for passing messages between the main thread and worker threads. Just like its name, SelectableQueue has an file descriptor, which can be wait by epoll.

    SelectableQueue: https://github.com/ideawu/ssdb/blob/master/src/util/thread.h#L94

    Usage in main thread:

    epoll_add(serv_sock);
    epoll_add(queue->fd());
    while(1){
        ret = epoll_wait();
        foreach(ret as fd){
            if(fd is queue){
                sock, resp = queue->pop_result();
                sock.send(resp);
            }
            if(fd is client_socket){
                req = fd.read();
                queue->add_task(fd, req);
            }
        }
    }
    

    Usage in worker thread:

    fd, req = queue->pop_task();
    resp = proc(req);
    queue->add_result(fd, resp);
    
    0 讨论(0)
  • 2020-12-31 04:09

    I've solved this exact problem using what you mention, pipe() and libevent (which wraps epoll). The worker thread writes a byte to its pipe FD when its output queue goes from empty to non-empty. That wakes up the main IO thread, which can then grab the worker thread's output. This works great is actually very simple to code.

    0 讨论(0)
提交回复
热议问题