如何在c / c ++中异步运行特定的线程函数?

时间:2010-09-11 04:05:09

标签: c windows multithreading

性能调优:将数据写入多个管道

现在我在一个线程中完成它:

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 ++中异步运行一个特定的线程函数(主线程应该立即返回)?

5 个答案:

答案 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中创建服务器的最有效的服务器设计。