繁体   English   中英

C++ 在具有同步队列的线程中等待通知

[英]C++ wait notify in threads with synchronized queues

我有一个这样结构的程序:一个线程接收任务并将它们写入输入队列,多个线程处理它们并写入 output 队列,一个用它的结果进行响应。 当队列为空时,线程会休眠几毫秒。 队列内部有互斥锁,推入执行 lock(),弹出执行 try_lock(),如果队列中没有任何内容则返回。

这是处理线程,例如:

//working - atomic bool
while (working) {
    if (!inputQue_->pop(msg)) {
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        continue;
    } else {
        string reply = messageHandler_->handle(msg);
        if (!reply.empty()) {
            outputQue_->push(reply);
        }
    }
}

我不喜欢的是,从收到任务到响应的时间,正如我用 high_resolution_clock 测量的那样,几乎是 0,此时没有睡眠。 当有睡眠时,它会变得更大。 我不希望浪费 cpu 资源并想做这样的事情:当接收线程获取任务时,它通知一个处理线程,它执行 wait_for,当处理任务完成时,它以相同的方式通知响应线程。 因此,我认为我会花费更少的时间,并且不会浪费 cpu 资源。 我有一些问题:

  1. 这会按照我认为的方式工作吗,唯一的区别是在通知时醒来?
  2. 为此,我必须创建 2 个条件变量:第一个用于接收线程和所有处理,第二个用于所有处理和响应? 处理线程中的互斥锁必须对所有线程都是通用的还是唯一的?
  3. 我可以在 if 分支中创建 unique_lock(mutex) 和 wait_for() 而不是 sleep_for 吗?
  4. 如果某些处理线程很忙,是否可能 notify_one() 可以尝试唤醒其中一个,但不能唤醒空闲线程? 我需要使用 notify_all()?
  5. 通知是否有可能不会唤醒任何线程? 如果有,是不是概率大?

这会按照我认为的方式工作吗,唯一的区别是在通知时醒来?

是的,假设你做对了。

为此,我必须创建 2 个条件变量:第一个用于接收线程和所有处理,第二个用于所有处理和响应? 处理线程中的互斥锁必须对所有线程都是通用的还是唯一的?

您可以使用单个互斥锁和单个条件变量,但这会使其更加复杂。 我建议使用单个互斥锁,但线程可能要等待的每个条件都有一个条件变量。

我可以在 if 分支中创建 unique_lock(mutex) 和 wait_for() 而不是 sleep_for 吗?

绝对不。 您需要在检查队列是否为空时持有互斥锁并继续持有它,直到您调用wait_for 否则,您将破坏条件变量的整个逻辑。 与条件变量关联的互斥锁必须保护线程将要等待的条件,在这种情况下是队列非空。

如果某些处理线程很忙,是否可能 notify_one() 可以尝试唤醒其中一个,但不能唤醒空闲线程? 我需要使用 notify_all()?

我不知道您所说的“免费线程”是什么意思。 作为一般规则,如果无法在无法处理条件的条件变量上阻塞线程,则可以使用notify_one 如果可能需要唤醒多个线程,或者可能会在条件变量上阻塞多个线程并且可能唤醒“错误线程”,即可能至少有一个线程,则应使用notify_all不能做任何需要做的事情的线程。

通知是否有可能不会唤醒任何线程? 如果有,是不是概率大?

当然,这很有可能。 但这意味着在这种情况下没有线程被阻塞。 在这种情况下,没有线程可以阻塞条件,因为线程必须在wait之前检查条件,并且它们在持有互斥锁的同时进行。 提供这种原子的“解锁和等待”语义是条件变量的全部目的。

您拥有的机制称为轮询。 线程反复检查(轮询)是否有可用数据。 正如你提到的,它有浪费时间的缺点。 (但这很简单)。 你提到的你想使用的东西叫做阻塞机制。 这将取消调度线程,直到工作变得可用。

1)是的(虽然我不知道你在想什么)

2)a)是的,2个条件变量是一种方法。 b) 普通互斥锁是最好的

3)您可能会将它们放在pop中,并且调用pop可能会阻塞。

4) 不会。 notify_one只会唤醒当前正在等待的线程调用wait 此外,如果有多个在等待,则不一定能保证哪个会收到通知。 (操作系统/库依赖)

5) 否。如果有 1+ 个线程在等待, notify_one保证唤醒一个。 但是如果没有线程在等待,则通知被消耗(并且没有效果)。 请注意,在某些边缘条件下, notify_one实际上可能会唤醒多个。 此外,线程可能会在没有人调用notify_one (“虚假唤醒”)的情况下从wait中唤醒。 完全可能发生的事实意味着您总是需要对其进行额外的检查。

这被称为生产者/消费者问题。

一般来说,您对条件变量的考虑是正确的。 我的建议更多地与此类功能的设计和可重用性相关。 主要思想是实现ThreadPool模式,它有带有工作线程数的构造函数,方法submitTask,shutdown,join。 有了这样的 class,您将使用 2 个池实例:一个用于处理多线程,第二个(由您选择单线程)用于结果发送。 池由任务阻塞队列和工作线程数组组成,每个执行相同的“弹出任务并运行”循环。阻塞队列封装了互斥体和cond_var。 Task 是通用函子。 这也将您的设计带入了面向任务的方法,这在您的应用程序的未来有很多优势。 如果您喜欢这个想法,欢迎您提出有关实施细节的更多问题。 最好的问候,丹尼尔

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM