Как дождаться окончания параллельных потоков ML до sh перед выходом из программы? - PullRequest
1 голос
/ 15 марта 2020

Я пытаюсь реализовать базовую c программу «стресс-тестирования» в MLton и ее параллельную реализацию 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, который будет использоваться. Изменение количества потоков не имеет никакого значения для этой проблемы. Также я не смог найти никаких других функций, которые превратили бы основную часть go в неблокирующее ожидание.

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

1 Ответ

3 голосов
/ 17 марта 2020

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

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

Вот небольшой пример, использующий различные механизмы для выключения 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 выходит странным образом и вы не вызываете shutdown самостоятельно, есть хороший шанс, что где-то возникло исключение, которое оказалось на всякий случай.

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

  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 Спасибо, что включили ссылку на ваш код, иначе было бы гораздо труднее понять проблему.

...