เขียนโปรแกรม node.js เพื่อศึกษาธรรมชาติ ของ node.js

เขียนโปรแกรม node.js เพื่อศึกษาธรรมชาติ ของ node.js

บทความนี้ เราจะเริ่มเขียนโปรแกรมด้วย node.js กันแล้วนะครับ โดยเนื้อหา จะเน้นไปในทางที่ ศึกษาธรรมชาติ ของการทำงาน และ ประมวลผล ของตัว node.js ซึ่งตรงนี้ จะเป็นจุดที่ยากที่สุดของ node.js เลยก็ว่าได้ โดยเฉพาะคนที่เขียนโปรแกรมประมวลผลแบบภาษา C, C++ , PHP, Python พอมาเจอการทำงานแบบนี้ก็จะ งง เพราะว่ามันทำงานไม่เหมือนกัน

ทั้งนี้ ก่อนที่จะอ่านต่อไป อยากให้มั่นใจก่อน ว่าทุกท่านได้อ่านเรื่อง node.js มาและเข้าใจพื้นฐานแล้ว ถ้ายังไม่เข้าใจ ผมแนะนำให้อ่าน เริ่มต้น node.js คืออะไร ยังไงนะ เจาะลึกกับ node.js แบบเริ่มต้นทำความรู้จัก และท่านก็ต้อง มี node.js ที่ติดตั้งเอาไว้เรียบร้อยแล้วด้วยนะครับ หากยังไม่มี ให้ลองอ่าน  เจาะลึก node.js เริ่มต้น ติดตั้ง และใช้งาน เราจะได้ไม่ งง

มาตรงนี้ ผมถือว่า ทุกท่านได้มี node.js ติดเครื่องแล้ว และสามารถเริ่มต้นเขียนโปรแกรมด้วย node.js ได้แล้วนะครับ เราจะได้ไปกันต่อเลย เพราะเราจะอ่านโค้ด เขียนโค้ด และประมวลผลกันจริงๆ source code ของบทความนี้ สามารถกด download ได้ที่นี่ครับ 

node ก็เป็น การเขียนโปรแกรมภาษาหนึ่ง

อย่าพึ่งตื่นตกใจว่า node.js มันจะยาก ล้ำยุคหรือออกนอกอวกาศ เพราะอย่างที่บอก มันก็คือ ภาษาหนึ่ง ที่คล้ายกับ javascript มากอย่างที่เล่าไปในบทความเก่านั่นเอง เพียงแต่ว่า คนที่ศึกษาจะต้องเปิดใจ รับสิ่งใหม่ และลืมการเขียนภาษา หรือการทำงานแบบทีละบรรทัด อย่างที่เคยเขียนมาออกไปก่อน เพื่อให้เรามองเห็นภาพกว้างขึ้น

node.js ทำงานทีละบรรทัด แต่บรรทัดต่อไป อาจจะทำงานก่อนบรรทัดแรก

ตรงนี้เลยครับ ที่จะทำให้มือใหม่หัดเขียน node.js ผิดพลาด เกิด bug และปวดหัวกันมากๆมาแล้ว ผมจะยกตัวอย่างหนึ่ง สมมุติว่า เราสร้าง function นึงขึ้นมา ต้องไปดึงข้อมูลจากที่อื่นมา โดยใช้เวลาไป 100ms แล้วค่อยแสดงผลมาที่หน้าจอ แล้วเราก็ค่อยแสดงผลต่อว่า ทำงานเสร็จแล้ว เขียนโค้ดได้ดังนี้

setTimeout(function() {
    console.log('This is timeout');
}, 100);
console.log('This is the end');

นี่ครับ โปรแกรมง่ายๆ ที่เขียนตาม logic เริ่มต้นคือสร้าง ตั้ง setTimeout เอาไว้ที่ 100ms เพื่อให้ print คำว่า This is timeout แล้วค่อย print คำว่า This is the end ถ้าเป็นในภาษาอื่น เช่น C, PHP เราก็จะต้องได้ผลลัพท์ว่า This is timeout ขึ้นมาแสดงก่อน โดย This is the end ตามมาทีหลัง แต่สำหรับ node.js ได้ผลแบบนี้ครับ

ตัวอย่าง node.js ที่ This is the end ขึ้นมาก่อน

จะเห็นได้ว่า This is the end ขึ้นมาก่อน This is timeout ตามมาทีหลัง ที่ทำให้หลายคน งง มานักต่อนักแล้ว

