由于使用事件导致的开销

Woo*_*kai 6 c++ events multithreading synchronization overhead

我有一个自定义线程池类,它创建一些线程,每个线程等待自己的事件(信号).将新作业添加到线程池时,它会唤醒第一个空闲线程,以便它执行该作业.

问题如下:我有大约1000个循环,每个循环大约10'000次迭代.这些循环必须按顺序执行,但我有4个CPU可用.我尝试做的是将10'000次迭代循环分成4个2'500次迭代循环,即每个线程一次.但是我必须等待4个小循环才能完成下一个"大"迭代.这意味着我无法捆绑作业.

我的问题是使用线程池和4个线程比顺序执行作业要慢得多(由一个单独的线程执行一个循环比直接在主线程中顺序执行它要慢得多).

我在Windows上,所以我创建事件,CreateEvent()然后等待其中一个使用,WaitForMultipleObjects(2, handles, false, INFINITE)直到主线程调用SetEvent().

似乎整个事件(以及使用关键部分的线程之间的同步)非常昂贵!

我的问题是:使用事件花费"很多"时间是否正常?如果是这样,我可以使用另一种机制,而且时间更便宜吗?

下面是一些代码来说明(从我的线程池类复制的一些相关部分):

// thread function
unsigned __stdcall ThreadPool::threadFunction(void* params) {
    // some housekeeping
    HANDLE signals[2];
    signals[0] = waitSignal;
    signals[1] = endSignal;

    do {
        // wait for one of the signals
        waitResult = WaitForMultipleObjects(2, signals, false, INFINITE);

        // try to get the next job parameters;
        if (tp->getNextJob(threadId, data)) {
            // execute job
            void* output = jobFunc(data.params);

            // tell thread pool that we're done and collect output
            tp->collectOutput(data.ID, output);
        }

        tp->threadDone(threadId);
    }
    while (waitResult - WAIT_OBJECT_0 == 0);

    // if we reach this point, endSignal was sent, so we are done !

    return 0;
}

// create all threads
for (int i = 0; i < nbThreads; ++i) {
    threadData data;
    unsigned int threadId = 0;
    char eventName[20];

    sprintf_s(eventName, 20, "WaitSignal_%d", i);

    data.handle = (HANDLE) _beginthreadex(NULL, 0, ThreadPool::threadFunction,
        this, CREATE_SUSPENDED, &threadId);
    data.threadId = threadId;
    data.busy = false;
    data.waitSignal = CreateEvent(NULL, true, false, eventName);

    this->threads[threadId] = data;

    // start thread
    ResumeThread(data.handle);
}

// add job
void ThreadPool::addJob(int jobId, void* params) {
    // housekeeping
    EnterCriticalSection(&(this->mutex));

    // first, insert parameters in the list
    this->jobs.push_back(job);

    // then, find the first free thread and wake it
    for (it = this->threads.begin(); it != this->threads.end(); ++it) {
        thread = (threadData) it->second;

        if (!thread.busy) {
            this->threads[thread.threadId].busy = true;

            ++(this->nbActiveThreads);

            // wake thread such that it gets the next params and runs them
            SetEvent(thread.waitSignal);
            break;
        }
    }

    LeaveCriticalSection(&(this->mutex));
}
Run Code Online (Sandbox Code Playgroud)

Ric*_*ick 1

如果您只是并行化循环并使用 vs 2008,我建议您查看 OpenMP。如果您使用的是 Visual Studio 2010 beta 1,我建议您查看并行模式库,特别是“并行”/“并行每个”API“任务组类”,因为这些可能会执行您的任务试图做到这一点,只是用更少的代码。

关于你关于性能的问题,这实际上取决于。您需要查看在迭代期间安排了多少工作以及成本是多少。如果你经常使用 WaitForMultipleObjects 并且你的工作量很小,那么 WaitForMultipleObjects 可能会非常昂贵,这就是为什么我建议使用已经构建的实现。您还需要确保您没有在调试器下以调试模式运行,并且任务本身不会因锁、I/O 或内存分配而阻塞,并且您不会遇到错误共享。其中每一个都有可能破坏可扩展性。

我建议在 Visual Studio 2010 beta 1 中的xperf f1 分析器(它有 2 种新的并发模式,有助于查看争用)或英特尔的 vtune 等分析器下查看此内容。

您还可以共享您在任务中运行的代码,这样人们就可以更好地了解您在做什么,因为我在性能问题上总是得到的答案首先是“这取决于”,其次是“您有吗?”对其进行了简介。”

祝你好运

-瑞克