在另一个线程中独立计时 For 循环的每个滴答声的最佳方法
Posted
技术标签:
【中文标题】在另一个线程中独立计时 For 循环的每个滴答声的最佳方法【英文标题】:Best approach to Independently Timing each Tick of a For Loop in another Thread 【发布时间】:2021-10-28 01:06:40 【问题描述】:假设我有一个客户端线程和一个服务器线程。客户端线程必须执行昂贵的 for 循环操作,这很容易挂起。这样,服务器就可以独立判断for循环的每个tick是否超过了最大时间。这背后的背景是,如果完成一个滴答的时间过长,服务器将使客户端超时。
我最初的想法是在客户端和服务器线程中有两个 for 循环。服务器线程将有一个等待 1 秒的条件变量。如果客户端在每个tick的1秒内没有通知条件变量,服务端会超时:
服务器
bool success;
for (int i = 0; i < 10; i++)
std::unique_lock<std::mutex> lock(CLIENT_MUTEX);
success = CLIENT_CV.wait_for(lock, std::chrono::seconds(1));
if (!success)
std::cout << "timed out during tick " << i << std::endl;
break;
客户
for (int i = 0; i < 10; i++)
std::unique_lock<std::mutex> lock(CLIENT_MUTEX);
//do work
CLIENT_CV.notify_one();
但是,考虑到客户端的相同工作,我的实现尝试是不可靠的,并且会随机超时。如何改进设计以使其更可靠?
旁注:
一个简单的解决方案是让服务器对整个 for 循环而不是每个滴答计时。但是,如果 for 循环在 10 次中的第 1 次失败,并且计时器正在等待 10 秒,则将在 10 秒后通知客户端。但是,如果服务器对每个滴答(10x1sec = 10 秒)施加 1 秒超时,则客户端将被通知超时,而无需等待整整 10 秒。
编辑。
整个客户端/服务器/超时的类比只是将问题置于上下文中。我完全对从不同线程计时 for 循环的最佳方法感兴趣。
【问题讨论】:
不可靠到底是什么意思?如果它随机超时,这可能只是表明工作需要大约 1 秒并且触发超时的次数相当多(即对于需要 0.8+-0.1 秒的任务,您预计大约 50 分之一会超时如果超时是 1 秒),或者它没有计时 1 秒但有时会在 0.1 秒后超时? @RichardCritten 超时部分不是问题的一部分。我只是对为 for 循环的每个滴答计时的最佳方法感兴趣。在我真正的问题中,它没有超时。我只是想表明对这个过程进行计时很重要。 这是一个 XY 问题,真正的问题是如何通知/终止客户端线程?如果通知客户端线程关闭,则客户端线程必须循环和轮询(在工作期间),并且它可以根据作为工作包的一部分传递给它的超时来关闭自己。如果客户端线程是硬循环(而不是轮询),那么在客户端线程完成工作之前,无法彻底关闭它。无论哪种方式,都不需要从服务器线程监视客户端线程。 @RichardCritten 我只是想把问题放到上下文中。我的真正问题不涉及服务器或客户端。标题为“在另一个线程中独立计时 For 循环的每个 Tick 的最佳方法”。我现在已经在问题中澄清了这一点。 你可以实现一个账本,它在每个滴答声(以及整个循环之后)接收一个std::chrono::time_point
,它会为每个滴答声计时。您可以将此分类帐提供给定期检查此分类帐的其他线程并休眠,以便在它期望下一个条目时唤醒它,而当它没有时它可以设置一些标志,但我不确定这是否是“最好的办法。不过,我也认为不需要其他线程。
【参考方案1】:
这样做的一种方法可能是:
共享变量:
std::vector<std::chrono::time_point<std::chrono::high_resolution_clock>> ledger;
std::mutex ledger_mtx;
客户:
for (int i = 0; i < 10; i++)
std::scoped_lock lock(ledger_mtx);
ledger.push_back(std::chrono::high_resolution_clock::now());
// Do work
std::scoped_lock lock(ledger_mtx);
ledger.push_back(std::chrono::high_resolution_clock::now());
服务器:
size_t id = 0;
std::this_thread::wait_for(1s); // Some time so that initial write to ledger is made
while(true)
std::scoped_lock lock(ledger_mtx);
if(ledger.size()==id) /* Do something if the thread hangs */
id = ledger.size();
std::chrono::time_point<std::chrono::high_resolution_clock> last_tick = ledger.back();
if(id == 11) break;
std::this_thread::sleep_for(1s - (std::chrono::high_resolution_clock::now() - last_tick));
通过这种方式,您可以对线程进行计时,同时从外部对其进行监控。这是最好的方法吗?可能不会,但它确实为您提供了所需的时间。
【讨论】:
问题始于“...用挂起的线程做点什么...” - 因为没有办法干净地终止挂起或不合作的线程。由于在不使进程处于未定义状态的情况下无法终止线程,因此为线程计时是没有意义的。如果线程正在协作(轮询等),则无需对其计时,因为客户端线程可以处理自己的超时。 @RichardCritten 我明白这一点,但 OP 说 “整个客户端/服务器/超时的类比只是将问题置于上下文中。我纯粹对最佳计时方式感兴趣来自不同线程的 for 循环。”。现在你为什么想要这个也超出了我的范围。 (即使线程挂起,也许仍然可以获得一些基准数据?)。我把这个评论表述得很糟糕,如果线程挂起,我打算做点什么以上是关于在另一个线程中独立计时 For 循环的每个滴答声的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章