ถัดมากับอีกตัวอย่างหนึ่งแล้วกัน ถ้าเราเขียนโปรแกรมแบบนี้

function create_array_number(){
    var temp_array = new Array();
    for(i = 1; i <= 100;i++){
        temp_array[i] = i;
    }
    return temp_array;
}
//return value from function
var array_number = new Array();
array_number = create_array_number();

//print to output
for(id in array_number){
    console.log(array_number[id]);
}
console.log("The end");

คุณคิดว่า ผลลัพท์ที่ได้ จะแสดงผล The end ขึ้นก่อน หรือว่า ตัวเลข 1-100 ขึ้นก่อนครับ... ...... คำตอบก็คือ ตัวเลขขึ้นก่อน ไม่ใช่ The end ขึ้นก่อน ทั้งๆที่ การทำงานวน loop 1-100 จะใช้เวลานานการการ print The end ขึ้นมาหน้าจอทันทีเลยก็ตาม

เหตุที่เป็นเช่นนั้น ก็คือ การทำงาน ของ node.js (ต้องไม่ลืมว่า อยู่ภายใต้การ compile ของ Google Javascript Engine V8) มีการทำงานบางอย่าง เป็นการทำงานแบบ Synchronous และ บางอย่าง ก็เป็นแบบ Asynchronous นั่นเอง

Asynchronous และ Synchronous คืออะไร

ตรงนี้ อธิบายยาวเลยครับ ดังนี้
การทำงานแบบ Synchronous คือการทำงานตามปกติ ที่เราพบได้ในการเขียนโปรแกรมด้วยภาษาทั่วๆไปเช่น C,PHP, Python เป็นต้น การทำงานแบบนี้ มันจะทำงานทีละบรรทัด และถ้า บรรทัดนั้นยังทำงานไม่เสร็จ ก็จะไม่ข้ามไปทำงานบรรทัดต่อไป ซึ่งแบบนี้ มันจะทำให้เกิดสิ่งที่เค้าเรียกว่า "blocking" หรือขัดขวางการทำงาน step ต่อไป หากจุดที่กำลังทำงานนั้นยังทำงานไม่เสร็จ โดยเค้าถือว่า การทำงานแบบนี้ มันให้ประสิทธิภาพที่ต่ำ เพราะว่าประสิทธิภาพของ computer ในปัจจุบัน สามารถทำงานได้มากกว่านั้น โดยไม่ต้องมานั่งรองานเสร็จ แล้วค่อยไปทำงานอื่นต่อ โดยปกติแล้ว เราจะพบการทำงานแบบ blocking เกิดขึ้นได้กับการเชื่อมต่อ IO ต่างๆ เช่นต้องอ่าน database, ต้องอ่านค่าจาก socket เป็นต้น 

การทำงานแบบ Asynchronous คือการทำงานแบบ ไม่อิงเรื่องความสำคัญของลำดับ เมื่อพบการทำงานแบบ Asynchronous ก็จะเริ่มต้นทำงานจุดนั้น และข้ามไปทำงานสิ่งต่อไปทันทีด้วยเหมือนกัน ทำให้คล้ายๆกับสิ่งที่เค้าเรียกว่า "parallel execution" หรือการทำงานแบบคู่ขนาน หรือถ้ามองภาพง่ายๆหน่อย ก็เหมือนกับ computer ที่มีหน่วยประมวลผลเป็น CPU มากกว่า 1 core เมื่อ core แรกทำงานอยู่ยังไม่เสร็จ ก็แบ่งงานบางส่วนไปทำงานที่ core ที่สอง นั่นเอง ทีนี้ ปัญหา มันก็เลยเกิดขึ้นตรงที่ว่า งานชิ้นแรกที่ทำ มันดันทำนาน แต่ว่า งานชิ้นที่สอง ที่เราส่งไปให้ core ที่สองทำงาน ดันเสร็จก่อน แล้วผลลัพท์ของ program มันจะออกมาหน้าตาอย่างไร?

