【问题标题】:overhead of thread-synchronization via Events通过事件进行线程同步的开销
【发布时间】:2013-03-19 13:08:22
【问题描述】:

我目前正在试验多线程同步。对于背景,我有一组大约 100000 个对象 - 可能更多 - 我想以不同的方式每秒处理多次。

现在我最关心的是同步的性能。

这是我认为应该可以正常工作的(我省略了所有安全方面,因为这只是一个测试程序,如果出现错误,程序只会崩溃..)。我写了两个函数,第一个由程序的主线程执行,第二个由所有附加线程运行。

void SharedWorker::Start()
{
    while (bRunning)
    {
        // Send the command to start task1
        SetEvent(hTask1Event);

        // Do task1 (on a subset of all objects) here

        // Wait for all workers to finish task1
        WaitForMultipleObjects(<NumberOfWorkers>, <ListOfTask1WorkerEvents>, TRUE, INFINITE);

        // Reset the command for task1
        ResetEvent(hTask1Event);

        // Send the command to start task2
        SetEvent(hTask2Event);

        // Do task2 (on a subset of all objects) here

        // Wait for all workers to finish task2
        WaitForMultipleObjects(<NumberOfWorkers>, <ListOfTask2WorkerEvents>, TRUE, INFINITE);

        // Reset the command for task2
        ResetEvent(hTask2Event);

        // Send the command to do cleanup
        SetEvent(hCleanupEvent);

        // Do some (on a subset of all objects) cleanup

        // Wait for all workers to finish cleanup
        WaitForMultipleObjects(<NumberOfWorkers>, <ListOfCleanupWorkerEvents>, TRUE, INFINITE);

        // Reset the command for cleanup
        ResetEvent(hCleanupEvent);
    }
}

DWORD WINAPI WorkerThreads(LPVOID lpParameter)
{
    while (bRunning)
    {
        WaitForSingleObject(hTask1Event, INFINITE);

        // Unset finished cleanup
        ResetEvent(hCleanedUp);

        // Do task1 (on a subset of all objects) here

        // Signal finished task1
        SetEvent(hTask1);

        WaitForSingleObject(hTask2Event, INFINITE);

        // Reset task1 event
        ResetEvent(hTask1);

        // Do task2 (on a subset of all objects) here

        // Signal finished task2
        SetEvent(hTask2);

        WaitForSingleObject(hCleanupEvent, INFINITE);

        // Reset update event
        ResetEvent(hTask2);

        // Do cleanup (on a subset of all objects) here

        // Signal finished cleanup
        SetEvent(hCleanedUp);
    }

    return 0;
}

为了指出我的要求,我举个小例子: 假设我们从上面得到 100000 个对象,分成 8 个子集,每个子​​集 12500 个对象,这是一个具有 8 个逻辑内核的现代多核处理器。相关部分是时间。所有任务必须在大约 8ms 内完成。

我现在的问题是,我能否从拆分处理中获得显着的时间提升,或者通过事件进行同步是否过于昂贵?或者如果所有任务都需要以这种方式完成,是否还有其他方式可以以更少的工作或处理时间来同步线程?

【问题讨论】:

  • 如果不了解您的任务及其资源要求(CPU、I/O)的更多信息,就不可能回答这个问题。一般来说,您应该尽量减少线程处于等待状态的时间。异步处理是线程间信号的一种替代方法,但这在您的任务执行中可能是不可能的。
  • 哦,是的,抱歉,完全忘记了我的资源需求。任务 1 和 2 是纯 CPU,清理只是为了延迟从任务 2 中删除对象。不幸的是,异步处理是不可替代的,因为任务 1、任务 2 和清理是一个固定的顺序,必须保持顺序,并且必须在下一个任务之前完成任务可以开始了。
  • 听起来您可能可以使用 OpenMP 之类的东西,而不是滚动您自己的线程/信号。这非常适合并行运行类似的、有序的任务。 msdn.microsoft.com/en-us/library/tt15eb9t(v=vs.110).aspx
  • 感谢您对 OpenMP 的提示,我已经对它感兴趣了。你有一些使用 OpenMP 的经验吗?我从第一次性能测试中得到了一些意想不到的结果。使用循环测量固定次数迭代所花费的时间进行了测试(目前没有工作,因为我想知道哪个消耗的时间更少)。所以我以一百万次迭代为目标。在调试模式下,OpenMP 在 100% CPU 下以每秒 16.4k 次迭代 (ips) 运行,在发布模式下,在 100% CPU 下以 440k ips 运行。我的解决方案在两种模式下以 33% 的 CPU 运行速度为 46k ips。从我的角度来看,OpenMP 的结果很糟糕?
  • 没有经验,只是听起来你的模式比求助于自定义任务调度更适合它

标签: c++ multithreading winapi events thread-synchronization


【解决方案1】:

如果您对单个对象的处理速度很快,请不要在线程之间拆分它。 Windows 上的线程同步在每次上下文切换时都会消耗超过 50 毫秒。这个时间不被系统使用,而只是系统上运行其他东西的时间。

但是,如果每个对象处理大约需要 8 毫秒,则需要跨线程池调度工作。但是,对象处理可能会有所不同,并且大量工作线程会在不同的时刻完成工作。

更好的方法是组织一个同步的对象队列,您可以将对象添加到该队列中以进行处理,并从该队列中取出它们进行处理。此外,由于单个对象的处理远低于线程的调度间隔,因此最好将它们分批进入处理线程(如10-20)。您可以通过测试估计池中的最佳工作线程数和最佳批次大小。

所以伪代码可以如下所示:

main_thread:
    init queue
    start workers

    set counter to 100000
    add 100000 objects to queue
    while (counter) wait();

worker_thread:
    while (!done)
        get up to 10 objects from queue
        process objects
        counter -= processed count
        if (counter == 0) notify done

【讨论】:

  • 请为您的陈述提供理由,即上下文切换在 Windows 上需要 50 毫秒。
  • 线程池的用途与我需要的不同。我的循环不断地运行并一遍又一遍地执行相同的三个任务,直到它停止。该集合基本保持不变(每次迭代少于十个新/删除的对象),因此同步队列将是一个很大的开销。此外,循环的一次迭代可能需要 8ms,而不是单个对象的处理。
  • @SteveTownsend 我没有准确的数字,但这里有一个链接:stackoverflow.com/questions/2898344/…。根据我的经验,线程在上下文切换时可能会松动超过 50 毫秒(如问题 cmets)。
  • @user2186380 如果整个处理需要 8 毫秒,你不想玩多个线程。
  • 那个讨论是关于时间切片的——q 和 cmets 都没有提到上下文切换
猜你喜欢
  • 1970-01-01
  • 2012-11-19
  • 2021-04-28
  • 2018-12-09
  • 1970-01-01
  • 2019-07-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多