【问题标题】:Run function when pthread exitspthread 退出时运行函数
【发布时间】:2012-03-25 15:32:40
【问题描述】:

我有一个 C++ 应用程序,我在其中创建 pthread 来运行用户提供的函数。我希望能够在线程退出时以某种方式收到警报,以便我可以将它从我用来保留线程的 pthread 数组中删除。有没有办法做到这一点,或者该函数是否应该设置一些“神奇值”。因为生成 pthread 的主要代码处于某种运行循环中,所以我可以轻松检查退出条件。


另外,使用std::vector<pthread_t> 来跟踪我的线程是否是过载?线程的数量不一定是恒定的,可以运行很多线程或很少。或者是否有另一个 STL 容器对这些添加和删除有好处(添加总是在一端,删除几乎在任何地方)。是否有其他结构用于跟踪 pthread?堆栈或列表会在这里吗?还是一个标准的 C 数组,它有一个最大的好处?由于问题的性质,我还可以维护一个固定大小的工作线程数组,我将必须执行的用户函数传递给这些线程。这是一个好的解决方案吗?

很抱歉,这个问题长期令人困惑,但我只使用动态语言中的线程,这永远不会成为问题。


编辑(2012 年 3 月 8 日): 在阅读了@jojojapan 的回答后,我决定使用各种线程池。在我的结构中,我有一个生产者(运行循环中的一个线程)和许多消费者(池中的工作线程)。是否有为多线程单生产者多消费者使用而设计的数据结构?或者我应该在上面使用std::queuepthread_mutex_t 吗?

【问题讨论】:

  • 当你说“运行用户提供的函数”时,是你在编写线程函数吗?
  • 动态语言?你的意思是 GC 语言;)。
  • @Duck:不。我正在编写一个库,使用它的程序员编写这些函数。
  • @J.N.:是的,这就是我的意思。具体来说是 Ruby 和 C#。

标签: c++ c pthreads threadpool


【解决方案1】:
  1. 您可能要考虑的一个选项是在线程完成任务后实际上不结束和删除线程,而是让它们保持活动状态并让它们等待分配新任务给他们。你可以通过做两件事来做到这一点:

    1. 在线程中使用(几乎)无限循环
    2. 使用并发队列或其他一些技术,使它们等待另一个线程发出信号。在几个 SO 问题中讨论了设计模式和策略,例如this one
  2. 如果您真的想在线程结束后发送信号,您可以使用pthread_cond_t 并在线程到达其return 语句之前对其调用pthread_cond_signal .当然,这假设有一些其他线程正在运行,等待这些信号并通过从向量中删除相应的线程来对它们起作用。有关用法的详细信息,请参见相应的手册页,以及 this SO post

编辑与评论和问题的编辑部分相关:

  1. 关于工作线程的数量:这取决于线程使用最多的资源。如果这些线程所做的主要是计算和一些内存访问,换句话说,如果它们受 CPU 限制,那么使用 CPU 可以维护的尽可能多的线程是有意义的(具体来说,有一定数量的内核,和每个内核的(硬件)线程数,您的 CPU 在它们开始相互减速之前可以运行。您正在创建的线程(软件线程)应该大约一样多,或者可能更多(最多两倍根据what @Tudor says here)),硬件线程是合理的。但是,如果您的线程大量使用内存(内存绑定)或硬盘(IO 绑定)或其他资源,例如网络、NFS 或其他服务器,您可能希望按顺序减少线程数(a ) 不会导致它们相互阻塞,以及 (b) 不会对某些资源施加不合理的过多负载。确定正确的线程数可能需要进行试验,保持可配置的数量通常是个好主意。

  2. 关于存储工作任务的最佳数据结构:我在上面进一步引用的帖子的 cmets 中提到的 concurrent bounded queue 可能非常好。不过我自己没试过。但是,如果您想让事情变得简单,那么标准的 std::queue,甚至只是一个 std::vector 都不是一个糟糕的选择,如果您使用信号/互斥技术正确保护它们。

【讨论】:

  • 感谢您的回答。你的第一个选择很有意义。只有两件事:一,你认为我应该有多少个工作线程,二,我应该使用什么数据结构来存储需要执行的调用? (std::queuepthread_mutex_t 锁定,或一些特殊的线程队列类)。另外,我也会在问题中提出这一点,有一个生产者和任意数量的消费者。
【解决方案2】:

一个简单的方法就是使用管道。

在产生线程之前打开管道。将管道 fd 作为线程数据的一部分传递。在线程退出之前,让它将其pthread_self() 写入管道。在管道的读取端有主螺纹或单独的螺纹。它读取死线程的 tid 并立即执行 pthread_join。 (如果它是一个单独的 reaper 线程,它可以阻塞管道读取;如果它在 main 中,则让它成为您的选择/轮询或其他任何内容的一部分。)

这使您可以灵活地不使用数据结构来保存 TID(如果不需要)。如果您确实想保存它们,那么列表或地图是比矢量更好的选择。

如果您有主启动线程和一个单独的“收割者”线程收集它们,并且您希望将它们保存在某个结构中,那么您将需要在两者之间同步对结构的访问。

【讨论】:

  • 管道似乎是一个重量级的工具,仅用于在同一进程的两个线程之间传递 TID。我倾向于使用已完成 TID 和 pthread_cond_signal 的共享列表来通知收割者新的 TID 已添加到列表中。
  • 一根管子的重量不是特别重,在这种情况下,它分摊在 N 个线程上。与 PIPE_BUF 大小以下的数据同步是自动的。以管道副本为代价的一点是它的相对易用性。
【解决方案3】:

考虑完全改变策略并使用现有的线程池库。他们会为你完成这项工作,你会省去很多不那么有趣的调试。

Boost.thread 池是其中之一,link

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-09
    • 2015-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多