ตรงนี้ เลยครับ ที่เรียกว่า เป็นประโยชน์ และ โทษ ของ node.js ในเวลาเดียวกัน จากตัวอย่างโปรแกรมที่เขียนและแสดงให้ดู จะเห็นว่า แบบแรก ที่มี setTimeout มันทำงานสลับกันก่อนหลัง แต่ว่า ตัวอย่างที่สอง กลับทำงานเรียงกันลงมา เพราะว่า setTimeout ในตัวอย่างแรก เป็น Asynchronous แต่ว่า for loop ในตัวอย่างสอง เป็นการทำงานแบบ Synchronous แม้ว่าจะใช้เวลาทำงานนาน แต่โปรแกรมที่รออยู่ ก็จะยังรอข้อมูลจนกว่าจะเสร็จนั่นเอง

ลองมาดูตัวอย่างอีกสักอัน เพื่อให้เห็นภาพนะครับ ผมมี function 2 function โดย function แรก หน่วงเวลา 200ms และ function ที่สอง หน่วงเวลา 100 ms แต่ผมเรียกใช้ function แรกก่อน แล้วค่อยเรียกใช้ function ที่สอง แบบนี้

function event01(){
    setTimeout(function(){
        console.log('Function 01');
    },200);
}
function event02(){
    setTimeout(function(){
        console.log('Function 02');
    },100);
}
event01();
event02();

คุณคิดว่า หน้าจอจะเห็น Function 01 ก่อน หรือ Function 02 ก่อนครับ ... ...... (คำตอบคือ Function 02 แสดงผลก่อน Function 01 แม้ว่าจะเรียก event01() ก่อนแล้วก็ตาม)

ประโยชน์ของ Asynchronous ที่มากกว่า Synchronous

ลองนึกภาพตามนะครับ สมมุติว่า คุณมีระบบหนึ่ง ทำงานตาม flow ดังนี้

  1. อ่านข้อมูลต้นฉบับมา (ใช้เวลาทำงาน 2 นาที)
  2. เอาข้อมูลที่ได้ มาประมวลผล ได้ผลลัพท์ประมาณ 1 ล้านรายการ (ใช้เวลาทำงาน 2 นาที)
  3. เอาผลลัพท์ 1 ล้านรายการเก็บลง database (ใช้เวลาทำงาน 10 นาที)
  4. วนไปที่ ข้อ 1 ด้วยงานต้นฉบับ ชิ้นต่อไป

ถ้าผมบอกว่า ระบบนี้มีงาน ต้นฉบับ 100 ชิ้น คุณคิดว่า ระบบนี้ ใช้เวลานานเท่าไรครับ? ถ้าเรามองผ่านๆ เราก็คิดง่ายๆว่า งาน 1 ชิ้น  ใช้เวลา 14 นาที ดังนั้น 100 ชิ้น ก็ใช้เวลา 100*14 = 1400 นาที หรือ 23 ชั่วโมงครึ่ง จึงจะเสร็จ แต่ถ้าคุณทำงานด้วย Asynchronous คุณจะใช้เวลาเพียง 17 ชั่วโมงเท่านั้น (เวลาทั้งหมด คือเวลาที่ได้จากการเก็บข้อมูลลง database 10นาที * 100 ชุด หรือ 100*10 = 1000 นาทีหรือ 16.6 ชั่วโมง แต่เราปัดเศษเล็กน้อย เลยกลายเป็น 17ชั่วโมง)  เหตุผลเพราะว่า การทำงานแบบ Asynchronous ไม่ต้องรอการเก็บข้อมูลลง database ให้เสร็จก่อน แล้วค่อยไปเริ่มรอบใหม่ แต่มันข้ามไปเริ่มต้นรอบใหม่เลยทันที โดยที่กระบวนการเก็บข้อมูล ก็ยังคงเก็บต่อไปเรื่อยๆอย่างไม่หยุดยั้งนั่นเอง ถ้าอธิบายการเขียนแบบ Asynchronous มันก็จะทำงาน ข้อที่ 1 แล้วไปทำงานข้อที่ 2 แล้วส่งข้อมูลไปให้กระบวนการข้อที่ 3 แล้วไปข้อ 4 ก็คือไปเริ่มต้นข้อ 1 ใหม่ทันทีทั้งๆที่ข้อ ที่ 3 ยังเก็บลง database ไม่เสร็จ แต่มันได้รับข้อมูลแล้ว และมันก็ยังทยอยเขียนลง database ไปเรื่อยๆ

ทีนี้ เราก็ต้องเขียน program เพื่อควบคุมการไหล และการพักข้อมูลเองเพื่อให้ทำงานต่อเนื่องแบบนี้ได้ครับ (มันคือประโยชน์ที่มาพร้อมโทษชัดๆ)

