【问题标题】:C++ Threads : Trigger a loop from main threadC++ 线程:从主线程触发循环
【发布时间】:2021-05-19 11:50:48
【问题描述】:

我有 4 个传感器,我想在 4 个不同的线程中异步轮询它们。我有一个计时器,它会以 30Hz 的频率调用一个函数,我希望这个函数能够触发线程内的轮询。

我尝试使用std::async 来在每次定时器调用时产生 4 个线程,但创建线程的开销太大。这是我对std::async 所做的:

for (auto& camera : camera_vector_) {
  camera_asyncs_.emplace_back(
      std::async(std::launch::async, [camera]() -> bool {
          return camera->poll();
      })
  );
}

for (auto& future : camera_asyncs_) {
  future.get();
}


现在我需要在程序开始时生成 4 个线程,并等待时钟告诉它们解锁并运行进程功能。像这样:

for (auto& camera : camera_vector_)
{
  camera_threads_.emplace_back(std::thread([&camera]() {
    while (true)
    {
      wait_for_trigger();
      camera->poll();
      block_trigger();
    }
  }));
}

void Driver::poll_threads() {
  for (auto& thread : camera_threads_) {
    trigger_thread(thread);
  }
}

你会怎么做呢?我的研究使我想到了互斥体和条件变量。例如here。但是我发现的例子是启动一堆线程并让它们完成。不要一次在一个线程中触发一个循环。

另外,我这里需要的似乎不是线程池,因为每个线程都是一个定义的作业,只需要一个触发器。我说的对吗?

在我看来,使用互斥锁是正确的做法。我应该将它们存储在与我的线程向量相同大小的第二个向量中吗?我知道我可以在线程内锁定互斥锁,但我可以从主线程解锁互斥锁吗?你有什么实现这一点的技巧吗?

我正在使用 C++14。

感谢您的帮助。

【问题讨论】:

  • 那么当定时器触发时,会产生另外四个新线程?
  • 在线程内部执行等待不是更有意义吗,否则主线程仍然为向线程发出信号而烦恼?
  • @Yves 理想情况下不是,我希望线程等待触发器。
  • 看来你需要的确实是一个线程池。它将允许您保持 4 个线程处于活动状态并在必要时唤醒它们(当您给线程池一个要执行的任务时)。但是对于互斥量和条件变量,这基本上是一样的:)
  • @Fareanor 什么是最简单的?有什么推荐吗?那我应该有一个互斥锁吗?

标签: c++ multithreading c++14


【解决方案1】:

以下是我对您的需求的理解:

  1. 你只需要四个线程,不多不少;
  2. 四个线程相互独立,不共享数据;
  3. 每个线程每 33ms 触发一次工作,在一个 33ms 内,不能多次工作;
  4. 如果触发发生时线程仍在处理上一个作业,则允许线程忽略此触发。

我会做如下设计:

#include <thread>
#include <vector>
#include <chrono>
#include <iostream>
#include <atomic>

using namespace std;

std::atomic_flag lock0 = ATOMIC_FLAG_INIT;
std::atomic_flag lock1 = ATOMIC_FLAG_INIT;
std::atomic_flag lock2 = ATOMIC_FLAG_INIT;
std::atomic_flag lock3 = ATOMIC_FLAG_INIT;

void trigger() {
    while (true) {
        lock0.clear(); // set to false
        lock1.clear(); // set to false
        lock2.clear(); // set to false
        lock3.clear(); // set to false
        std::this_thread::sleep_for(std::chrono::milliseconds(33)); // 33 ms = 1000ms/30
        // from now on, all threads lost the chance to be triggered
        // they have to wait for the next time
        lock0.test_and_set(); // set to true
        lock1.test_and_set(); // set to true
        lock2.test_and_set(); // set to true
        lock3.test_and_set(); // set to true
    }
}

void f0()
{
    while (true) {
        while(!lock0.test_and_set()) {
            std::cout << "f0" << std::endl;
        }
    }
}

void f1()
{
    while (true) {
        while(!lock1.test_and_set()) {
            std::cout << "f1" << std::endl;
        }
    }
}

void f2()
{
    while (true) {
        while(!lock2.test_and_set()) {
            std::cout << "f2" << std::endl;
        }
    }
}

void f3()
{
    while (true) {
        while(!lock3.test_and_set()) {
            std::cout << "f3" << std::endl;
        }
    }
}

int main()
{
    std::vector<std::thread> v; // use std::async if you want
    std::thread t(trigger);
    v.emplace_back(f0);
    v.emplace_back(f1);
    v.emplace_back(f2);
    v.emplace_back(f3);
    for (auto &ele : v) {
        ele.join();
    }
    t.join();

    return 0;
}

这段代码不会阻塞,相反,它会继续运行,直到触发到来之前什么都不做。

【讨论】:

  • 请注意,像test_and_set 这样的操作非常低效,因为它涉及缓存争用。最好在普通加载/读取上旋转,直到锁被解锁。参见,例如,this article
  • 此外,在此设置中,所有原子标志可能最终都位于同一缓存行中,这会使事情变得更糟。我会考虑将它们与缓存行对齐,例如alignas(64)
  • @DanielLangr 但是std::atomic_flag 不提供加载或存储操作...有什么建议吗?
  • 当然,您查看我发布的链接了吗?他们在那里使用std::atomic&lt;bool&gt;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-12
  • 2013-01-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多