惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
Martin Fowler
Martin Fowler
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Stack Overflow Blog
Stack Overflow Blog
C
Check Point Blog
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog
O
OpenAI News
云风的 BLOG
云风的 BLOG
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
L
LangChain Blog
V
Vulnerabilities – Threatpost
P
Privacy & Cybersecurity Law Blog
Microsoft Azure Blog
Microsoft Azure Blog
H
Help Net Security
博客园 - 三生石上(FineUI控件)
C
Cyber Attacks, Cyber Crime and Cyber Security
N
News | PayPal Newsroom
The Register - Security
The Register - Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Threat Research - Cisco Blogs
C
Cisco Blogs
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Vercel News
Vercel News
宝玉的分享
宝玉的分享
T
Tailwind CSS Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Attack and Defense Labs
Attack and Defense Labs
人人都是产品经理
人人都是产品经理
S
Security Affairs
爱范儿
爱范儿
Engineering at Meta
Engineering at Meta
T
Tor Project blog
大猫的无限游戏
大猫的无限游戏
L
Lohrmann on Cybersecurity
aimingoo的专栏
aimingoo的专栏
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 【当耐特】
V
V2EX
V
Visual Studio Blog
The GitHub Blog
The GitHub Blog
雷峰网
雷峰网
WordPress大学
WordPress大学
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
L
LINUX DO - 最新话题
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Schneier on Security
Schneier on Security
小众软件
小众软件
PCI Perspectives
PCI Perspectives

Ruby

一個用 DIDComm 實現服務器間通信的 Rails demo - V2EX RubyConf China 2024 参会报告 - Day 1 - V2EX 有对 Ruby 感兴趣的吗? - V2EX MRuby Devkit 一个简单的脚手架,帮助你像 Go 一样把 Ruby 编译成可执行二进制文件 - V2EX 使用 Ruby-build 在 MacOS 上 编译 Portable Ruby - V2EX [翻译] Async Ruby(异步 Ruby) - V2EX [翻译] Ruby Fiber Scheduler - V2EX Ruby 3.2.0 发布 - V2EX Ruby on Mac - V2EX 管窥蠡测从思考游戏到实现 2048 - V2EX 用 Ruby 讲从创业到 996 公司的故事(戏说 master-worker 模式) - V2EX Sinatra 源码分析 (一):set 系统工作原理 - V2EX 《Ruby 设计模式》笔记 - V2EX Ruby 标准库有趣部分摘要 - V2EX 并行并发进程线程协程 GIL 概念简明解释笔记(Ruby 举例) - V2EX Sinatra 的 app 模板,提供一些胶水代码支持类似 Rails 的体验 - V2EX Ruby 的方法查找再往前一步 - V2EX 有在深圳至简天成的吗? 求加微信 - V2EX Ruby 3.0.0 Released - V2EX 新建了个 ruby 中文讨论群 - V2EX Language Hopping, Ruby 为什么不火,一点瞎想 - V2EX ruby,一代优秀语言就这样陨落了吗? - V2EX ruby 的 "a" 和 :a 能转换吗 - V2EX 求大佬帮安装一个 mastodon,有偿! - V2EX notepad++ ssh 一些个小坑 - V2EX 有什么好的方法/IDE 阅读 Gem 库的源码? - V2EX 问一个 ruby 的问题 - V2EX 用 discourse 搭了个论坛 求测试 - V2EX 找小伙伴一起开发和完善一个 Ruby 增量代码覆盖率统计工具 - V2EX 都在说抑郁,我们学一下 ruby 吧,一门让你快乐的语言 - V2EX ruby2.6 的 jit 大家感觉怎么样? - V2EX 在 one-liner 文本处理方面, ruby 比 perl 要逊色吗? - V2EX 使用 ruby 写了一个周报系统 - V2EX ruby 的 devkit 安装 - V2EX 总听人说 ruby 代码写起来很爽,有多爽?怎么个爽法? - V2EX RubyGems 注册不了的? - V2EX Ruby 破环方法为什么比非破坏方法慢一些? - V2EX Ruby Conference 2017 AU - V2EX ruby 这么优雅的语言,怎么没有像 python 这样流行开来? - V2EX Ruby 中 Singleton 方法的另一种理解方式 - V2EX 需要学习 ruby,想问现在 ruby 的主流版本是多少? - V2EX Ruby 的内存占用极限? Ruby 写了一个命令行火车票查看器 - V2EX 有谁用过 Ruby 写的比特币交易平台 Peatio,admin 面板进不去,求教啊啊啊啊啊啊啊 - V2EX 和隔壁的 python 比起来,这节点好冷清 阿里云 OSS Ruby SDK 开发手记 - V2EX 淘宝 RubyGems 镜像有段时间没同步了 - V2EX 为什么 Ruby 给人一种「只有 Rails」的印象?
用 100 行 Ruby 代码模拟 JavaScript 的 Eventloop - V2EX
Mark24 · 2022-08-11 · via Ruby

