Sys.set_signal在主线程上中断input_line,但不在子线程中

时间:2018-04-25 17:25:36

标签: multithreading signals ocaml

在OCaml中编写可中断读者线程的正确方法是什么?具体来说,以下单线程程序可以正常工作(即 Ctrl-C Ctrl-C 立即中断它):

exception SigInt

let _ =
  Sys.set_signal Sys.sigint (Sys.Signal_handle (fun _ -> raise SigInt));
  try output_string stdout (input_line stdin);
  with SigInt -> print_endline "SINGLE_SIGINT"

另一方面,以下程序不能用C-c C-c中断:

let _ =
  Sys.set_signal Sys.sigint (Sys.Signal_handle (fun _ -> raise SigInt));
  let go () =
    try output_string stdout (input_line stdin);
    with SigInt -> print_endline "CHILD_SIGINT" in
  try Thread.join (Thread.create go ());
  with SigInt -> print_endline "PARENT_SIGINT"

在OCaml中实现可中断读者线程的跨平台方法是什么?。也就是说,我需要对上面的多线程程序进行哪些更改才能使其可中断?

我已经探索了多个假设来理解为什么上面的多线程示例不起作用,但没有一个对我有意义:

  • 也许input_line不可中断?但上面的单线程示例不起作用。

  • 也许Thread.join阻止了整个过程的信号?但在这种情况下,以下示例也不会中断:

    let _ =
      Sys.set_signal Sys.sigint (Sys.Signal_handle (fun _ -> raise SigInt));
      let rec alloc acc =
        alloc (1::acc) in
      let go () =
        try alloc []
        with SigInt -> print_endline "CHILD_SIGINT" in
      try Thread.join (Thread.create go ());
      with SigInt -> print_endline "PARENT_SIGINT"
    

    ...但它是:按 Ctrl-C Ctrl-C 立即退出。

  • 也许信号被传递到主线程,主线程在Thread.join不间断地等待。如果是这样,按 Ctrl-C Ctrl-C 然后 Enter 将打印"PARENT_SIGINT"。但它没有:它打印"CHILD_SIGINT",这意味着信号被路由到子线程并延迟到input_line完成。但令人惊讶的是,这可行(并打印CHILD_SIGINT

    let multithreaded_sigmask () =
      Sys.set_signal Sys.sigint (Sys.Signal_handle (fun _ -> raise SigInt));
      let go () =
        try 
          ignore (Thread.sigmask Unix.SIG_SETMASK []);
          output_string stdout (input_line stdin);
        with SigInt -> print_endline "CHILD_SIGINT" in
      try
        ignore (Thread.sigmask Unix.SIG_SETMASK [Sys.sigint]);
        Thread.join (Thread.create go ());
      with SigInt -> print_endline "PARENT_SIGINT"
    

    ...但Windows上没有sigmask。

1 个答案:

答案 0 :(得分:1)

两件事正在共同努力,使行为难以理解。第一个是OS信号传递到过程。第二个是OCaml运行时如何将它们传递给应用程序代码。

查看OCaml源代码,其OS信号处理程序通过全局变量简单地记录信号被引发的事实。然后,该标志由OCaml运行时的其他部分轮询,有时可以安全地传递信号。因此,Thread.sigmask控制OS信号可以传递到OCaml运行时的哪个线程。它无法控制您的应用的投放。

挂起信号由caml_process_pending_signals()传递,由caml_enter_blocking_section()和caml_leave_blocking_section()调用。这里没有线程掩码或亲和力......处理全局待处理信号列表的第一个线程就这样做了。

input_line函数轮询操作系统以获取新输入,每次输入都会进入和离开阻塞部分,因此它会频繁轮询信号。

Thread.join进入阻塞部分,然后无限期地阻塞,直到线程完成,然后离开阻塞部分。因此,在等待时,它不会轮询未决信号。

在您的第一个可中断的示例中,如果您实际键入并按Enter键会发生什么? input_line调用是否实际累积输入并返回它?它可能不会...... Thread.join可能拥有阻塞部分并阻止整个进程的输入和信号传递。