ลอง event loop และ non-blocking IO ใน crystal-lang
Tags: concurrency, crystal, crystallang
Published: 2021-06-04 07:44:12

---
title: ลอง event loop และ non-blocking IO ใน crystal-lang
published: true
description: 
tags: concurrency,crystal,crystallang
//cover_image: https://direct_url_to_image.jpg
---



หลายครั้งผมแค่อยากได้ Ruby ที่มันเร็ว ไม่ต้องแนว ไม่ต้อง functional ก็ได้ ไม่ต้อง minimal ก็ได้

[Crystal](http://crystal-lang.org/) เป็นภาษาโปรแกรมแบบ static type ความเร็วแบบ thread เดียว ผมเคยเอามาลองตัดคำ ถ้าไม่ optimize อะไรเลย ความเร็วไล่ ๆ กับ Go

ประเด็น hot ช่วงนี้คือถ้าใช้แบบ concurrent จะเป็นอย่างไร Crystal ไม่ได้ใช้ multithreading แน่ ๆ เพราะมันยังทำไม่เสร็จ 🤣 แต่ว่าตอนนี้มีพวก [event loop](https://github.com/crystal-lang/crystal/blob/c679102854b702e0e7edde4a759335d6aab021e5/src/crystal/event_loop.cr) และ [fiber](https://github.com/crystal-lang/crystal/blob/17c64da978c851d4b7d08a4ccc11b9a374b1e849/src/fiber.cr) แน่ ๆ ดูคร่าว ๆ แล้วน่าจะทำงานคล้าย ๆ Node.js ได้โดยไม่ต้องเขียน async-await 

code ต่อไปนี้ผมเขียนมาลองว่าจะเป็นแบบที่คิดจริงหรือไม่

```ruby
require "socket"

def load(id, chan)
  puts "ID=#{id}; BEFORE"
  (id..11).each do 
    socket = TCPSocket.new("www.ku.ac.th", 80)
    puts "ID=#{id}; Blocking?=#{socket.blocking}"
    socket.close
  end
  puts "ID=#{id}; AFTER"
  chan.send nil
end

def main
  chan = Channel(Nil).new
  (1..10).each{|i| spawn(load(i,chan))}
  # Wait
  (1..10).each{chan.receive}
end

main 
```

code มันก็จะคล้าย ๆ Ruby หน่อย ผมเขียนให้ ถ้า id ยิ่งน้อยยิ่งรันนาน เพื่อที่จะดูว่างานที่ spawn มาทีหลังมันมันแซงมาเสร็จก่อนได้เปล่า    อีกอย่างก็คือ print มาให้ดูด้สนว่าใช้ non-blocking IO

```
ID=1; BEFORE
ID=2; BEFORE
ID=3; BEFORE
ID=4; BEFORE
ID=5; BEFORE
ID=6; BEFORE
ID=7; BEFORE
ID=8; BEFORE
ID=9; BEFORE
ID=10; BEFORE
ID=1; Blocking?=false
ID=2; Blocking?=false
ID=3; Blocking?=false
ID=4; Blocking?=false
ID=5; Blocking?=false
ID=6; Blocking?=false
ID=7; Blocking?=false
ID=8; Blocking?=false
ID=9; Blocking?=false
ID=10; Blocking?=false
ID=1; Blocking?=false
ID=2; Blocking?=false
ID=3; Blocking?=false
ID=4; Blocking?=false
ID=5; Blocking?=false
ID=7; Blocking?=false
ID=6; Blocking?=false
ID=8; Blocking?=false
ID=9; Blocking?=false
ID=2; Blocking?=false
ID=1; Blocking?=false
ID=10; Blocking?=false
ID=10; AFTER
ID=4; Blocking?=false
ID=5; Blocking?=false
ID=3; Blocking?=false
ID=7; Blocking?=false
ID=6; Blocking?=false
ID=8; Blocking?=false
ID=9; Blocking?=false
ID=9; AFTER
ID=2; Blocking?=false
ID=1; Blocking?=false
ID=4; Blocking?=false
ID=5; Blocking?=false
ID=3; Blocking?=false
ID=7; Blocking?=false
ID=6; Blocking?=false
ID=8; Blocking?=false
ID=8; AFTER
ID=2; Blocking?=false
ID=1; Blocking?=false
ID=4; Blocking?=false
ID=5; Blocking?=false
ID=3; Blocking?=false
ID=7; Blocking?=false
ID=7; AFTER
ID=1; Blocking?=false
ID=2; Blocking?=false
ID=6; Blocking?=false
ID=4; Blocking?=false
ID=5; Blocking?=false
ID=3; Blocking?=false
ID=1; Blocking?=false
ID=2; Blocking?=false
ID=6; Blocking?=false
ID=6; AFTER
ID=4; Blocking?=false
ID=5; Blocking?=false
ID=5; AFTER
ID=3; Blocking?=false
ID=1; Blocking?=false
ID=2; Blocking?=false
ID=4; Blocking?=false
ID=4; AFTER
ID=3; Blocking?=false
ID=1; Blocking?=false
ID=2; Blocking?=false
ID=3; Blocking?=false
ID=3; AFTER
ID=1; Blocking?=false
ID=2; Blocking?=false
ID=2; AFTER
ID=1; Blocking?=false
ID=1; AFTER
```

อันนี้ก็จะเห็นได้เลยว่า ID=1 ที่เริ่มรันก่อนมันเสร็จทีหลังเลย สรุปก็คือใน Crystal ใช้ non-blocking IO กับ event loop ได้โดยที่ programmer ไม่ต้องทำอะไรพิเศษเลย ก็แค่เขียน ๆ ไปธรรมดา ในกรณีที่เป็น web server ส่วน spawn และ channel ก็ไม่ต้องเขียนเองด้วย

Go, Erlang, Elixir ทำอะไรได้มากกว่านี้ แต่ Crystal มันเป็นภาษาที่แทบไม่ต้องปรับตัวเลย สำหรับคนที่เคยเขียน Ruby งาน CPU bound ก็น่าจะเร็วกว่า Erlang แลพ Elixir ด้วย