node.js ก็เขียนแบบ PHP style (synchronous) ได้

ครับ ไม่ต้องตกใจ เพราะว่า คนที่จะเขียน node.js นั้น เราสามารถเขียนให้มันทำงานแบบ หนึ่งไปสอง สองไปสาม สามไปสี่ ก็ได้เหมือนกัน ทั้งๆที่มี setTimeout ด้วย โดยเราจะอาศัยสิ่งที่เค้าเรียกว่า "Event driven" นั่นเอง ก็คือ การกระตุ้นการทำงานส่วนต่อไป ด้วย event ที่เกิดขึ้น 

ผมลองเอา ตัวอย่างเดิมมาปรับนิดเดียว เพื่อให้ผลลัพท์เป็นไปอย่างที่เราต้องการนะครับ ก็คือ เราต้องการให้ระบบแสดง Function 01 ก่อน Function 02 เราก็ตกแต่งโค้ดลงไป ดังนี้ครับ

function event01(callback){
    setTimeout(function(){
        console.log('Function 01');
        callback();
    },200);
}
function event02(){
    setTimeout(function(){
        console.log('Function 02');
    },100);
}
event01(function(){
    event02();
});

จะเห็นว่า ผมเพิ่ม callback เข้าไปที่ event01 และเวลาเรียก ก็เรียก event02 หลังจากที่ได้ callback จาก event01 มาก่อนแล้วเท่านั้น ซึ่งการทำงานแบบนี้ จะทำให้เกิด blocking ทันที เพราะมันจะต้องรอผลลัพท์ที่เกิดจาก event01 เสียก่อน ถึงจะเริ่มทำ event02 ต่อได้ไม่ว่า event01 จะทำงานนานแค่ไหน event02 ก็จะรอ.... (เอิ่ม นี่ไม่ใช่นิยายรักนางเอกอกหัก หรือรอผัวจนตัวตายนะ)

เป็นอย่างไรบ้างครับ เห็นหรือยัง ว่า node.js มันทำงานแบบไหน อย่างไร และมันต้องเปลี่ยน แนวความคิดเราไปบ้างหรือเปล่า มาถึงตรงนี้ อาจจะมีคำถามว่า แล้วเราเอา node.js มาเขียนแบบ synchronous ทั้งโปรแกรมเลยได้หรือเปล่า คำตอบก็คือ ได้ครับ ไม่ได้มีใครห้าม คำตอบก็ง่ายๆแค่นี่แหล่ะครับ เพียงแต่ว่า ถ้าคุณต้องการจะเจ๋ง คุณก็ไม่ควรเขียนแบบ synchronous นะเออ (แต่ความเจ๋งมันก็กินไม่ได้นะ 5555) แต่อย่างไรต้องไม่ลืมว่า Asynchronous มันคือจุดเด่นของ node.js ที่ทำให้มันทำงานได้เร็วมากกกกกก ถ้าคุณต้องการเพิ่มความเร็วของการทำงานให้เร็วสุดๆ ก็ต้องเขียนแบบ asynchronous นี่แหล่ะ มันจะได้ไม่ต้องรอกันไปมา

คิดว่า ทั้งหมดนี้ คงจะทำให้พอเริ่มเขียนกันได้แล้วนะครับ ที่ผมพูดเรื่องพื้นฐาน หลายๆรอบ เพราะว่า ผมเน้นเรื่องการสร้างฐานรากให้แข็งแรงเสมอ ตอนนี้ เรากำลังเตรียมตัวจะสร้างตึก เราก็ต้องตอกเสาเข็มให้แน่นก่อน ไม่อย่างนั้น รีบสร้างไปเลยก็ล้มไม่เป็นท่ากลางทาง เพราะเราไม่เข้าใจมันอย่างชัดเจนนั่นเอง อย่างไรก็ดี ผมแนะนำเป็นอย่างยิ่งว่า ให้ลองเขียนด้วยตัวเอง ยิ่งเขียนเยอะ ยิ่งทำตัวอย่างเยอะ ก็จะยิ่งเจอปัญหาเยอะ และยิ่งได้แก้ไขเยอะ นั่นก็คือประสบการณ์ก็ยิ่งเยอะ ทำให้เรายิ่งเก่ง

Create: Modify : 2013-10-11 09:29:01 Read : 11513 URL :