为什么我的文件加载线程没有与主线程并行化?

时间:2009-04-08 06:36:19

标签: c++ multithreading winapi

我的程序会在后台进行文件加载和memcpy,而屏幕则需要以交互方式进行更新。这个想法是让程序很快就需要异步加载文件,以便在主线程需要它们时可以使用它们。但是,加载/副本似乎并不与主线程并行发生。主线程在加载过程中暂停,并且经常等待所有加载(一次最多可以达到8),以便在主线程主循环的下一次迭代之前完成。

我正在使用Win32,所以我使用_beginthread来创建文件加载/复制线程。

工作线程功能:

void fileLoadThreadFunc(void *arglist)
{
    while(true)
    {
        // s_mutex keeps the list from being updated by the main thread
        s_mutex.lock();  // uses WaitForSingleObject INFINITE
        // s_filesToLoad is a list added to from the main thread
        while (s_filesToLoad.size() == 0)
        {
            s_mutex.unlock();
            Sleep(10);
            s_mutex.lock();
        }
        loadObj *obj = s_filesToLoad[0];
        s_filesToLoad.erase(s_filesToLoad.begin());
        s_mutex.unlock();

        obj->loadFileAndMemcpy();
    }
}

主线程启动:

_beginThread(fileLoadThreadFunc, 0, NULL);

主类中用来“踢”线程以加载文件的类中的代码:

// I used the commented code to see if main thread was ever blocking
// but the PRINT never printed, so it looks like it never was waiting on the worker
//while(!s_mutex.lock(false))
//{
//  PRINT(L"blocked! ");
//}
s_mutex.lock();
s_filesToLoad.push_back(this);
s_mutex.unlock();

基于评论的更多说明:

  • 工作线程中的loadFileAndMemcpy()函数通过Win32 ReadFile函数加载 - 这会导致主线程被阻塞吗?
  • 我将工作线程优先级降低到THREAD_PRIORITY_BELOW_NORMAL和THREAD_PRIORITY_LOWEST,这有点帮助,但当我移动鼠标以查看工作线程工作时它的移动速度有多慢时,鼠标会“跳”一点(没有降低优先级,情况更糟糕。)
  • 我在Core 2 Duo上运行,所以我不希望看到任何鼠标滞后。
  • 互联网代码似乎不是一个问题,因为“被阻止!”从未在上面的测试代码中打印过。
  • 我将睡眠时间提高到100毫秒,但即使是1000毫秒,鼠标滞后也没有帮助。
  • 正在加载的数据很小 - 20k .png图像(但它们是2048x2048)..它们体积小,因为这只是测试数据,图像中只有一种颜色,所以实际数据会大得多。

6 个答案:

答案 0 :(得分:1)

您必须显示主线程的代码,以指示如何通知它已加载文件。最有可能的是阻塞问题。如果您可以将其用于主循环,那么使用异步I / O而不是线程确实是一个很好的例子。如果没有别的,你真的需要使用条件或事件。一个用于触发文件阅读器线程,有工作要做,另一个用于向主线程发信号通知文件已加载。

编辑:好的,所以这是一个游戏,你正在轮询以查看文件是否作为渲染循环的一部分加载完成。这是我要尝试的:使用ReadFileEx启动重叠读取。这不会阻止。然后在主循环中,您可以使用其中一个具有零超时的等待函数来检查读取是否已完成。这也不会阻止。

答案 1 :(得分:0)

不确定您的具体问题,但确实也应该互相保护大小调用。

void fileLoadThreadFunc(void *arglist) {
    while (true) {
        s_mutex.lock();
        while (s_filesToLoad.size() == 0) {
            s_mutex.unlock();
            Sleep(10);
            s_mutex.lock();
        }
        loadObj *obj = s_filesToLoad[0];
        s_filesToLoad.erase(s_filesToLoad.begin());
        s_mutex.unlock();
        obj->loadFileAndMemcpy();
    }
}

现在,检查您的具体问题,我发现您提供的代码没有任何问题。如果互斥锁是它们之间唯一的争用,那么主线程和文件加载器线程应该很乐意并行运行

我这样说是因为可能存在其他争用点,例如在标准库中,您的示例代码未显示。

答案 2 :(得分:0)

我会以这种方式编写循环,减少锁定解锁,这可能会搞砸:P:

void fileLoadThreadFunc(void *arglist)
{
    while(true)
    {
        loadObj *obj = NULL;

        // protect all access to the vector
        s_mutex.lock();
        if(s_filesToLoad.size() != 0)
        {
             obj = s_filesToLoad[0];
             s_filesToLoad.erase(s_filesToLoad.begin());
        }
    s_mutex.unlock();

    if( obj != NULL )
            obj->loadFileAndMemcpy();
        else
            Sleep(10);

    }
}

Synchronization

上的MSDN

答案 3 :(得分:0)

如果可以考虑开源选项,Java就像Python [link]一样有阻塞队列[link]。这会减少你的代码(这里的队列绑定到load_fcn,即使用闭包)

def load_fcn():
    while True:
        queue.get().loadFileAndMemcpy()
threading.Thread(target=load_fcn).start()

即使您可能不应该使用它们,python 3.0线程也有_stop()函数,python2.0线程有_Thread__stop函数。您还可以向队列写入“None”值并检入load_fcn()。

另外,如果你愿意,可以搜索“[python] gui”和“[subjective] [gui] [java]”的stackoverflow。

答案 4 :(得分:0)

根据此时提供的信息,我的猜测是文件加载处理程序中的某些内容与主循环交互。我不知道所涉及的库,但根据您的描述,文件处理程序按以下方式执行:

  • 加载20k文件的原始二进制数据
  • 将20k解释为PNG文件
  • 加载到表示2048x2048像素图像的结构中

关于用于实现这些步骤的库,我们会想到以下可能性:

  • 对于未压缩的图像数据的内存分配是否正在保持主线程对其执行的任何绘图/交互操作所需的锁定?
  • 负责将PNG数据转换为像素的调用实际上是否存在与主线程负面交互的低级游戏库锁?

获取更多信息的最佳方法是尝试对文件加载器处理程序的活动进行建模而不使用其中的当前代码...自己编写一个例程来分配正确大小的内存块并执行一些处理将20k的源数据转换为目标块大小的结构...然后一次一位地添加一个逻辑,直到你在性能崩溃时缩小以隔离罪魁祸首。

答案 5 :(得分:-1)

我认为您的问题在于访问FilesToLoad对象。

正如我所看到的那样,当你的线程实际处理它时(根据你的代码每隔10毫秒)和你的主线程试图更新列表时,这个对象被你的线程锁定。这可能意味着您的主线程正在等待一段时间来访问它和/或操作系统会排除可能发生的任何竞争情况。

我建议您根据需要启动工作线程,只是为了加载文件,设置信号量(甚至是bool值)以显示它何时完成或使用_beginthreadex并创建一个暂停每个文件的线程,然后同步它们,以便当每个文件完成后,下一行将恢复。

如果你想让一个线程在后台擦除和加载文件时透明地运行,那么你总是可以让它处理它自己的消息队列并使用Windows消息传递来回传递数据。这节省了很多关于螺纹锁定和避免竞争条件的心痛。