【问题标题】:Ocaml Lwl_mvar.take does not block threadOcaml Lwl_mvar.take 不阻塞线程
【发布时间】:2015-08-23 14:11:08
【问题描述】:

我还在继续写一个简单的游戏服务器。由于here 的一条建议,我实现了 mvar 支持,希望它能在不包含至少 2 个玩家的情况下阻塞线程。但它不会等到我把任何数据放在那里。它总是返回睡眠 Lwt.t。 首先,在这里我们接受连接并提供玩家进入START开始寻找合作伙伴:

let waiting_players = 
    Lwt_mvar.create_empty();;

let rec make_ready player = 
    player >>= fun cli ->
        send_to_client player "Type in START";
        let answer = read_from_client player in 
            answer >>= fun str ->
            match str with
                |"START" -> 
                    let ready_client =  cli in  
                    send_to_client player "Waiting for opponent";
                    Lwt_mvar.put waiting_players ready_client;
                | _ -> 
                    send_to_client player "Unknown command. try again";
                    make_ready player

let handle_income () =
    let in_conection = Lwt_unix.accept sock in 
    in_conection >>= fun (cli, addr) ->
    let player = Lwt.return cli in
    send_to_client player "Welcome to the server. To start game type in START and press Enter";
    make_ready player;;

    val make_ready : Lwt_unix.file_descr Lwt.t -> unit Lwt.t = <fun>
    val handle_income : unit -> unit Lwt.t = <fun>

似乎没问题,但是当我调用 Lwt_mvar.take waiting_players 时,它总是返回一些值,即使之前没有放任何东西并且线程没有被阻塞。这种奇怪的(对我来说)行为在例子中更好看:

# let bucket = Lwt_mvar.create_empty ();;
val bucket : '_a Lwt_mvar.t = <abstr>

# let apple = Lwt_mvar.take bucket;;
val apple : '_a Lwt.t = <abstr>

# Lwt.state apple;;
- : '_a Lwt.state = Sleep

如果“阻塞”意味着返回正是这样的睡眠对象,请告诉。以及如何制作一个循环,以最好的方式只返回“准备好的”对象?使用 Lwt.is_sleeping 是个好主意吗?非常感谢。

【问题讨论】:

    标签: concurrency ocaml lwt


    【解决方案1】:

    您的方法几乎没有问题,代码中也存在一些错误。因此,我将首先强调后者,然后提出并证明另一种方法。

    问题

    问题 1

    看起来您的send_to_client 返回一个unit Lwt.t 类型的值。如果您只是通过使用; 终止您的表达式来忽略它,那么这意味着“不要等到消息发送并继续前进”。通常这不是你想要的。因此,您需要等待unit Lwt.t 线程完成,方法是绑定到它的返回值。

    问题 2

    通常在 Lwt 编程中,函数接受立即类型的值(即未包装到 Lwt.t 中的值)并返回延迟线程(即 'some Lwt.t 类型的值)。当然,这通常是没有人阻止你做一些不同的事情。但尽量坚持“立即输入,延迟输出”的模式。

    问题 3

    使用工具。使用ocp-indent 缩进您的代码,这将有助于提高可读性。此外,看起来您不使用编译器并且正在顶级播放。通常这是一个坏主意,尤其是对于系统编程。使用ocamlbuild 编译和运行您的代码:

    ocamlbuild game.native --
    

    游戏

    OCaml 编程与 Python 或其他弱类型系统语言的编程理念不同。在 OCaml 中,应该从设计类型和签名开始,然后填写实现。当然,这是理想化,在现实生活中会有一个迭代提炼的过程,但大体的做法还是一样的。从类型开始。

    所以,首先,让我们定义一个player 类型。这是微不足道的,但有改进的余地。

    open Lwt
    
    type player = {
      fd : Lwt_unix.file_descr
    }
    

    接下来,让我们使用类型系统来帮助我们,了解我们的游戏初始化问题。你需要让两名玩家准备好并愿意玩你的游戏。这意味着,您有三个连续的状态:

    • Nobody 准备好了
    • One player 准备好了
    • Both (player1, player2) 准备好了

    实际上,因为一旦你到达第三个状态,你就可以开始游戏了,你不需要那个状态,所以我们最终只有两个选择:

    type stage =
      | Nobody
      | One of player
    

    我们可以在这里使用player option 类型,因为它与我们的选择同构。但是让我们更明确地使用我们自己的stage 类型。它将使我们的模型更受约束和更适合。

    下一步是定义客户端和服务器之间的交互协议。我们将使用名称 request 表示从服务器到客户端的消息,并使用 response 表示反向移动的消息。

    type request =
      | Init
      | Wait
      | Unknown_command
      | Bye 
    
    type response =
      | Start
      | Quit
    

    此协议是抽象的,因为它不包含任何具体的表示形式——基于它,您可以构建不同的表示形式,例如 gui 界面或支持不同语言的文本聊天。

    但是让我们模拟一个使用文本命令的最简单的具体实现:

    let response_of_string msg =
      match String.trim (String.uppercase msg) with
      | "START" -> Some Start
      | "QUIT" -> Some Quit
      | _ -> None
    

    在相反的方向(注意:最好在客户端呈现此消息,并在线发送 requestresponse 类型的值,这将使您的流量配置文件保持在较低水平,更重要的是,将允许透明地附加不同的客户端)。

    let string_of_request = function
      | Init -> "Welcome to a game server.
        Please, type 
        - `start' to start game;
        - `quit' to finish session"
      | Wait -> "Please wait for another player to join the game"
      | Unknown_command -> "Don't understand this"
      | Bye -> "Thank you, see you later!"
    

    下一步是为Io 定义接口。该模块负责客户端和服务器之间的交互。请注意我们如何通过抽象隐藏所有细节,例如使用套接字或字符串。

    module Io : sig
      val send : player -> request -> unit Lwt.t
      val recv : player -> response option Lwt.t 
    end = struct
      let send dst msg = return_unit
      let recv dst = return None
    end 
    

    现在,我们可以定义我们的Game 模块。起初它会有两个不同的自动机:

    • init 初始化两个玩家之间的游戏;
    • play 玩一次游戏,我们得到两个受害者。

    让我们在 OCaml 中明确地说出来:

    module Game : sig
    
      (** [play a b] play a game between player [a] and player [b] *) 
      val play : player -> player -> unit Lwt.t
    
      (** [init next_player] waits until two players are ready to play.
          TODO: Describe a grammar that is recognized by this automaton. *)
      val init : (unit -> player Lwt.t) -> (player * player) Lwt.t
    end = struct
      let play a b = return_unit
    
      let init next_player =
        let rec process stage player = 
          Io.send player Init >>= fun () -> 
          Io.recv player >>= function
          | None ->
            Io.send player Unknown_command >>= fun () ->
            process stage player
          | Some Quit ->
            Io.send player Bye >>= fun () -> 
            next_player () >>= process stage
          | Some Start -> match stage with
            | One a -> return (a,player)
            | Nobody -> 
              Io.send player Wait >>= fun () ->
              next_player () >>= process (One player) in
        next_player () >>= process Nobody
    end
    

    现在我们可以写出 main 函数,它将所有内容粘合在一起:

    let main server_sock = 
      let next_player () =
        Lwt_unix.accept server_sock >>=
        fun (fd,_) -> return {fd} in
      Game.init next_player >>= fun (a,b) -> 
      Game.play a b
    

    当您继续使用这种方法时,您稍后可能会注意到,游戏的不同有限状态机定义了不同的语言(即协议)。因此,您最终可能会为每个 FSM 使用特定的协议,而不是使用一个协议,例如,init_protocolplay_protocol 等。但是您可能还会注意到,这些协议有一些交叉点。要处理此问题,您可以使用子类型和多态变体。

    【讨论】:

      【解决方案2】:

      找到了方法。

      let rec form_pairs () = 
      let player1 = Lwt_mvar.take waiting_players in 
          player1 >>= fun descriptor1 ->
      let player2 = Lwt_mvar.take waiting_players in 
          player2 >>= fun descriptor2->
      Lwt_io.printl "Pairs formed";
      Lwt.return (descriptor1, descriptor2);
      form_pairs ();;
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-12-04
        • 2016-01-27
        • 1970-01-01
        • 2014-05-24
        • 1970-01-01
        • 1970-01-01
        • 2023-03-24
        • 2018-02-16
        相关资源
        最近更新 更多