【问题标题】:C++, What and/or where is a pthread executing?C++,什么和/或在哪里执行 pthread?
【发布时间】:2012-02-16 22:41:02
【问题描述】:

我已经构建了一个多线程生产者-消费者(添加到队列,使用多个线程从队列中消耗),但我试图通过将新的生产()直接发送到执行线程来进一步优化,如果它们是空闲的(而不是将其排入队列)。

所以,我需要弄清楚线程当前在哪里执行(它当前是有条件地等待,还是正在执行某些事情)。谁能建议一种方法来做到这一点?

【问题讨论】:

    标签: c++ multithreading pthreads producer-consumer


    【解决方案1】:

    如果执行线程空闲,它不会在队列中等待吗?让它完成一些工作的最快方法可能是将工作推到队列中。

    你有理由相信队列是一个瓶颈吗?

    【讨论】:

    • '如果执行线程空闲,是不是在队列中等待?' - 不一定 - 我有一个狡猾的计划.. :)
    【解决方案2】:

    这就是队列应该做的。

    首先,线程不能空闲,除非队列为空,对吧?

    那么您的“入队和信号”操作是做什么的?它在线程可以找到数据的地方放置一个指针,然后告诉线程处理数据。无论如何,这是做你想做的事情的最低限度的任务。

    所以应该不可能优化。

    【讨论】:

    • 优化是可能的,并且确实提供了一些性能提升。我不记得有多少 - 几十年前我在 Delphi 队列中使用了 OP 设计。我确实记得它击败了基于信号量的队列、Windows 消息队列和 IOCP 队列(当用于普通的线程间通信时,它们的速度非常慢)。
    • 如果可以优化,则应该将其内置到队列设计中,而不是将其视为单独的调度路径。
    • 是的,应该可以将这样的队列实例化为通用队列的后代 - 没有全局变量,没有外部标志。
    【解决方案3】:

    您可以为每个线程设置一个全局标志,指示它是否正在等待。只需在进入 pthread_cont_wait 之前设置标志并在释放时将其重置。

    说了这么多,我真的不明白你为什么要冒险放弃经典的任务队列模式。它在大多数情况下都能正常工作。

    【讨论】:

    • 全局标志,除了“全局”方面,除非用锁保护,否则一切都变得混乱。线程状态检查和其他类似的微管理几乎总是出错。
    • @MartinJames 它将受到锁的保护,因为您不能调用pthread_cond_wait,除非您持有保护共享状态的互斥锁。但我同意你们俩的观点,这种微观管理不太可能产生成效。
    【解决方案4】:

    你可以这样做,但你是否真的想要是另一回事 - 请参阅其他帖子。

    首先,忘记所有等待公共信号量的消费者线程。为了做你想做的事,等待的消费者线程必须通过实例来解决。为此,出现的消费者锁定队列并发现它为空,需要等待它自己的事件。此外,消费者需要在其“pop”调用中提供它想要放置对象的地址。因此,除了“普通”对象队列之外,需要等待的消费者线程还需要一个包含指针和要等待的事件的结构。您可以在创建 P-C 队列时创建这些 wait_struct 的数组或循环缓冲区。

    那么你就准备好了。

    PRODUCER:(使用对象 ref/ptr 调用 push) 获取队列锁并检查 wait_structs 列表。如果有一个条目,它将其对象加载到由 wait_struct 指针指向的地址中,(因此“直接向执行线程发送一个新的 producer()”),并发出 wait_struct 事件的信号。如果 wait_structs 列表中没有条目,则生产者将其对象放入对象队列中。哦,是的 - 释放队列锁:)

    CONSUMER:(调用 pop 并使用它想要一个对象引用的地址) 获取队列锁并检查对象队列计数。如果它不为零,它会弹出对象,将其推入它提供的目标地址,释放锁并继续运行。如果对象队列为空,消费者在wait_structs列表中获得一个空闲的wait_strut,将指针设置为它传入的值,释放队列锁并等待事件。当事件发出信号时,消费者已经拥有了它的对象(由生产者推入),并且可以继续运行 - 无需再次访问 PC 队列。

    是的,这种设计有效,(无论如何,在 Delphi 中 - 应该在 C++ 中工作),并且比“经典”基于信号量的 PC 队列更快(比 Windows 消息队列快,比IOCP 队列)。

    我已经让它在超时的情况下工作 - 我会让你弄清楚如何做到这一点。 (提示 - 您必须使用消费者对象的位置,(由传入的指针寻址)作为临时存储:)

    【讨论】:

    • 这是一个有趣的想法,但它如何更快呢?辅助代码路径仅在队列为空时运行。如果队列为空,那么您的工作人员处于空闲状态,并且通过让他们在下一个工作项上“跳过队列”,整体吞吐量不会发生有意义的变化。如果您的员工处于空闲状态,那么您需要让他们做更多事情,而不是让他们等待得更快。
    • 这就是您要求的“直接向执行线程发送一个新的生产(),如果它们是空闲的(而不是将它排入队列)”。由于数量减少,它更快内核调用与“经典”PC 队列相比。如果队列保护是 futex/criticalSection,则在生产者将对象添加到对象队列的情况下,绝对不需要内核调用(即,没有消费者等待),并且消费者从队列中取出对象,(消费者不需要等待)。
    • @DerekPark - '如果你的工人闲着,那么你需要给他们更多的工作' - 如果他们无事可做,我不能这样做。他们是空闲的,因为没有工作。如果有工作,他们就不会闲着。我能想到的唯一进一步优化需要为每个线程提供大量私有数据,即。每个希望使用队列的线程都必须先“登录”才能获得它们的私有队列接口对象。这对于一般的 P-C 队列来说太混乱了——它正在成为一个线程池管理器。
    • 我不是 OP,也没有问如何直接发送给工人。我的问题是您的提案实际上如何更快。我知道从概念上讲,您可能会避免使用您的方法进行内核调用。但这在宏观层面上并不重要。如果您的工作人员处于空闲状态,那么尝试优化工作项传输的速度不会提高吞吐量。如果你的工人没有闲着,那么队列中就会有项目,你的备用路径实际上不会运行。
    • '我不是 OP,也没有问如何直接发送给工人' - 过失,对不起。您对其他问题是正确的 - 只有在消费者短期空闲且延迟很重要的情况下,才能预期显着的性能提升。在具有 150 个生产者和 150 个消费者的 PC 队列压力测试工具中,延迟对于高吞吐量很重要。在 99% 的家庭应用程序中,它并不那么重要,这就是为什么我通常使用简单的信号量队列。
    猜你喜欢
    • 1970-01-01
    • 2015-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-28
    • 2016-10-23
    • 2014-05-02
    • 2011-03-07
    相关资源
    最近更新 更多