Multithreading (ตอนที่ 00 -- ต้องรู้อะไรก่อนดี)การเขียนโปรแกรมให้ทำงานแบบ Multithreading ถือได้ว่าเป็นการเขียนโปรแกรมในระดับสูง ภายใต้กระบวนการคิดที่ชวนให้สับสนได้ง่ายๆ ถึงแม้ว่าโดยทั่วไปแล้วจะมี API ให้เรียกใช้ได้แบบง่ายและรวดเร็วก็ตาม
แน่นอนว่าคนที่หัดเขียนโปรแกรมแบบ Multithreading บน Windows จะเจอกับคำสั่ง/ฟังก์ชัน/API ที่ชื่อว่า CreateThread() ฟังก์ชั่นนี้ต้องการพารามิเตอร์หลายตัว ผมขอเดาว่าหลายคนรู้ว่าแต่ละตัวคืออะไร ถึงแม้ว่าการรู้เชิงลึกจะไม่มีความจำเป็นในการใช้งาน API แต่ผมมั่นใจว่าเขียนโปรแกรมไปสักพัก แค่ API อย่างเดียวอาจจะไม่พอที่จะทำให้ได้มาซึ่งโปรแกมที่ต้องการ
บทความนี้ผมไม่ได้มากล่าวถึงรายละเอียดของพารามิเตอร์ต่างๆ หรือการใช้งานฟังก์ชัน CreateThread() แต่จะพูดถึงสิ่งที่ผมคิดว่าสำคัญมากที่สุดสำหรับการเริ่มต้นเขียนโปรแกรมแบบ Multithreading (และการเขียนโปรแกรมระดับสูงทั่วไป)
ก่อนอื่นมาดูรูปบบของฟังก์ชันกันก่อน:HANDLE WINAPI CreateThread(
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,
__in_opt LPVOID lpParameter,
__in DWORD dwCreationFlags,
__out_opt LPDWORD lpThreadId
);
พารามิเตอร์ทั้ง 6 ตัวมีอยู่ 2 ตัวที่ชวนให้ปวดหัว นั่นคือตัวที่ 1 และตัวที่ 3 สำหรับตัวที่ 1 ผมยังไม่พูดถึง เพราะการเขียนโปรแกรมแบบ Multithreading ทั่วๆไปไม่ได้ใช้ และถูกกำหนดใช้เป็น NULL หรือ (void *)0 ตัวที่จะพูดถึงคือตัวที่ 3 นั่นคือ __in LPTHREAD_START_ROUTINE lpStartAddress
__in เป็นตัวบอกว่าเป็นพารามิเตอร์ชนิด input (ส่งค่าผ่านเข้าไปยังฟังก์ชั่น ไม่มีการเปลี่ยนค่ากับตัวแปรนี้)
LPTHREAD_START_ROUTINE เป็น Pointer ที่ชี้ไปยังฟังก์ชัน ถูกกำหนดรูปแบบขึ้นมาโดยไมโครซอฟต์มีรูปแบบดังนี้typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
LPVOID lpThreadParameter
);
(จริงๆแล้ว WINAPI คือ __stdcall จะไม่พูดถึงในที่นี้)การเขียนโปรแกรมแบบ Multithreading บน Windows รูปแบบของ Callback Function (ส่วนของโปรแกรมที่จะถูกเรียกโดย Kernel) จะต้องเป็นไปตามที่ไมโครซอฟต์ (Windows) กำหนดเท่านั้น และ Callback Function ก็เป็นพารามิเตอร์ตัวนึงที่ต้องส่งไปให้ฟังก์ชัน CreateThread() นั่นคือเหตุผลว่าทำไม ผมเลือกที่จะพูดถึงสิ่งนี้เป็นอันดับแรกหัวใจของ Callback Function คือ Pointer หรือเรียกว่า "Pointer to Function" หรือ "Function Pointer" อย่างที่ทราบกันดี Pointer เป็นตัวแปรที่เก็บค่า Address ของสิ่งที่เราสนใจ ซึ่งในที่นี่คือ Function นั่นเอง
เพื่อให้เข้าถึง Function Pointer ผมจะขอยกตัวอย่างง่ายๆ ดังนี้typedef int(*Type_FuncPtr)(int, int); /* ประกาศ Function Pointer */
int MyAdd(int a, int b); /* Prototype function */
/* Function บวก */
int MyAdd(int a, int b){
return(a + b);
}
/* Function ที่มีการรับค่าเป็น Address ของ Functon */
void MainFunc(Type_FuncPtr FuncAddr){
int a, b, added;
a = 11;
b = 22;
added = (*FuncAddr)(a,b); /* ใช้ Function Pointer เรียกฟังก์ชัน โดยอ้างผ่าน Address ที่ส่งผ่านมาทาง FuncAddr */
printf("a + b = %d\n", added);
}
ตอนนี้ในฟังชัน MainFunc ผมได้ชี้ให้เห็นสองอย่างคือ - จะรับ Address ของ Function เข้ามาใน Function (MainFunc) ได้อย่างไร?
- จะเรียก Function โดยอ้างผ่านทาง Function Pointer (FuncAddr) ได้อย่างไร?
แล้วจะเรียก MainFunc() ได้อย่างไร? อย่างที่ทราบว่า MainFunc() ต้องการพทรามิเตอร์ที่เป็น Address ของ Function ในที่นี้คือ MyAdd() ตอนนี้มาดูกันว่าเราจะเรียก MainFunc() และส่งผ่าน Address ของ MyAdd() ไปได้อย่างไรint main(void){
MainFunc(&MyAdd); /* เรียก MainFunc() และ ส่ง Address ของ MyAdd() ไป */
/* สามารถตัด & ออกไปได้หากต้องการ */
return(0);
}
อาจจะเกิดคำถามว่าทำไมไม่เรียก MyAdd() จาก main() หรือ MainFunc() ไปเลย?
แน่นอนครับทำแบบนั้นก็ย่อมได้ แต่นั่นไม่ใช่จุดประสงค์ที่เรากำลังพูดถึงกันอยู่...อย่างที่ผมได้กล่าวนำไปแล้วตอนต้นว่า Callback Function จะถูกเรียกโดย Kernel หรือ OS (เรียกแบบตัวอย่างก็ได้ แต่ไม่มีประโยชน์อะไร) แล้ว Kernel จะเรียก Callback Function นี้ตอนไหนล่ะ??
ยกตัวอย่างเลยดีกว่าครับ:- ถ้าเขียนโปรแกรมเกี่ยวกับ Image Processing, Callback Function อย่างน้อย 1 ตัวที่ต้องมี คือเมื่อ Hardware รับข้อมูลจากกล้องมาครบ 1 เฟรม หรือ 1 ภาพ
- ถ้าเขียนโปรแกรมเกี่ยวกับ Speech/Signal Processing, Callback Function อย่างน้อย 1 ตัวที่ต้องมี คือเมื่อ Hardware รับข้อมูลจากไมโครโฟนมาครบ 1 เฟรม (เช่น 512 จุด)
- ถ้าเขียนโปรแกรมเกี่ยวกับ การสือสารข้อมูล Serial port, USB, Callback Function อย่างน้อย 1 ตัวที่ต้องมี คือเมื่อ Hardware รับข้อมูลเข้ามาครบ 1 ไบต์ หรือ 1 เฟรม (เช่น 8 ไบต์)
สำหรับคนที่เขียนโปรแกรมอยู่กับไมโครคอนโทรลเลอร์ สิ่งที่ผมพูดมา (Callback Function) คือ Interrupt Function นั่นแหละครับ แต่ในการเขียนโปรแกรมบนไมโครคอนโทรลเลอร์ทั่วไป เราจะไม่ใช้ Kernel หรือ OS การเรียกฟังก์ชัน Interrupt จึงกระทำโดยตัว CPU โดยตรง แต่การเขียนโปรแกรมบน Windows หรือ OS เราในฐานะผู้เขียนโปรแกรมจะไม่ได้รับการอนุญาติในลงลึกไปถึงการ Interrupt ของ CPU การ Interrupt ของ CPU หรือการกระทำระดับ Hardware จะรับรู้และควบด้วย Kernel+Driver และเรากระทำหรือร้องขอการกระทำต่างๆผ่าน Kernel ด้วย API Function ที่ติดมากับตัว Kernel หรือ OS ได้อย่างง่ายดายในตอนต่อไปผมจะพูดถึงการสร้าง Thread ง่ายๆ และจะสอดแทรกสิ่งที่เกี่ยวข้องลงไปเรื่อยๆตามที่คิดออก
ขอบคุณที่อ่านมาจนจบครับ :P