VS2017下Fortran77 OpenMP线程打印输出异常问题求解
OpenMP并行Fortran循环输出顺序错乱问题
问题复现环境与现象
- 开发环境:Visual Studio 2017,Fortran77语法,OpenMP多线程
- 测试代码逻辑:传入初始值为1的变量VAR,进入并行域前先做+1操作,开启4线程并行执行5次循环迭代,每次迭代先打印当前线程编号,再打印VAR取值,最后调用子例程ROUTINE2。复现代码如下:
SUBROUTINE TAKEVAR(VAR) USE OMP_LIB INTEGER:: VAR VAR = VAR + 1 !$OMP PARALLEL NUM_THREADS(4) !$OMP DO DO i = 1, 5 WRITE(*, *) 'Hello from thread ', OMP_GET_THREAD_NUM() WRITE(*, *) VAR CALL ROUTINE2(VAR) ENDDO !$OMP END DO !$OMP END PARALLEL END SUBROUTINE
- 实际运行表现:总迭代次数符合预期(4线程合计执行5次),但线程编号的打印和VAR值的打印没有按代码顺序成对出现,输出顺序混乱。
根本原因
- 标准输出本身是进程共享的全局资源,默认不带线程安全锁,且Fortran的
WRITE(*,*)默认带缓冲机制:单次write调用的内容不会立刻刷到控制台,会先存在内存缓冲区里,等缓冲区满或者程序退出时统一输出。多个线程同时往同一个缓冲区写内容时,不同线程的write调用内容会交错填充到缓冲区,自然不会保持单线程内的代码书写顺序。 - 变量VAR默认是并行域内的共享变量,没有做任何线程同步保护,如果ROUTINE2内部会修改VAR值,多线程同时读写VAR会触发数据竞争,属于未定义行为,不仅会导致输出值异常,严重时还可能让程序崩溃。
修复方案
- 解决输出错乱问题:
- 调试阶段最简单的方案是用临界区把输出逻辑包起来,保证同一时间只有一个线程能操作标准输出,这样同一个线程内的连续write就不会被其他线程插队:
注意这种方式会让输出逻辑变成串行,会损失并行性能,不要在正式业务逻辑里使用。DO i = 1, 5 !$OMP CRITICAL WRITE(*, *) 'Hello from thread ', OMP_GET_THREAD_NUM() WRITE(*, *) VAR !$OMP END CRITICAL CALL ROUTINE2(VAR) ENDDO - 如果不想加锁,可以把同一次要输出的内容合并到单条write语句里,同时强制刷新缓冲区,能保证单条语句的内容不会被拆分,但没法保证不同线程的输出先后顺序——多线程程序本身就不应该假设不同线程的执行先后顺序。
- 调试阶段最简单的方案是用临界区把输出逻辑包起来,保证同一时间只有一个线程能操作标准输出,这样同一个线程内的连续write就不会被其他线程插队:
- 解决VAR的数据竞争问题:
- 如果每个迭代需要独立操作VAR的副本,就在OpenMP DO指令上增加private声明,给每个线程分配独立的VAR内存空间:
!$OMP DO PRIVATE(VAR) - 如果VAR本身是需要所有线程共享的变量,那所有读写VAR的位置都要加临界区或者原子操作保护,避免同时读写。
- 如果每个迭代需要独立操作VAR的副本,就在OpenMP DO指令上增加private声明,给每个线程分配独立的VAR内存空间:
内容的提问来源于stack exchange,提问作者ibryant
相关产品推荐
相关产品推荐