前言

大家好,我是 Mark24

背景

我们都知道 JavaScript 是单线程的。

今天看到一个有趣的帖子 www.v2ex.com/t/871848,主要是争论 JavaScript 的优缺点。我看到这个评论觉得很有意思:

@qrobot:

....省略....


多线程下会消耗以下资源

1. 切换页表全局目录
2. 切换内核态堆栈
3. 切换硬件上下文(进程恢复前,必须装入寄存器的数据统称为硬件上下文)
ip(instruction pointer):指向当前执行指令的下一条指令
bp(base pointer): 用于存放执行中的函数对应的栈帧的栈底地址
sp(stack poinger): 用于存放执行中的函数对应的栈帧的栈顶地址
cr3:页目录基址寄存器,保存页目录表的物理地址
......

4. 刷新 TLB
5. 系统调度器的代码执行

....省略.....

这位同学列举了多线程切换的时候发生了什么。 这样给了一种很直观的感受,就是多线程切换的时候发生了很多事情,实际上会比单线程(只需要切换函数上下文)要消耗点更多的资源。

实际上凡是交互的软件,最终都是 单线程模型 + 事件驱动辅助。

从熟悉的浏览器、游戏、应用程序……都是如此。

也有多线程实现的。这里Multithreaded toolkits: A failed dream? (2004) 有很多讨论。

实际上单线程模型是最后的胜出者。

JavaScript 内部单线程处理任务,主要是有一个 EventLoop 单线程的循环实现。

我们可以通过 JavaScript 的表现,反推实现一下 EventLoop 。

JavaScript 的行为

我们知道 setTimeout 在 JavaScript 中用来推迟任务。实际上自从 Promise 出现之后,渐渐有两个概念出现在大家的视野里。

  • Macrotask(宏任务)
  • Microtask (微任务)

setTimeout 属于宏任务,而 promise 的 then 回调属于微任务。

还有一个就是 JavaScript 在第一次同步执行代码的时候,是宏任务。

EventLoop 的表现是,除了第一次执行结束之后,如果有更高优先级的 微任务总是先执行微任务,然后再执行宏任务。

setTimeout 是一个定时器,很特别的是他在会在计时器线程工作,运行时间之后,回调函数会被插入到 宏任务中执行。计时器线程其实不是 JavaScript 虚拟的一部分,他是浏览器的部分。

Ruby 模拟

JavaScript 是单线程的。Ruby 是支持多线程的。我们可以用 Ruby 模拟一个 单线程的核心,和单独的计时器线程,这都是很轻松的事情。

其实我们听到了这个行为 —— 花 1 分钟大概能想到,EventLoop 的工作模型

  • 首先他是一个主循环,这样才能所谓的单线程
  • 其次,既然有两种任务,应该是两种队列
  • 再者,如果第一次同步代码是宏任务,多半可以代码任务也丢到队列里

我们可以用数组当做 队列。但是 由于存在时间线程,还得用 Thread#Queue 有保障一点。

