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