ธันวาคม 11, 2017, 08:55:13 am *
ยินดีต้อนรับคุณ, บุคคลทั่วไป กรุณา เข้าสู่ระบบ หรือ ลงทะเบียน
ส่งอีเมล์ยืนยันการใช้งาน?

เข้าสู่ระบบด้วยชื่อผู้ใช้ รหัสผ่าน และระยะเวลาในเซสชั่น
   หน้าแรก   ช่วยเหลือ เข้าสู่ระบบ สมัครสมาชิก  
หน้า: [1]   ลงล่าง
  พิมพ์  
ผู้เขียน หัวข้อ: Multithreading (ตอนที่ 00 -- ต้องรู้อะไรก่อนดี)  (อ่าน 13076 ครั้ง)
0 สมาชิก และ 1 บุคคลทั่วไป กำลังดูหัวข้อนี้
ShadowMan
Administrator
Hero Member
*****
ออฟไลน์ ออฟไลน์

เพศ: ชาย
กระทู้: 8267


ShadowWares


| |
« เมื่อ: กันยายน 26, 2010, 07:19:20 pm »

Multithreading (ตอนที่ 00 -- ต้องรู้อะไรก่อนดี)

การเขียนโปรแกรมให้ทำงานแบบ Multithreading ถือได้ว่าเป็นการเขียนโปรแกรมในระดับสูง ภายใต้กระบวนการคิดที่ชวนให้สับสนได้ง่ายๆ ถึงแม้ว่าโดยทั่วไปแล้วจะมี API ให้เรียกใช้ได้แบบง่ายและรวดเร็วก็ตาม
แน่นอนว่าคนที่หัดเขียนโปรแกรมแบบ Multithreading บน Windows จะเจอกับคำสั่ง/ฟังก์ชัน/API ที่ชื่อว่า CreateThread() ฟังก์ชั่นนี้ต้องการพารามิเตอร์หลายตัว ผมขอเดาว่าหลายคนรู้ว่าแต่ละตัวคืออะไร ถึงแม้ว่าการรู้เชิงลึกจะไม่มีความจำเป็นในการใช้งาน API แต่ผมมั่นใจว่าเขียนโปรแกรมไปสักพัก แค่ API อย่างเดียวอาจจะไม่พอที่จะทำให้ได้มาซึ่งโปรแกมที่ต้องการ
บทความนี้ผมไม่ได้มากล่าวถึงรายละเอียดของพารามิเตอร์ต่างๆ หรือการใช้งานฟังก์ชัน CreateThread() แต่จะพูดถึงสิ่งที่ผมคิดว่าสำคัญมากที่สุดสำหรับการเริ่มต้นเขียนโปรแกรมแบบ Multithreading (และการเขียนโปรแกรมระดับสูงทั่วไป)

ก่อนอื่นมาดูรูปบบของฟังก์ชันกันก่อน:


Code: (c)
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 นั่นคือ
Code: (c)
  __in       LPTHREAD_START_ROUTINE lpStartAddress


__in เป็นตัวบอกว่าเป็นพารามิเตอร์ชนิด input (ส่งค่าผ่านเข้าไปยังฟังก์ชั่น ไม่มีการเปลี่ยนค่ากับตัวแปรนี้)
LPTHREAD_START_ROUTINE เป็น Pointer ที่ชี้ไปยังฟังก์ชัน ถูกกำหนดรูปแบบขึ้นมาโดยไมโครซอฟต์มีรูปแบบดังนี้

Code: (c)
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 ผมจะขอยกตัวอย่างง่ายๆ ดังนี้

Code: (c)
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() ไปได้อย่างไร

Code: (c)
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


บันทึกการเข้า

By SDW: Do No Wrong Is Do Nothing
          If you want to increase your success rate, double your failure rate
วิสิทธิ์ แผ้วกระโทก
Global Moderator
Sr. Member
*****
ออฟไลน์ ออฟไลน์

กระทู้: 307



| |
« ตอบ #1 เมื่อ: ธันวาคม 18, 2010, 10:07:32 am »

เห็นภาพเลยครับ พอพี่ยกตัวอย่างของ Interrupt ใน mcu กับ callback function ใน windows

