【发布时间】:2011-06-10 17:27:33
【问题描述】:
2011 年 6 月 14 日更新
快速更新... 大多数受访者都专注于处理要记录的消息队列的狡猾方法,但是虽然肯定缺乏优化,但这肯定不是问题的根源。我们将 Yield 切换为短暂睡眠(是的,一旦系统安静下来,Yield 确实会导致 100% 的 CPU)但是系统仍然无法跟上日志记录,即使它远未达到睡眠状态。据我所知,发送并不是很有效。一位受访者评论说,我们应该将 Send() 一起阻塞到一个发送中,这似乎是解决更大的潜在问题的最合适的解决方案,这就是为什么我将其标记为原始问题的答案。我当然同意队列模型存在很大缺陷,因此感谢您对此的反馈,并且我对所有有助于讨论的答案都投了赞成票。
然而,这个练习让我们回顾了为什么我们像我们一样在套接字上使用外部日志记录,虽然以前当日志记录服务器做了很多处理日志条目......它不再做任何事情,因此我们选择远程整个模块并通过一些预先存在的日志框架采用直接到文件的方法,这应该完全消除问题,因为并消除系统中不必要的复杂性。
再次感谢所有反馈。
原始问题
在我们的系统中,我们有两个对这个问题很重要的组件 - 一个是用 Visual C++ 开发的,另一个是 Java (不要问,历史原因)。
C++ 组件是主要服务并生成日志条目。这些日志条目通过 CSocket::Send 发送到 Java 日志服务。
问题
发送数据的性能似乎很低。如果我们在 C++ 端排队,那么队列会在更繁忙的系统上逐步备份。
如果我用一个简单的 C# 应用程序访问 Java Logging Server,那么我可以更快地敲击它,然后我将永远需要从 C++ 工具中获得它,并且它可以很好地跟上。
在 C++ 世界中,将消息添加到队列中的函数是:
void MyLogger::Log(const CString& buffer)
{
struct _timeb timebuffer;
_ftime64_s( &timebuffer );
CString message;
message.Format("%d%03d,%04d,%s\r\n", (int)timebuffer.time, (int)timebuffer.millitm, GetCurrentThreadId(), (LPCTSTR)buffer);
CString* queuedMessage = new CString(message);
sendMessageQueue.push(queuedMessage);
}
在发送到套接字的单独线程中运行的函数是:
void MyLogger::ProcessQueue()
{
CString* queuedMessage = NULL;
while(!sendMessageQueue.try_pop(queuedMessage))
{
if (!running)
{
break;
}
Concurrency::Context::Yield();
}
if (queuedMessage == NULL)
{
return;
}
else
{
socket.Send((LPCTSTR)*queuedMessage, queuedMessage->GetLength());
delete queuedMessage;
}
}
注意ProcessQueue是由外层循环线程自己重复运行的,不包括一堆废话:
while(parent->running)
{
try
{
logger->ProcessQueue();
}
catch(...)
{
}
}
队列是:
Concurrency::concurrent_queue<CString*> sendMessageQueue;
所以我们看到的效果是队列越来越大,日志条目被发送到套接字,但速度比它们输入的速度要低得多。
这是 CSocket::Send 的一个限制,使它对我们没有用处吗?滥用它?还是整个红鲱鱼,问题出在其他地方?
非常感谢您的建议。
亲切的问候
马特·佩德尔斯登
【问题讨论】:
-
尝试注释掉 Yield()。
-
如果我注释掉产量,系统将旋转到 100% CPU。我会认为如果队列中有东西,那么 try_pop 会返回,它甚至不会到达 Yield?还是我错过了什么……?
-
你正忙于旋转,如果队列中没有项目,try_pop 不会阻塞。无论有没有使用 yield() 调用,当前的方案似乎都非常无效。如果队列为空,您将需要以某种方式阻塞,并在数据被推送到队列时唤醒。不过,concurrent_queue 似乎没有提供这一点,因此您必须自己制作一些东西,无论是使用条件变量还是事件。
-
好的,我会接受反馈,我们会调查它 - 但是,如果系统现在大部分空闲(超时),并且日志大约 1 小时过期(大约 150mb 的内存)——为什么现在不能很快地把它炸掉?我假设您描述的情况意味着我们在白天落后,但一旦系统安静下来就会很快赶上。碰巧的是,系统正在以与一整天相同的速度进行记录。在这种情况下,我们每次都从 try_pop 中获取一些东西,并将其记录在线程的每个周期中,大概是这样吗?还是我错过了什么?
-
什么是“线程循环”?如果这个函数在其他线程循环中被连续调用,那么我希望这个函数和调用它的循环在系统其余部分空闲时用完 100% CPU,无论 Yield() 是在还是不是。调用 Yield() 只会立即重新安排该线程,因为没有其他事情可做。这非常令人困惑!
标签: c++ c sockets visual-c++ concurrency