性能调优:将数据写入多个管道
现在我在一个线程中完成它:
for(unsigned int i = 0; i < myvector.size();)
{
tmp_pipe = myvector[i];
fSuccess = WriteFile( tmp_pipe, &Time, sizeof(double), &dwWritten, NULL );
if(!fSuccess)
{
myvector.erase(myvector.begin()+i);
printf("Client pipe closed\r\n");
continue;
}
fSuccess = WriteFile( tmp_pipe, &BufferLen, sizeof(long), &dwWritten, NULL );
if(!fSuccess)
{
myvector.erase(myvector.begin()+i);
printf("Client pipe closed\r\n");
continue;
}
fSuccess = WriteFile( tmp_pipe, pBuffer, BufferLen, &dwWritten, NULL );
if(!fSuccess)
{
myvector.erase(myvector.begin()+i);
printf("Client pipe closed\r\n");
continue;
}
i++;
}
结果是第一个pipe
获取数据最快,最后pipe
获得最慢。
我正在考虑在单独的线程中执行此操作,以便每个pipe
都得到同等处理。
但是如何在c / c ++中异步运行一个特定的线程函数(主线程应该立即返回)?
答案 0 :(得分:2)
您可以使用CreateThread
函数创建新线程并将管道句柄作为参数传递给线程函数:
DWORD PipeThread(LPVOID param) {
HANDLE hPipe = (HANDLE)param;
// Do the WriteFile operations here
return 0;
}
for(unsigned int i = 0; i < myvector.size(); i++)
CreateThread(NULL, 0, PipeThread, myvector[i], 0, NULL);
请注意,向量类不是线程安全的,因此如果您不同步对myvector.erase
的访问,则会遇到问题,例如。使用关键部分。
更新:由于您提到了高频,您可以使用I/O completion ports代替每个管道的单独线程。然后,您可以使用与WriteFile
重叠的I / O来异步执行写入,并且您可以只有一个额外的线程来监听写入的完成:
// Initial setup: add pipe handles to a completion port
HANDLE hPort = CreateCompletionPort(myvector[0], NULL, 0, 1);
for (unsigned int i = 1; i < myvector.size(); i++)
CreateCompletionPort(myvector[i], hPort, 0, 0);
// Start thread
CreateThread(NULL, 0, PipeThread, hPort, 0, NULL);
// Do this as many times as you want
for(unsigned int i = 0; i < myvector.size(); i++) {
OVERLAPPED *ov = new OVERLAPPED;
ZeroMemory(ov, sizeof ov);
WriteFile(myvector[i], buffer, size, NULL, ov);
// If pipe handle was closed, WriteFile will fail immediately
// Otherwise I/O is performed asynchronously
}
// Close the completion port at the end
// This should automatically free the thread
CloseHandle(hPort);
---
DWRD PipeThread(LPVOID param) {
HANDLE hPort = (HANDLE)param;
DWORD nBytes;
ULONG_PTR key;
LPOVERLAPPED ov;
// Continuously loop for I/O completion
while (GetQueuedCompletionStatus(hPort, &nBytes, &key, &ov, INFINITE)) {
if (ov != NULL) {
delete ov;
// Do anything else you may want to do here
}
}
return 0;
}
答案 1 :(得分:0)
您有writev()
可用吗?如果是这样,您可以将每个管道的三个写入操作减少到一个,这样会更有效。它还略微简化了错误处理,但您可能会崩溃所需的内容:
for (unsigned int i = 0; i < myvector.size(); i++)
{
tmp_pipe = myvector[i];
if (!WriteFile(tmp_pipe, &Time, sizeof(double), &dwWritten, NULL) ||
!WriteFile(tmp_pipe, &BufferLen, sizeof(long), &dwWritten, NULL) ||
!WriteFile(tmp_pipe, pBuffer, BufferLen, &dwWritten, NULL))
{
myvector.erase(myvector.begin()+i);
printf("Client pipe closed\r\n");
}
}
这在很多方面都比较容易阅读,因为有1/3的错误处理 - 因此操作代码的隐藏性较低。
当然,您仍然希望将此代码包装到线程代码中,因此写操作将由单独的线程处理。你会安排每个线程获得自己的管道;他们共享对时间和缓冲区的只读访问权限。每个线程都会写入并在完成时返回状态。父线程将等待每个子线程,如果子线程报告它失败,则将从向量中移除相应的客户端管道。由于只有父线程会操纵向量,因此无需担心线程问题。
概述:
for (i = 0; i < myvector.size(); i++)
tid[i] = thread create (myvector[i], write_to_pipe);
for (i = 0; i < myvector.size(); i++)
{
status = wait for thread(tid[i]);
if (status != success)
myvector.erase(...);
}
数组(或向量)tid
包含线程标识。 write_to_pipe()
函数是线程主函数;它在传递的管道上写入并以适当的状态退出。
答案 2 :(得分:0)
这些命名管道?如果是这样,您可以在创建它们时使用FILE_FLAG_OVERLAPPED
,这允许您在不处理线程的情况下执行异步写入。这是an example from MSDN。
如果这些是匿名管道,重叠的I / O是not supported,所以这可能是切换到命名管道的一个很好的理由。
对于每次写入,另一个选项可能是queue a work item,但这并不能保证所有三次写入都会同时执行。
答案 3 :(得分:0)
我会考虑以试图减少I / O调用次数的方式准备数据。在知道数据是以必须有效的方式写入并且I / O调用次数最少之后,我会考虑使用异步I / O.如果性能仍然不够好,那么考虑在设计中添加额外的线程。
您可以减少写入次数的一种方法是使用组合所有数据的结构,以便可以使用一次写入而不是三次写入。需要使用编译指示来消除编译器可能添加的任何额外填充/对齐。
#pragma pack(push,1)
struct PipeData {
double _time;
long _buffer_len;
char* _buffer;
};
#pragma pack(pop)
PipeData data;
int data_len = sizeof(double) + sizeof(long) + <yourbufferlen>;
for(unsigned int i = 0; i < myvector.size();)
{
tmp_pipe = myvector[i];
fSuccess = WriteFile( tmp_pipe, &data, data_len, &dwWritten, NULL );
if(!fSuccess)
{
myvector.erase(myvector.begin()+i);
printf("Client pipe closed\r\n");
continue;
}
i++;
}
答案 4 :(得分:0)
要专门回答你的问题“如何在c / c ++中异步运行一个特定的线程函数(主线程应立即返回)?”
你可以通过解除这两个行为来轻松地做到这一点。创建一个工作线程池,由需要与之通信的管道初始化,并编写一个函数,为这些线程安排新作业。与您的管道写入相比,这将是即时的,并且所有线程将获得其工作,并且将在您的主线程可以继续其工作时开始写入它控制的管道。
如果您拥有的客户数量很少,这将是一个简单的解决方案。如果没有,为每个客户端创建一个线程将在一个点之后不会扩展很多,并且您的服务器将因为大量的上下文切换和线程争用而变得很大。
在针对服务器设计的场景中,您应该认真考虑卡萨布兰卡的解决方案。它只创建一个线程来监听完成通知,并且是win 2003中用于在Windows中创建服务器的最有效的服务器设计。