มันทำให้ function ของโปรแกรมทำงานกันอย่างอิสระได้เลย มันสิ่งที่ท้าทายมากๆเลยครับ

บันทึกการเข้า

ShadowMan
Administrator
Hero Member
*****
ออฟไลน์ ออฟไลน์

เพศ: ชาย
กระทู้: 8267


ShadowWares


| |
« ตอบ #2 เมื่อ: ธันวาคม 18, 2010, 02:22:24 pm »

ลองเล่นดูครับ รับรองว่าไม่ผิดหวัง
บันทึกการเข้า

By SDW: Do No Wrong Is Do Nothing
          If you want to increase your success rate, double your failure rate
TheONE
Newbie
*
ออฟไลน์ ออฟไลน์

กระทู้: 14


| |
« ตอบ #3 เมื่อ: ธันวาคม 18, 2010, 05:31:26 pm »

ยากๆ แบบนี้ผมต้องฝากไว้ก่อน ตอนนี้กำลังงมโข่งพื้นฐานอยู่เลยครับ  ???
บันทึกการเข้า
supachai_ps
Newbie
*
ออฟไลน์ ออฟไลน์

กระทู้: 12


| |
« ตอบ #4 เมื่อ: กรกฎาคม 20, 2013, 08:27:36 pm »

เคยศึกษาแบบผ่านๆ นานมากแล้วครับ ได้อ่านเรื่อง call-back ก็เลยกระตุ้นทำให้อยากกลับมาศึกษาอีกครั้ง ตอนนี้ เริ่มต้นที่ p-thread ก่อน จำได้สมัยก่อนตอนที่จะใช้ใน Win ตอนนั้นเรียก Mux หรือยังไงเนี่ยแหละครับ กำลังค่อยๆ แกะเส้นทางไป

แต่สำหรับผมแล้วปัญหาอย่างนึงที่ทำให้ผมยังไม่ค่อยเข้าใจก็คือ เรื่องของ การจัดการของระบบปฏิบัติการ ซึ่งส่วนตัวคิดว่ามีผลพอสมควร
ในภาพที่แนบด้านล่าง เป็นการทดสอบ Run โปรแกรมที่ทำการ Create P-Thread โดยที่ฟังก์ชันสำหรับ Thread จะแสดงข้อความ Hello Word และเลขที่ Thread จากการส่งค่าผ่าน Pointer Arg  ทดสอบรันบนระบบปฏิบัติการ OS X 2 รุ่น คือ Mountain Lion กับ Snow Leopard ผลปรากฎดังภาพ

ภาพ Black Ground สีดำเป็น OS X - Snow Leopard ทดสอบซ้ำ 3 ครั้ง ได้ผลเหมือนกัน คือ จะแสดงข้อความที่เขียนไว้ใน main ซึ่งเข้า for loop สำหรับสร้าง Thread ก่อนจนครบ แล้วจึงค่อยแสดงผลของ Function ที่เขียนสำหรับ Create P-Thread  ในขณะที่ Mountain Lion จะสร้างและรันไปค่อนข้างจะขนานมากกว่า






โดยส่วนตัวคิดว่ากลไกในระดับของระบบปฏิบัติการก็มีผลเช่นกัน แต่ยังไม่เข้าใจว่ากลไกนั้นทำงานอย่างไร ผมสงสัยว่าคงต้องกลับไปรื้อ อ่านเรื่องระบบปฏิบัติการให้เข้าใจมากกว่านี้ ถึงจะทำให้ความเข้าใจเรื่อง Multi task/thread เกิดความเข้าใจมากยิ่งขึ้น
บันทึกการเข้า
ShadowMan
Administrator
Hero Member
*****
ออฟไลน์ ออฟไลน์

เพศ: ชาย
กระทู้: 8267


ShadowWares


| |
« ตอบ #5 เมื่อ: กรกฎาคม 20, 2013, 08:32:39 pm »

เป็นกำลังใจให้ครับ และจะตั้งตารอติดตามผลงาน
บันทึกการเข้า

By SDW: Do No Wrong Is Do Nothing
          If you want to increase your success rate, double your failure rate
หน้า: [1]   ขึ้นบน
  พิมพ์  
 
กระโดดไป: