• require "socket"
    require "nio"
    
    def run(host, port)
      server = TCPServer.new(host, port)
      puts "Listening to #{host}:#{port}"
    
      selector = NIO::Selector.new
      monitor = selector.register(server, :r)
      monitor.value = ->(_) do
        socket = server.accept
        puts "New client #{socket}"
        client_monitor = selector.register(socket, :r)
        client_monitor.value = ->(mon) do
          s = mon.io
          puts "Got: #{s.read_nonblock(4096)}"
        end
      end
    
      loop do
        selector.select { |monitor| monitor.value.call(monitor) }
      end
    end
    
    run('localhost', 1234)
    
  • Puma 是多个地方用到IO.select,reactor 从IO.select替换到NIO::Selector#select https://github.com/puma/puma/commit/e83a4954e4c0214d18beb594ddf598fafdf058d7#diff-8b7db4d5cc4b8f6dc8feb7030baa2478

    IO.select 跟 NIO::Selector 的区别官方 wiki 就有 https://tonyarcieri.com/a-gentle-introduction-to-nio4r

  • 是 Thread B 里的selector.select do |monitor| block Thread A 里的monitor = selector.register(socket, :r)

    • NIO:: Selector#selectIO.select的替代品,不应该同时用.
    • TCPServer 先 register 到 nio selector, 然后再调用NIO:: Selector#select。server monitor 的 callback 里接收客户端 io 对象,然后把 io 对象再注册到 nio selector 里。
  • 我那句提反转二叉树了?贴个老梗显得自己懂得多?一个极端特例当论据是不是抬扛?一直用反问句回复别人是不是自我感觉很良好?

  • 工程师也会 “相轻” 的,跟不同背景的同事吹逼,聊到算法,设计模式以及 “众所周知” 的 pattern 一定不能露怯,否则将会被暗暗鄙视(其实大部分人都是半桶水,所以都会很默契的不会聊的很深入)

    面试中算法有点像义务教育里那些感觉没用的课程,一是能快速筛掉学习能力差的人,二是算法确实对编码有影响。

  • 三楼的需要在方法外定义 param

    不需要,写里面也一样。

  • 还不是很理解,为啥 broadcast 的时候要用 thread pool,因为 io 已经 异步了

    ActionCable 的实现是在内存里维护一个订阅列表的 hash,key 是 channel,value 是一个订阅者回调 lambda 的 array,

    回调 lambda : https://github.com/rails/rails/blob/master/actioncable/lib/action_cable/channel/streams.rb#L146-L154

    def worker_pool_stream_handler(broadcasting, user_handler, coder: nil)
      handler = stream_handler(broadcasting, user_handler, coder: coder)
    
      -> message do
        connection.worker_pool.async_invoke handler, :call, message, connection: connection
      end
    end
    

    broadcast 的时候: https://github.com/rails/rails/blob/master/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb#L36-L45

    def broadcast(channel, message)
      list = @sync.synchronize do
        return if !@subscribers.key?(channel)
        @subscribers[channel].dup
      end
    
      list.each do |subscriber|
        invoke_callback(subscriber, message)
      end
    end
    

    invoke_callback最终是上面那段 lambda

    -> message do
      connection.worker_pool.async_invoke handler, :call, message, connection: connection
    end
    

    worker_pool是一个 thread pool (Concurrent::ThreadPoolExecutor),async_invoke是往 thread pool 里塞任务,任务异步执行,所以遍历 subscribers 时是不会阻塞的,而消息何时向全部 client 发完取决于 thread pool 消化任务是的速度了。而且整个 ActionCable 是公用一个 thread pool 的。

  • 可以看一下 https://hashrocket.com/blog/posts/websocket-shootout 粗略比较了各种编程语言/框架 websocket 的指标。

    结论

    When it comes to websockets, Clojure, Elixir, and Go are all choices that balance great performance with ease of development. NodeJS is in a lower performance tier, but is still good enough for many uses. Rails can only be recommended when part of an existing Rails application with few concurrent clients. C++ offers the best performance, but it is hard to recommend due to the complexity and difficulty of development.

    需要指出的是做测试的时候 Rails 6 刚出,puma 还没有用上 nio4r,所以 Rails 支持的连接数看起来弱爆了,但是不代表现在 Rails 的真实水平。

    ActionCable 比较大的问题是 broadcast 是基于线程池的,线程池大小默认是 5(可以通过 config.action_cable.worker_pool_size 调整),换句话说一个 puma worker 同一时间只能同时向 5 个 websocket client 发消息 😂

  • 装饰器可以对原方法进行修饰,返回一个修改之后的方法,在打印日志,切面编程中运用广泛,你说 ruby 开放那你可以试一下在不改变函数名的情况下对原函数进行修改

    module LogDecortor
      def log_method *method_names
        mod = Module.new do
          method_names.each do |method_name|
            define_method(method_name) do |*args, &blk|
              puts "Method #{method_name}: start"
              ret = super(*args, &blk)
              puts "Method #{method_name}: end"
              ret
            end
          end
        end
        self.prepend(mod)
      end
    end
    
    class Foo
      extend LogDecortor
    
      log_method :bar
      def bar
        return 1
      end
    end
    
    p Foo.new.bar
    

    另外 Ruby 即使有不改变方法代码本身就改变方法行为的能力,但是实践中还是比较克制,像加日志、打点一类的需求有另外的方法,就不展开说了。

    第二 javascript 只是我用来举例,可能你没懂我在说什么

    只是没懂你的举的例子有什么太大的好处。

    第三纯函数式语言是写不出复杂的系统的,或者说是不容易写出复杂的系统

    纯函数式编程不容易写出复杂系统是因为程序员要求高一点。

    所以这里的函数式编程就是一个概念,允许方法有副作用

    "函数式编程就是一个概念"不是我的原话吗?我本意是现在市面上绝大部分面向对象语言都可以写 “函数式编程” 的代码,只是前端生态很推崇,别的领域还没有一要使用 “函数式编程” 的场景。

    Ruby 2.7 曾经有过管道符 https://bugs.ruby-lang.org/issues/15799 ,你对函数式编程那么了解肯定知道管道符对函数式编程的意义,虽然种种原因取消了,但是可以反映出 Ruby 是拥抱函数式编程的,你可以期待一下 ❤

  • 比如增加 python 的 AOP 特性 (装饰器)

    Ruby 足够开放,不需要语言层面装饰器。

    def 定义的方法名也是一个变量,可以覆盖(就是重复定义)类似 javascript,

    javascript 还没有好到需要别的语言模仿吧 😅 覆盖方法不就是 override 么……

    然后支持函数式编程,高阶函数(支持传入函数,传出函数),现在来看还不能算真正支持函数式。

    高阶函数核心在与 “穿作用域”,而在 “穿作用域” 领域,Ruby 比大多数语言强很多。函数式编程也仅仅是个概念:要不你就用纯函数式编程语言,写不出违反函数式编程 pattern 的代码;要不你就用其他语言假装在函数式编程:有些领域大家都在假装,有些领域大家还没开始装,并不是说不能装,只是还没遇到需要装的场景。

    学新语言还是要有空杯的心态。