【问题标题】:Join with any terminated thread加入任何终止的线程
【发布时间】:2012-08-06 02:02:10
【问题描述】:

我正在编写一个 pthreading 网络应用程序(用 C 语言),并且正在考虑我应该创建一个线程来处理每个传入连接。但是,我不知道应该使用哪种设计,因为我必须将连接数限制为固定数量(例如 5)。

在阅读 pthread_join 的手册页时,我发现:

There is no pthreads analog of waitpid(-1, &status, 0), that  is,  "join  with  any terminated
thread". If you believe you need this functionality, you probably need to rethink your
application design.

这是为什么?我怎样才能实现我的目标? 谢谢

【问题讨论】:

  • 您是否考虑过只在您的套接字上使用非阻塞 I/O(可能使用 poll、epoll 或 select)并且只为所有连接使用一个线程?使用这种方法,您绝对可以扩展超过 5 个连接 - 这并不难。

标签: c linux unix networking pthreads


【解决方案1】:

这是个好问题。

我认为手册页作者的标准推理是需要使用waitpid 来获取进程,否则它们会使资源悬而未决。 pthreads 的情况并非如此。如果您不需要知道特定线程何时终止(或不需要其返回码),您可以将其设为分离线程;它结束了,就是这样。相反,如果你真的需要加入一个线程,你应该知道你需要加入哪个线程。

进一步考虑,父进程与其子进程之间存在直接的一对多关系。 Waitpid 将等待它的孩子,操作系统将跟踪这些孩子并将它们交付给父母。无论创建多少代进程,这都会发生 - 父进程收获其子进程。

在线程程序中,任何线程都可以创建其他线程。在这种情况下,一个包罗万象的pthread_join 意味着什么?如果所有线程都打算与这个包罗万象的线程连接,那么一切都很好。但是对于某些线程确实需要与其子线程连接而其余线程可以由包罗万象连接的程序呢?操作系统或 pthreads 如何在不构建大量基础架构的情况下跟踪每种情况下哪个join 应用?

我想这是可能的,我想每个人都有机会希望在 pthreads 中有一个通用的waitpid 模拟,但实际上这可能会带来很多开销,这主要是一种烦恼。当您发现自己处于有多个线程要加入但您不知道哪个将首先结束的情况时,您可以构建一个队列(或使用管道或其他)并让垂死的线程指示它应该加入.

【讨论】:

    【解决方案2】:

    如果您要限制并发线程的数量,最好的办法是经常预先创建所有线程(工作线程)并让它们在无限循环中运行,等待排队工作。

    然后主线程负责将工作项(在本例中为连接)添加到该队列以供工作人员处理。

    这样做的好处是简单和快速。简单,因为您永远不必担心线程启动或停止,除了一次,它们始终在运行并为工作项提供服务。

    速度出于同样的原因。它就像一个固定大小的线程池。它还以最有效的方式处理工作项(工作负载会自动平衡,因为线程仅在完成前一项时才会请求新项)。

    在伪代码中,这将类似于以下内容。主线只是简单地将工作项添加到队列中。一旦它完成了所有工作,它可以为每个线程发布一个特殊的完成工作项,然后等待它们全部完成。

    def queue workqueue = empty
    
    def main:
        # start the threads
    
        create threadId[5]
        for each threadId (i):
            threadId[i] = startThread (worker)
    
        # main work loop, finished with (for example) external signal.
    
        while not finished:
            get workitem from some source
            add workitem to workqueue
    
        # Place one special FINISH work item for each thread.
    
        for each threadId (i):
            add FINISH item to workqueue
    
        # Wait for all threads to exit, then exit main.
    
        for each threadId (i):
            wait for threadId[i] to exit
    
        exit
    

    工作线程同样简单。无限循环,根据需要获取工作项并进行处理。

    如果工作项是完成工作项,则退出,保证每个线程得到一个且只有一个完成工作项:

    def worker:
        # Simple infinite loop to get work items.
    
        while true:
            get workitem from workqueue
    
            # Exit if told to.
    
            if workitem is a FINISH item:
                break
    
            # Otherwise, process the item and loop around to request next.
    
            process workitem
    
        exit
    

    【讨论】:

      【解决方案3】:

      请允许我首先回应 paxdiablo 的建议,即您允许线程保持持久性,而不是允许它们终止并重新启动它们。此外,我赞同 selbie 使用非阻塞 I/O 的建议,但我会将它与您的固定线程结合起来(我认为每个 CPU 只有 1 个)。同时使用这两种方法可以让您实现更高的工作负载并最大限度地利用机器的 CPU 资源。

      不过,要回答您的问题,如果您希望能够按终止顺序加入线程,则需要线程与收割者进行通信,以了解它们的终止顺序。这可以通过pipe 相对简单地完成。当线程终止时,它将其tid 写入管道,而收割者读取tid 并执行pthread_join

      int tid_pipe[2];
      pipe(tid_pipe);
      
      void * thread_proc (void *arg) {
          /* ... */
          /* thread exiting */
          pthread_t me = pthread_self();
          write(tid_pipe[1], &me, sizeof(me));
          return 0;
      } 
      
      /* thread reaper */
      while (read(tid_pipe[0], &tid, sizeof(tid)) == sizeof(tid)) {
          pthread_join(tid, &retval);
          /* ... */
      }
      

      但是,作为替代方案,您可以允许线程分离运行,而不必担心加入。相反,您可以使用条件变量让您的主线程知道何时可以启动另一个线程。

      void * thread_proc (void *arg) {
          pthread_detach(pthread_self());
          /* ... */
          /* thread exiting */
          pthread_mutex_lock(&m);
          if (thread_count++ == 0) pthread_cond_signal(&c);
          pthread_mutex_unlock(&m);
          return 0;
      }
      
      /* thread spawner */
      while (waiting_for_work()) {
          pthread_mutex_lock(&m);
          while (thread_count == 0) pthread_cond_wait(&c, &m);
          pthread_mutex_unlock(&m);
          /* ... handle work with new thread ... */
      }
      

      【讨论】:

        【解决方案4】:

        由于线程不像进程那样组织在层次结构中,等待“任何”线程意味着任何线程都可以访问有关所有线程的全局信息。在某种数据结构(或其他)中组织这些信息将是一定的开销,因为所有线程的创建和终止都必须经过那里。这种开销是自愿避免的,线程应该是轻量级和快速的。

        还有一个方面就是有一些线程甚至不能被加入,即那些已经分离或从一开始就分离的线程。将这样的开销强加给他们是更不可接受的。

        【讨论】:

          【解决方案5】:

          如果它是一个多线程服务器,每个客户端一个线程,只需将它们计数出来并重新计数。在客户端线程创建时(即在 accept() 线程中)以及在客户端-服务器线程退出之前使用“clientCount”int 的原子 inc/dec,如果计数 >5,则不再接受()任何连接,(即直接从 accept() 线程发出“连接太多,稍后再试”页面,而不是创建新的服务器-客户端线程)。

          试着忘记“加入”——想象一下你从未读过它。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-05-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-01-28
            • 1970-01-01
            相关资源
            最近更新 更多