Multithreading 基于它的读-算-写线程安全程序的总体设计思想是什么';s的单线程版本?

Multithreading 基于它的读-算-写线程安全程序的总体设计思想是什么';s的单线程版本?,multithreading,thread-safety,multiprocessing,openmp,Multithreading,Thread Safety,Multiprocessing,Openmp,假设程序的顺序版本已经存在,并且在单个输入文件和其他单个输出文件上实现了一系列“读-计算-写”操作。“读”和“写”操作由第三方库函数执行,这些函数很难(但可能)修改,而“计算”函数由程序本身执行。读写库函数似乎不是线程安全的,因为它们使用内部标志和内部内存缓冲区进行操作 发现该程序是CPU受限的,计划通过设计程序的多处理器版本并为此使用OpenMP利用多个CPU(最多80个)来改进该程序。其思想是用相同的单输入和单输出实例化多个“计算”函数 显然,在确保对读取、数据传输、计算和数据存储的一致访问

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

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

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

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


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

作为快速响应,如果您正在处理单个文件并写入另一个文件,使用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知道何时可以安全地写入

编辑:

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

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


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

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

#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();
  }
}
因此,基本思想是输入()和输出()读取/写入数据块。然后,当其他线程正在读/写时,计算部分将处理一块数据。在notify*()和wait*()中需要一些手动同步工作,但这并不是魔术

干杯,
-michael

恐怕IO需要同步,因为它是通过内部缓冲区运行的。磁盘读取可能是原子性的,但缓冲区存储/检索不是原子性的-磁盘读取请求可能正好出现在上一次读取和上一次缓冲区传输之间。请查看编辑,我无法全部响应