多线程并发修改队列时如何校验队列状态以替代OMP_BARRIER同步屏障
问题根因与解决方案
原有两种方案的问题
第一种队列轮询方案卡住的原因
- 队列入队操作
queue_enqueue_data未做临界区保护:即使queue_full加了临界区,1号线程修改queue%size的操作如果不在同一个临界区内,size的更新对其他线程不可见,其他线程永远读不到队列已满的状态 - 无名称的
!$OMP CRITICAL是全局互斥的:轮询线程每1微秒就抢一次临界区锁,导致1号线程拿不到锁,无法完成队列填充,最终死锁
第二种共享flag方案不可行的原因
- 你编写的
wait子程序入参full是intent(in)属性,进入子程序时就已经复制了传入时的.false.值,后续1号线程修改共享的full变量,子程序内部永远拿不到更新后的值,会无限等待 - 即使修改参数传递方式,没有加内存同步指令的前提下,编译器会优化寄存器缓存,不会重新读取内存中的
full值,依然会卡住
正确实现方案
方案1:队列轮询实现(推荐,逻辑更安全)
首先修改所有队列操作,加同名临界区保证原子性:
! 入队函数修改示例 subroutine queue_enqueue_data(queue, data) type(QUEUE_STRUCT), asynchronous, intent(inout) :: queue ! 替换为你自己的任务类型 type(TASK_TYPE), intent(in) :: data !$OMP CRITICAL(QUEUE_LOCK) ! 原有入队逻辑,修改queue%size的操作放在临界区内 queue%size = queue%size + 1 ! ... 其他入队操作 !$OMP END CRITICAL(QUEUE_LOCK) end subroutine queue_enqueue_data
重写wait_thread函数:
subroutine wait_thread(master_queue) type(QUEUE_STRUCT), pointer, asynchronous :: master_queue do !$OMP CRITICAL(QUEUE_LOCK) if (master_queue%size == master_queue%capacity) then !$OMP END CRITICAL(QUEUE_LOCK) exit end if !$OMP END CRITICAL(QUEUE_LOCK) ! 调整sleep时长为100微秒,避免频繁抢锁占用CPU call system_sleep(100) ! 强制刷新缓存,读取队列最新状态 !$OMP FLUSH(master_queue) end do end subroutine wait_thread
方案2:共享flag实现
需要给共享flag加可见性保证:
! 声明full时加volatile属性(Fortran 2003及以上支持) logical, volatile :: full full = .false. if (num_thread==1) then do i_task=first_task,last_task tasklist_GRAD(i_task)%state=STATE_READY call queue_enqueue_data(master_queue,tasklist_GRAD(i_task)) end do full=.true. ! 强制把full的更新刷回内存 !$OMP FLUSH(full) end if if (num_thread .ne. 1) then do call system_sleep(100) ! 强制从内存读取full的最新值 !$OMP FLUSH(full) if (full) exit end do end if
注意事项
- 两种自定义同步方案的性能都弱于原生
!$OMP BARRIER,原生屏障是操作系统级的线程同步,不会产生无用的轮询CPU占用 - 如果场景允许使用内置屏障,优先用原生实现;如果确实不能用,上述两种方案都可以正常工作,调整sleep时长可以平衡响应速度和CPU占用
内容的提问来源于stack exchange,提问作者hakim
相关产品推荐
相关产品推荐

