Как дождаться завершения параллельных потоков ML перед выходом из программы?

Я пытаюсь реализовать базовую программу "стресс-тестирования" в MLton и ее реализацию Concurrent ML, в частности тест Монте-Карло Пи, описанный здесь. Хотя я думаю, что у меня есть большая часть того, что мне нужно, у меня проблема в том, что моя программа всегда завершается до того, как потоки CML завершают свою работу. Я знаю, что они что-то делают, так как иногда я вижу, как они выводят текст на консоль, который, по моему указанию, должен быть напечатан, но, похоже, существует состояние гонки между их запуском и запуском и завершением программы в целом.

Код, в котором я запускаю CML, следующий:

local
    val iterations : int = 10
    val num_threads : int = 1
    val still_going : bool ref = ref true
in
   val _ = (RunCML.doit ((experiment iterations num_threads still_going), NONE);
            (* while !still_going do (); (* Spin-wait for the CML stuff to finish.  This doesn't work... *) *)
            print "All done!\n")
end

содержание experiment функции:

fun experiment (iterations : int) (num_threads : int) (still_going : bool ref) () : unit = let
    val iters_per_thread : int = iterations div num_threads
    val return_ivars = Vector.tabulate (num_threads, (fn _ => SyncVar.iVar()))
    val _ = Vector.map (fn return_ivar => CML.spawn (montecarlopi iters_per_thread return_ivar)) return_ivars
    val return_val = Vector.foldl (fn (elem, acc) => acc + (SyncVar.iGet elem)) 0.0 return_ivars
in
    (TextIO.print ("Result is: " ^ (Real.toString return_val) ^ "\n");
            still_going := false)
end

и, наконец, montecarlopi функция:

fun montecarlopi (iterations : int) (return_ivar : real SyncVar.ivar) () = let
    val _ = MLton.Random.srand (valOf (MLton.Random.useed ()))
    fun helper accumulator 0 = accumulator
      | helper accumulator iteration = let
          val x : real = wordToBoundedReal (MLton.Random.rand ())
          val y : real = wordToBoundedReal (MLton.Random.rand ())
          val in_target = (x * x) + (y * y)
          val next_iter = iteration - 1
          val _ = TextIO.print ("next_iter is: " ^ (Int.toString next_iter) ^ ", in_target is: " ^ (Real.toString in_target)  ^ ",x is: " ^ (Real.toString x) ^ ",y is: " ^ (Real.toString y) ^ "\n")
      in
          if in_target < 1.0 then
              helper (accumulator + 1) next_iter
          else
              helper accumulator next_iter
      end
in
    SyncVar.iPut (return_ivar, (4.0 * ((real (helper 0 iterations)) / (real iterations))))
end

(Полную (небольшую) программу и сопроводительный файл.mlb можно посмотреть здесь). Я достаточно уверен, что биты внутриRunCML.doitвызов функции делают то, что они должны делать, что наводит меня на мысль, что проблема, вероятно, связана с самой внешней частью программы.

Как вы можете видеть, я пытался вращать ожидание, используя ячейку ref для логического значения, чтобы определить, когда остановиться, но это, похоже, не сработало. И не пытается крутиться и ждать, используяRunCML.isRunning- хотя обе эти идеи поначалу кажутся ужасными, в любом случае. Конечно, я не могу использовать что-то вроде CML-канала или syncvar, поскольку они должны находиться внутриRunCML.doitсегмент, который будет использоваться. Изменение количества потоков не влияет на эту проблему. Я также не смог найти никаких других функций, которые заставили бы основную часть перейти в неблокирующее ожидание.

Как мне заставить внешнюю часть моей программы ждать, пока большая ее часть внутри RunCML.doitвызов функции, завершается? Или я делаю что-то не так внутри этой части, что является причиной проблемы?

1 ответ

Решение

Если мы посмотрим на функцию RunCML.doit, Имеет тип OS.Process.status который может быть success или failure, с которого ваш звонок doitвозвращается неудача. Есть функция CMLshutdown: OS.Process.status -> 'a.

Это может быть объяснением того, почему это не удается, за исключением того, что вы не вызываете выключение, и части ваших результатов вывода никогда не печатаются.

Вот небольшой пример, демонстрирующий различные механизмы выключения CML, где CML, кажется, делает что-то вроде "изящного" внутри. Выявление возникших исключений и превращение их в провал.

structure Main = struct
  open CML
  structure RunCML = RunCML;
  exception ohno

  fun raises() = raise ohno
  fun succeed() = RunCML.shutdown(OS.Process.success)
  fun fail() = RunCML.shutdown(OS.Process.failure)
  fun graceful f () =
    let val () = f() handle _ => RunCML.shutdown(OS.Process.failure);
     in RunCML.shutdown(OS.Process.success)
    end

  fun print_status status =
      if OS.Process.isSuccess status
         then TextIO.print("success\n")
         else TextIO.print("failure\n")

  fun main() = let
    val _ = TextIO.print(banner ^ "\n");
    val _ = print_status(RunCML.doit(succeed, NONE))
    val _ = print_status(RunCML.doit(fail, NONE))
    val _ = print_status(RunCML.doit(raises, NONE))
    val _ = print_status(RunCML.doit(graceful(raises), NONE))
    val _ = print_status(RunCML.doit(graceful(succeed), NONE))
    in OS.Process.success end
end

Итак, если CML завершается странным образом, и вы не вызываете выключение самостоятельно, это хороший шанс, что где-то возникло исключение, которое, как оказалось, имело место.

Одним из способов избежать этой тихой обработки исключений в будущем могло бы быть добавление чего-то вроде:

  fun noisy f () =
    let val () = f()
    handle e => 
       let val () = TextIO.print ("Exception: " ^ (exnName e)
         ^ " Message: " ^ (exnMessage e) ^ "\n")
        in RunCML.shutdown(OS.Process.failure) end
     in RunCML.shutdown(OS.Process.success)
    end

затем звоню RunCML.doit(noisy(f), NONE)

PS Спасибо, что включили ссылку на свой код, иначе было бы намного сложнее разобраться в проблеме.

Другие вопросы по тегам