Web กับ process thread และอื่น ๆ
Tags: web, coding, process, thread
Published: 2021-06-04 07:42:11

---
title: Web กับ process thread และอื่น ๆ
published: true
description: 
tags: web,coding,process,thread
//cover_image: https://direct_url_to_image.jpg
---



ผมไม่ค่อยแม่นเรื่องนี้เท่าไหร่ถ้ามีอะไรมั่วไปก็ขออภัยด้วยครับ สามารถพูดคุยกันเพิ่มเติมได้ที่ [veer66@mastodon.in.th](https://mastodon.in.th/@veer66) 

เขียนโปรแกรมบน web server ยุค 90 ส่วนมากก็ผ่าน [CGI](https://en.wikipedia.org/wiki/Common_Gateway_Interface) เอาครับ สมัยนั้นใช้ C, Perl, [Bash](https://www.gnu.org/software/bash/) เขียนกัน URL มันก็จะประมาณ http://localhost/cgi-bin/do_sth.pl งี้เลย web server มันก็จะเรียก do_sth.pl ให้อีกที ถ้าเป็นบน GNU/Linux ก็คือต้อง fork/spawn process ขึ้นมาใหม่เลย

ปลาย ๆ ยุค 90 ค่าย Java ก็มีตัวเลือกมาให้อีกแบบคือใช้ [Servlet](https://en.wikipedia.org/wiki/Java_servlet) ซึ่งตัวนี้ใช้หลาย ๆ thread เอาแทนทีจะมา spawn process ใหม่ ซึ่งผมเดาว่าเวลา implement เขาก็น่าจะมี [thread pool](https://en.wikipedia.org/wiki/Thread_pool) ตั้งแต่ตอนนั้น พูดอีกอย่างคือเอา thread สร้างมาแล้วมาใช้ซ้ำ

PHP ก็มาปลาย ๆ ยุค 90 เหมือนกัน แต่ผมเริ่มใช้ทีแรกก็เป็น mod_php ที่ฝังมาใน Apache Httpd แล้ว เวลา[ใช้จริง ๆ มันก็ fork process ใหม่อยู่ดี](https://serverfault.com/questions/231628/apache-mpms-worker-vs-prefork/231660#231660) แต่ว่าก็มี prefork ไว้ก่อนได้ ถ้าผมเข้าใจไม่ผิดแต่ละ process ก็จะมี PHP runtime พร้อม extension ต่าง ๆ อยู่ใน memory ด้วย อันนี้น่าจะเป็นเหตุผลหลัก ๆ ที่หลาย ๆ ท่านบ่นกันว่ามันกิน RAM

ยุค 90 MS ก็มี [ASP](https://en.wikipedia.org/wiki/Active_Server_Pages) ออกมา แต่ผมไม่รู้เลยว่าทำงานอย่างไร ก็เลยข้ามไปแล้วกัน

หลังจากที่ไม่ค่อยจะมีอะไรน่าสนใจออกมาสักพัก กลาง ๆ ยุค 2000 ก็มี Ruby on Rails ออกมา ผลที่ตามมาคือมี web server ที่เอามาใช้รัน Rails ได้หลายตัว ซึ่งผมไม่รู้เลยว่าตอนนั้นเขาทำแบบไหนกัน 😅; ตอนนั้นผมใช้ [TurboGears](http://www.turbogears.org/) ก่อน 1.0 (บน Python) ส่วน  Web Server ใช้ [CherryPy](https://github.com/cherrypy/cherrypy) ผมลองดู version เก่า ๆ ก็ใช้หลาย thread แล้วมี threadpool ด้วย

ราว ๆ ค.ศ. 2008 ผมก็เริ่มได้ยินพี่ป็อกพูดถึง [Erlang](http://www.erlang.org/) ซึ่ง Erlang นี่ผมก็เพิ่งมาทราบทีหลังว่ามาในชุมสายโทรศัพท์ตั้งแต่ยุค 80 แล้ว ใน Erlang ใช้คำว่า process แทน thread แต่ว่าเอาจริง ๆ มันไม่ใช่ process ของ Linux หรือ [BSD](https://en.wikipedia.org/wiki/Berkeley_Software_Distribution) แบบของ Apache Httpd มันคล้าย green thread ของ Java ยุคแรก ๆ มากกว่า process ของ Linux แต่ว่าเรียกว่า process เพราะว่าไม่ได้ share memory แบบที่ thread ส่วนมากทำ Erlang process ใช้ memory อยู่ในวิสัยที่จะ spawn มาเป็นแสนตัวได้ สลับ ๆ ไปสลับมาแบบ preemptive ต่อให้ไม่มี I/O หรือไปสั่งให้สลับ scheduler มันก็จะสลับให้ แต่ถึงมันจะสร้าง process ได้เยอะแยะแต่ก็เหมือนว่าเขาจะไม่ได้สร้างกันทุกครั้งที่ request เข้ามา แต่ว่าไปใช้ [gen_server](http://erlang.org/doc/man/gen_server.html) ซึ่งถ้าผมเข้าใจไม่ผิดมันทำให้ reuse process เดิมได้

ยุคหลัง 2010 คนเริ่มเปลี่ยนมาใช้ [node.js](https://nodejs.org) ซึ่งใช้ประโยชน์จาก kernel polling เช่น [epoll](https://en.wikipedia.org/wiki/Epoll) และ [kqueue](https://en.wikipedia.org/wiki/Kqueue) เอามาทำ event loop พอมีงานที่ต้องรอ I/O ก็เอาออกไปรอก่อนแล้วสลับไปรับงานอื่นได้ แต่ว่า JavaScript แต่เดิมมันแตก thread ไม่ได้ node.js ก็แตก thread ไม่ได้เหมือนกัน เวลาจะใช้ประโยชน์จาก CPU หลาย ๆ core ก็เลยต้องแตก process ของ Linux หรือ OS อื่น ๆ เอาแล้วก็มี runtime ไปอยู่ทุก process เลย 

พอเริ่มใช้ node.js ไม่เท่าไหร่ ก็มี [Golang](https://golang.org/) จาก Google ออกมา ซึ่งจะมองว่า Golang ก็คือแบบที่ใช้ multithread เลยก็ได้ พวกตัวที่เร็ว ๆ เช่น fasthttp ก็มี thread pool คล้าย ๆ กับ web server ก่อนหน้านี้ แต่ว่า thread ของ Golang หรือที่เรียกว่า Goroutine มันใช้ memory น้อยไม่ใช่ thread ของ OS ตรง ๆ ทำให้ spawn ออกมาเยอะ ๆ ได้ fasthttp อาจจะเป็นไปได้ว่า spawn ออกมาหลักแสนตัวได้เลย แล้วก็ปล่อย scheduler ของ Go runtime ไปจัดลง thread ของ OS เอาเอง จะเห็นว่าคล้าย ๆ Erlang อยู่บ้าง แต่ว่า Goroutine อย่างน้อยก็ช่วงนี้ยังไม่ได้สลับแบบ preemptive 

วงการ Rails เองก็มีการปรับตัว [Puma](http://puma.io/) ที่เป็น default web server ทุกวันนี้ก็เป็นแบบ multithread โดยที่ค่า default คือให้เปิดมาได้เลย 5 thread แต่ใน production จะใช้มากกว่านี้ก็ได้ เทียบกับตัวเก่า ๆ Puma รับ concurrency ได้มากขึ้นใช้ RAM น้อยลง เท่าที่ผมเห็นก็คือมีทั้ง worker thread ที่มาประมวลผล โดยที่ก็จะวน ๆ ใช้จาก thread pool ไม่ใช่ว่าสร้างใหม่บ่อย ๆ มีอีก thread นึงคือไป accept request แล้วอ่านข้อมูลมารอใส่ queue ชื่อ todo ไว้ นอกจากนั้น parser ก็เขียนด้วย C ทำให้เร็วขึ้นไปอีก Puma นี่มันเลือกแตก process ผสมกับ Thread ได้ด้วย ผมเดาว่าเพราะ MRI-Ruby มันติด global lock ถ้าเจองานใช้ CPU มาก ๆ อาจจะใช้ได้ไม่ทั่วถึง แต่ถ้าไปใช้ [JRuby](https://www.jruby.org/) แทนก็เป็นอันตกไป

[Tokio](https://tokio.rs/) เขียนบน Rust ที่มี mio ที่ไปหุ้ม epoll และ kqueue ได้เหมือน node.js ถ้าเทียบกันน่าจะอยู่ในระดับเดียวกับ libuv แต่ว่า Rust ไม่มีปัญหาเรื่องแตก thread อยู่แล้ว Tokio ก็เลยทำได้ทั้งแตก thread (multithread) แล้วก็มี event loop แบบ node.js ด้วย ใน[แต่ละ thread ก็มี event loop](https://github.com/tokio-rs/tokio/blob/76198f63d73019311cc09d870b8397e46d8c0f2d/src/runtime/threadpool/builder.rs)  เองเลย 

อีกวิธีหนึ่งของค่าย Rust คือทำ coroutine คล้าย ๆ Goroutine ขึ้นมาเลยใน project ชื่อ [May](https://github.com/Xudong-Huang/may) แต่ทั้ง May และ Tokio นี่มันเป็นระดับล่างมาก ๆ ผมเข้าใจว่าคนทำเว็บส่วนมากคงไม่มาเรียกตรง ๆ น่าจะใช้ผ่าน [Rocket](https://rocket.rs/) หรือ [Actix](https://actix.rs/) แทน

Rust นี่แตก thread กันเป็นว่าเล่นส่วนหนึ่งอาจจะเพราะว่ามันไม่มี [GC](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) ส่วนหนึ่งที่ยากเวลาแตก thread คือต้องทำ GC ให้ใช้งานสอดคล้องกับ scheduler ด้วย ผมเห็นสองภาษาที่โมเรื่องนี้อยู่คือ [Ocaml](https://ocaml.org/) และ [Crystal](https://crystal-lang.org) ถ้าเสร็จแล้วก็น่าจะจัดอยู่กลุ่มเดียวกับ Golang ได้ แต่อาจจะเขียนง่ายกว่า