大概的模型可以画出来,想这个样:

Eventloop Model

( start)
   |
  init (e.g create TimerThread )
   |
  sync task (e.g read & run code) 
   |                                        
   |
 ------------------>
|                  |                                    -------------
|               macro_task  ---  add timer task -->    | TimerThread |
|   (Eventloop)    |       <--  insertjob result ---    -------------
|                  |
|               micro_task
|                  |
|                  |
 <-----------------   
   |
   |
  (end)

然后我们大概用 100 行不到就可以实现如下:

需要说明的是:

  1. settimeout 不要用每一个新的线程来模拟,因为一旦多线程,涉及到抢占式回调,其实返回的时间不确定。你的结果是不稳定的。 我们需要单独实现一个计时器线程。

  2. 我们通过行为封装,把两边函数写法对照,这样可以复制

运行看结果

result_example

具体实现

# https://github.com/Mark24Code/rb_simulate_eventloop
require 'thread'

class EventLoop
  
  attr_accessor :macro_queue, :micro_queue
  def initialize
    @running = true
    
    @macro_queue = Queue.new
    @micro_queue = Queue.new

    @time_thr_task_queue = Queue.new

    @timer = Timer.new(@time_thr_task_queue, @macro_queue)

    # 计时线程,是一个同步队列
    # 会把定时任务结果塞回宏队列
    @timer_thx = Thread.new do
      @timer.run
    end
  end


  def before_loop_sync_tasks
    # do sth setting
    @first_task.call
  end

  def task(&block)
    # 这里放置第一次同步任务
    # 
    # 外部书写的代码,模拟读取 js
    # 提供内部的 api
    @first_task = -> () { instance_eval(&block) }
  end

  def after_loop
    puts "[after_loop] eventloop is quit :D"
  end

  def macro_queue_works
    while !@macro_queue.empty?
      job = @macro_queue.shift
      job.call
    end
  end

  def micro_queue_works
    while !@micro_queue.empty?
      job = @micro_queue.shift
      job.call
    end
  end

  def start
    begin
      before_loop_sync_tasks

      while @running

        macro_queue_works

        micro_queue_works

        # avoid CPU 100%
        sleep 0.1
      end
    ensure
      after_loop
    end
  end

  # dsl public api
  # inner api
  def macro_task(&block)
    @macro_queue.push(block)
  end

  def micro_task(&block)
    @micro_queue.push(block)
  end

  def settimeout(time, &block)
    # 模拟定时器线程
    if time == 0
      time = 0.1
    end

    # 方案 1: 用独立分散的线程模拟存在问题
    # 抢占的返回顺序不是固定的
    # t = Thread.new do
    #   sleep time
    #   @micro_queue.push(block)
    # end
    ## !!! 这里一定不能阻塞,一旦阻塞就不是单线程模型
    ## 有外循环控制不会结束
    # t.join

    # 方案 2: 时间线程也需要单独模拟
    # 建立一个时间任务
    @time_thr_task_queue.push({
      sleep_time: Time.now.to_i + time,
      job: -> () { @micro_queue.push(block) }
    })

  end
end

class Timer
  def initialize(task_queue, macro_queue)
    @task_queue = task_queue
    @macro_queue = macro_queue
  end
  def run
    while (task = @task_queue.shift)
      sleep_time = task[:sleep_time]
      if sleep_time >= Time.now.to_i
        @macro_queue.push(task[:job])
      else
        @task_queue.push(task)
      end
    end
  end
end

总结

选择单线程的原因是因为

  • 结果运行的更快
  • 无上下文负担
  • 任务队列清晰而又简单
  • 非 IO 密级任务,可以跑满 CPU

Nginx 、Redis 内部也实现了单线程模型,来应对大量的请求,提高并发。

现在我们大概知道了,浏览器、应用、app 、图形界面、游戏……

他们的背后大概是什么样子。 破除神秘感 +1 :D