0

考虑到程序的后续版本已经存在,并在单个输入文件和其他单个输出文件上实现了一系列“读取-计算-写入”操作。“读取”和“写入”操作由难以(但可能)修改的第 3 方库函数执行,而“计算”功能由程序本身执行。读写库函数似乎不是线程安全的,因为它们使用内部标志和内部内存缓冲区进行操作。

OpenMP发现该程序受 CPU 限制,并计划通过设计程序的多处理器版本并用于该目的,利用多个 CPU(最多 80 个)来改进该程序。这个想法是用相同的单输入和单输出实例化多个“计算”函数。

很明显,需要做一些事情来确保对读取、数据传输、计算和数据存储的一致访问。可能的解决方案是:(硬)以线程安全的方式重写 IO 库函数,(中等)为 IO 函数编写一个线程安全的包装器,该包装器也可用作数据缓存器。

是否有任何通用模式涵盖转换、包装或重写单线程代码以符合OpenMP线程安全假设的主题?

EDIT1:该程序足够新鲜,可以进行更改以使其成为多线程(或者,通常是并行程序,通过多线程、多处理或其他方式实现)。

4

2 回答 2

1

作为快速响应,如果您正在处理单个文件并写入另一个文件,则使用 openMP 可以轻松地将程序的顺序版本转换为多线程版本,而无需过多关注 IO 部分,前提是计算算法本身可以并行化。

这是真的,因为通常主线程负责 IO。如果由于数据块太大而无法一次读取,并且计算算法无法处理较小的块而无法做到这一点,则可以使用 openMP API 来同步每个线程中的 IO。这并不意味着整个应用程序将停止或等待其他线程完成计算才能读取或写入新数据,这意味着只需要原子地完成读取和写入部分。

例如,如果您的顺序应用程序的流程如下:

1) Read
2) compute
3) Write

鉴于它确实可以并行化,并且需要从每个线程中读取每个数据块,因此每个线程可以遵循下一个设计:

1) Synchronized read of chunk from input (only one thread at the time could execute this section)
2) Compute chunk of data (done in parallel)
3) Synchronized write of computed chunk to output (only one thread at the time could execute this section)

如果您需要以与读取它们相同的顺序写入块,则需要先缓冲,或者采用不同的策略,例如 fseek 到正确的位置,但这实际上取决于输出文件大小是否从一开始就已知,. ..

请特别注意 openMP 调度策略,因为默认值可能不是最适合您的计算算法的。如果您需要在线程之间共享结果,例如您已读取的输入文件的偏移量,您可以使用 openMP API 提供的归约操作,这比让代码的单个部分在所有线程之间原子运行要高效得多,只是为了更新一个全局变量,openMP 知道何时可以安全写入。

编辑:

关于“读、处理、写”操作,只要你在每个工人之间保持每次读写原子性,我想你不会有任何麻烦。即使读取的数据存储在内部缓冲区中,让每个工作人员以原子方式访问它,数据也会以完全相同的顺序获取。您只需要在将该块保存到输出文件时特别注意,因为您不知道每个工作人员将完成处理其属性块的顺序,因此,您可以准备好保存一个块,该块在其他块之后读取仍在处理中。您只需要每个工作人员跟踪每个块的位置,并且您可以保留指向需要保存的块的指针列表,直到您拥有自最后一个保存到输出文件以来的一系列完成的块。

如果您担心内部缓冲区本身(并且请记住,我不知道您正在谈论的库,所以我可能是错的)如果您向某些数据块发出请求,则应该只修改该内部缓冲区在您请求该数据之后和数据返回给您之前;并且当您以原子方式发出该请求时(意味着每个其他工作人员都需要排队等候),当下一个工作人员请求他的数据时,该内部缓冲区应该处于与最后一个工作人员收到其数据时相同的状态块。即使在库特别说明它返回一个指向内部缓冲区位置的指针而不是块本身的副本的情况下,您也可以在释放整个原子读取操作的锁之前复制到工作人员的内存。

如果正确遵循我建议的模式,我真的认为您不会发现在同一顺序版本的算法中找不到的任何问题。

于 2013-09-24T05:47:32.077 回答
1

通过一点同步,您可以走得更远。考虑这样的事情:

#pragma omp parallel sections num_threads
{
#pragma omp section
  {
    input();
    notify_read_complete();
  }
#pragma omp section
  {
    wait_read_complete();
#pragma omp parallel num_threads(N)
    {
      do_compute_with_threads();
    }
    notify_compute_complete();
  }
#pragma omp section
  {
    wait_compute_complete();
    output();
  }
}

因此,基本思想是 input() 和 output() 读/写数据块。然后,计算部分将处理一大块数据,而其他线程正在读/写。在 notify*() 和 wait*() 中需要进行一些手动同步工作,但这并不神奇。

干杯,-迈克尔

于 2013-09-24T20:04:19.177 回答