คลาสเธรดใน Delphi-(1)
คลาสเธรดใน Delphi-(1) Raptor (งานต้นฉบับ)
คำหลัก theadeventcriticalsectionsynchronize
คลาสเธรดใน Delphi
Raptors [Mentalstudio]
http://mental.mentsu.com
(หนึ่ง)
มีคลาสเธรด tthread ใน Delphi ที่ใช้ในการใช้การเขียนโปรแกรมแบบมัลติเธรด ซิงโครไนซ์เสร็จสิ้น อย่างไรก็ตามนี่ไม่ใช่การเขียนโปรแกรมแบบมัลติเธรดทั้งหมด
เธรดเป็นส่วนหนึ่งของรหัสที่ทำงานพร้อมกันในกระบวนการ กระบวนการมีเธรดอย่างน้อยหนึ่งเธรดที่เรียกว่าเธรดหลัก นอกจากนี้ยังสามารถมีหลายเธรดเด็ก เมื่อใช้มากกว่าหนึ่งเธรดในกระบวนการจะเรียกว่า "มัลติเธรด"
ดังนั้นสิ่งนี้เรียกว่า "ชิ้นส่วนของรหัส" ที่กำหนดไว้อย่างไร? มันเป็นฟังก์ชั่นหรือกระบวนการ (สำหรับ Delphi)
หากคุณใช้ Windows API เพื่อสร้างเธรดจะถูกนำไปใช้ผ่านฟังก์ชั่น API ที่เรียกว่า createTheRke ซึ่งกำหนดเป็น:
handleCreateTheDread (
lpsecurity_attributeslpthreadattributes
dworddwstacksize,
lpthread_start_routinelpstartaddress
lpvoidlpparameter
dworddwcreationflags
lpdwordlpthreadid
-
พารามิเตอร์ของพวกเขาดังที่ได้กล่าวไว้ในชื่อของพวกเขาคือ: แอตทริบิวต์เธรด (ใช้ในการตั้งค่าแอตทริบิวต์ความปลอดภัยของเธรดภายใต้ NT ไม่ถูกต้องภายใต้ 9x) ขนาดสแต็กที่อยู่เริ่มต้นพารามิเตอร์และธงการสร้าง (ใช้เพื่อตั้งค่าสถานะการสร้างเวลา) , ID เธรดและในที่สุดก็ส่งคืนด้ามจับ ที่อยู่เริ่มต้นคือทางเข้าสู่ฟังก์ชั่นเธรดและเธรดจะสิ้นสุดจนกระทั่งฟังก์ชั่นเธรดสิ้นสุดลง
กระบวนการดำเนินการของเธรดทั้งหมดมีดังนี้:
เนื่องจากพารามิเตอร์ createThread มีจำนวนมากและเป็น Windows API ฟังก์ชั่นการทำเกลียวทั่วไปมีให้ใน cruntimelibrary (ในทางทฤษฎีจึงสามารถใช้ในระบบปฏิบัติการใด ๆ ที่รองรับเธรด):
unsignedlong_beginthread (void (_userentry*__ เริ่มต้น) (void*), unsigned__stksize, void*__ arg);
Delphi ยังมีฟังก์ชั่นที่คล้ายกันด้วยฟังก์ชั่นเดียวกัน:
FunctionBeginThread (SecurityAttributes: Pointer; stackSize: longword; threadfunc: tthreadfunc; พารามิเตอร์: ตัวชี้; CreationFlags: longword; varthreadid: longword): จำนวนเต็ม;
ฟังก์ชั่นของฟังก์ชั่นทั้งสามนี้เหมือนกัน ความแตกต่างที่ใหญ่ที่สุดระหว่างฟังก์ชั่นเธรดและฟังก์ชั่นทั่วไปคือทันทีที่ฟังก์ชั่นเธรดเริ่มต้นขึ้นฟังก์ชั่นเริ่มต้นของเธรดทั้งสามนี้กลับมาและเธรดหลักยังคงดำเนินการลงในขณะที่ฟังก์ชั่นเธรดถูกดำเนินการในเธรดอิสระ ต้องใช้ในการดำเนินการและสิ่งที่เมื่อกลับมาเธรดหลักไม่สนใจหรือรู้
ภายใต้สถานการณ์ปกติหลังจากฟังก์ชั่นเธรดส่งคืนเธรดจะสิ้นสุดลง แต่มีวิธีอื่น:
Windows API:
VoidexitThread (dworddwexitcode);
cruntimelibrary:
void_endthread (เป็นโมฆะ);
Delphiruntimelibrary:
ProcedureEndThread (ExitCode: Integer);
เพื่อบันทึกข้อมูลเธรดที่จำเป็น (สถานะ/คุณสมบัติ ฯลฯ ) ระบบปฏิบัติการจะสร้างวัตถุภายในสำหรับเธรด เธรดจะสิ้นสุด
แม้ว่าการเขียนโปรแกรมแบบมัลติเธรดสามารถทำได้อย่างง่ายดายโดยใช้ API หรือ RTL (Runtimelibrary) การประมวลผลที่มีรายละเอียดเพิ่มเติมยังคงเป็นสิ่งจำเป็น
มันเป็นเรื่องง่ายมากที่จะใช้คลาสนี้ นี่คือฟังก์ชั่นเธรดซึ่งเป็นส่วนโค้ดที่ดำเนินการในเธรด) สำหรับรายละเอียดเฉพาะเกี่ยวกับเรื่องนี้ฉันจะไม่ทำซ้ำที่นี่โปรดดูหนังสือที่เกี่ยวข้อง
ต่อไปในบทความนี้เราจะหารือเกี่ยวกับคลาส tthread ห่อหุ้มเธรดนั่นคือเราจะทำการวิจัยเชิงลึกเกี่ยวกับการดำเนินการของคลาส tthread เพราะมันจะดีกว่าที่จะใช้ถ้าคุณเข้าใจจริงๆ
ด้านล่างคือการประกาศคลาส tthread ใน Delphi7 (บทความนี้จะกล่าวถึงการใช้งานภายใต้แพลตฟอร์ม Windows เท่านั้นดังนั้นรหัสทั้งหมดเกี่ยวกับส่วนแพลตฟอร์ม Linux จะถูกลบออก):
tthread = คลาส
ส่วนตัว
FHANDLE: Thandle;
fthreadid: thandle;
fcreatesuspended: บูลีน;
fterminated: บูลีน;
fsuspended: บูลีน;
ffreeontermate: บูลีน;
ffinished: บูลีน;
Freturnvalue: จำนวนเต็ม;
Fonterminate: tnotifyevent;
fsynchronize: tsynchronizerecord;
ffatalexception: tobject;
ProcedureCallontermate;
classproceduresynchronize (asyncrec: psynchronizerecord);
functiongetPriority: tthreadpriority;
ProcedureSetPriority (ค่า: tthreadpriority);
ขั้นตอนการตอบโต้ (ค่า: บูลีน);
ได้รับการคุ้มครอง
ProcedureCheckThreadError (ErrCode: Integer); Overload;
ProcedureCheckThreadError (ความสำเร็จ: บูลีน); โอเวอร์โหลด;
proceduredoterminate; เสมือน;
procedureexecute; เสมือน; บทคัดย่อ;
Proceduresynchronize (วิธีการ: tthreadmethod); โอเวอร์โหลด;
PropertyReturnValue: IntegerReadFreturnValuewRiteFreturnValue;
PropertyTreminated: BooleanReadFterminated;
สาธารณะ
ConstructorCreate (createSuspended: บูลีน);
Destructordestroy; แทนที่;
ProcedUreferTruction; Override;
ขั้นตอน
Proceduresuspend;
ขั้นตอน
functionwaitfor: longword;
classproceduresynchronize (athread: tthread; amethod: tthreadmethod); overload;
classprocedurestaticsynchronize (athread: tthread; amethod: tthreadmethod);
PropertyFatalexception: Tobjectreadffatalexception;
PropertyFreeOnTermate: BooleanReadFREEONTERMINATEDWRITEFREEONTERMINATE;
PropertyHandle: Thandlereadfhandle;
PropertyPriority: TTHREADPROIRITYREADGETPROIRITYWRITESSETPROIRITY;
PropertySuspended: BooleanReadFsuspendedWritesetSuspended;
PropertyThreadid: Thandlereadfthreadid;
PropertyOntermate: tnotifyEventReadFonTermateWriteFonTermate;
จบ;
คลาส TThread เป็นคลาสที่ค่อนข้างง่ายใน Delphi RTL โดยมีสมาชิกชั้นเรียนไม่มากและแอตทริบิวต์คลาสนั้นง่ายมากและชัดเจน
(จะดำเนินการต่อ)
คลาสเธรดใน Delphi-(2)
คลาสเธรดใน Delphi-(2) Raptor (งานต้นฉบับ)
คำหลัก theadeventcriticalsectionsynchronize
คลาสเธรดใน Delphi
Raptors [Mentalstudio]
http://mental.mentsu.com
สอง
อย่างแรกคือตัวสร้าง:
constructortHread.create (createSuspended: บูลีน);
เริ่ม
มรดก;
addThread;
fsuspended: = createSuspended;
fcreatesuspended: = createSuspended;
fhandle: = เริ่มต้น (ไม่มี, 0,@threadproc, ตัวชี้ (ตัวเอง), create_suspended, fthreadid);
iffhandle = 0 แล้ว
RaiseThread.Createresfmt (@SthreadCreateRor, [syserrorMessage (getLasterror)]);
จบ;
แม้ว่าตัวสร้างนี้จะไม่มีรหัสมาก แต่ก็ถือได้ว่าเป็นสมาชิกที่สำคัญที่สุดเพราะเธรดถูกสร้างขึ้นที่นี่
หลังจากเรียก tobject.create ผ่านการสืบทอดประโยคแรกคือการเรียกกระบวนการ: addThread ซอร์สโค้ดของมันมีดังนี้:
ProcedUreadDTHREAD;
เริ่ม
InterlockedIncrement (ThreadCount);
จบ;
นอกจากนี้ยังมีการลบที่เกี่ยวข้อง:
ProcedurerEmoveThread;
เริ่ม
InterlockedDecrement (ThreadCount);
จบ;
ฟังก์ชั่นของพวกเขานั้นง่ายมากซึ่งจะนับจำนวนเธรดในกระบวนการโดยการเพิ่มหรือลดตัวแปรทั่วโลก อย่างไรก็ตามกระบวนการ INC/DEC ที่ใช้กันทั่วไปที่ใช้ในที่นี้ไม่ได้เป็นกระบวนการ INC/DEC ที่ใช้กันทั่วไป แต่ใช้กระบวนการเชื่อมต่อระหว่างการเชื่อมต่อ/เชื่อมต่อกัน แต่มีความแตกต่างที่ใหญ่ที่สุดอย่างหนึ่งระหว่างพวกเขานั่นคือ InterlockedIncrement/InterlockedDecrement คือ Thread-Safe นั่นคือพวกเขาสามารถมั่นใจได้ว่าผลลัพธ์การดำเนินการนั้นถูกต้องภายใต้มัลติเธรด แต่ INC/DEC ไม่สามารถทำได้ หรือในแง่ของทฤษฎีระบบปฏิบัติการนี่คือคู่ของการดำเนินการ "ดั้งเดิม"
นำเพิ่มเติมหนึ่งเป็นตัวอย่างเพื่อแสดงความแตกต่างในรายละเอียดการใช้งานของทั้งสอง:
โดยทั่วไปการพูดมีสามขั้นตอนหลังจากย่อยสลายการทำงานของการเพิ่มหนึ่งลงในข้อมูลหน่วยความจำ:
1. อ่านข้อมูลจากหน่วยความจำ
2. เพิ่มข้อมูลหนึ่งข้อมูล
3. บันทึกในหน่วยความจำ
ตอนนี้สมมติว่าการดำเนินการของการเพิ่มหนึ่งโดยใช้ Inc ในแอปพลิเคชันสองเธรดอาจเกิดขึ้น:
1. เธรด A อ่านข้อมูลจากหน่วยความจำ (สมมติว่าเป็น 3)
2. เธรด B อ่านข้อมูลจากหน่วยความจำ (เช่น 3)
3. เธรด A เพิ่มหนึ่งลงในข้อมูล (ตอนนี้คือ 4)
4. เธรด B เพิ่มหนึ่งลงในข้อมูล (ตอนนี้เป็น 4)
5. เธรด A เก็บข้อมูลลงในหน่วยความจำ (ข้อมูลในหน่วยความจำตอนนี้ 4)
6. เธรด B ยังเก็บข้อมูลไว้ในหน่วยความจำ (ข้อมูลในหน่วยความจำยังคงเป็น 4 ในขณะนี้ แต่เธรดทั้งสองจะเพิ่มหนึ่งรายการซึ่งควรเป็น 5 ดังนั้นผลลัพธ์ข้อผิดพลาดจะปรากฏขึ้นที่นี่)
ไม่มีปัญหากับกระบวนการ interlockincrement เนื่องจากสิ่งที่เรียกว่า "ดั้งเดิม" เป็นการดำเนินการที่ไม่หยุดยั้งนั่นคือระบบปฏิบัติการสามารถมั่นใจได้ว่าจะไม่มีการสลับเธรดก่อนที่จะดำเนินการ "ดั้งเดิม" ดังนั้นในตัวอย่างข้างต้นเฉพาะหลังจากเธรด A ได้จัดเก็บข้อมูลลงในหน่วยความจำเสร็จแล้วเธรด B สามารถเริ่มดึงหมายเลขจากมันและเพิ่มการดำเนินการหนึ่งครั้งซึ่งทำให้มั่นใจได้ว่าแม้ในสถานการณ์มัลติเธรดผลลัพธ์จะถูกต้องแน่นอน
ตัวอย่างก่อนหน้านี้ยังแสดงให้เห็นถึงสถานการณ์ของ "ความขัดแย้งในการเข้าถึงเธรด" ซึ่งเป็นสาเหตุที่เธรดจำเป็นต้อง "ซิงโครไนซ์"
การพูดถึงการซิงโครไนซ์มีการเบี่ยงเบนความสนใจ: Li Ming ศาสตราจารย์ที่มหาวิทยาลัยวอเตอร์ลูในแคนาดาเมื่อมีการคัดค้านการแปลคำว่าซิงโครไนซ์เป็น "การซิงโครไนซ์" ใน "การซิงโครไนซ์ด้าย" สมเหตุสมผลมาก ในภาษาจีน "การซิงโครไนซ์" หมายถึง "เกิดขึ้นในเวลาเดียวกัน" ในขณะที่ "การซิงโครไนซ์เธรด" มีวัตถุประสงค์เพื่อหลีกเลี่ยง "เกิดขึ้นในเวลาเดียวกัน" ในภาษาอังกฤษการซิงโครไนซ์มีสองความหมาย: หนึ่งคือการซิงโครไนซ์ในความหมายดั้งเดิม (tooccuratthesametime) และอีกอันคือ "การประสานงาน" คำที่ซิงโครไนซ์ใน "การซิงโครไนซ์เธรด" ควรอ้างถึงความหมายหลังนั่นคือ "เพื่อให้แน่ใจว่าหลายเธรดรักษาการประสานงานและหลีกเลี่ยงข้อผิดพลาดเมื่อเข้าถึงข้อมูลเดียวกัน" อย่างไรก็ตามยังมีคำพูดมากมายเช่นนี้ที่ไม่ได้แปลอย่างถูกต้องในอุตสาหกรรมไอที มันควรจะชี้แจง
ฉันจะไปไกลและฉันจะกลับไปที่คอนสตรัคเตอร์ของ TThread
fhandle: = เริ่มต้น (ไม่มี, 0,@threadproc, ตัวชี้ (ตัวเอง), create_suspended, fthreadid);
ที่นี่เราใช้ฟังก์ชั่น Delphirtl startHread ที่กล่าวถึงข้างต้น พารามิเตอร์ที่สามคือฟังก์ชั่นเธรดที่กล่าวถึงข้างต้นนั่นคือส่วนรหัสที่ดำเนินการในเธรด พารามิเตอร์ที่สี่คือพารามิเตอร์ที่ส่งผ่านไปยังฟังก์ชันเธรดนี่คือวัตถุเธรดที่สร้างขึ้น (เช่นตัวเอง) ท่ามกลางพารามิเตอร์อื่น ๆ ที่ห้าใช้เพื่อตั้งเธรดเพื่อระงับหลังจากการสร้างและไม่ดำเนินการทันที (งานของการเริ่มต้นเธรดจะถูกกำหนดในการก่อสร้างหลังการสร้างธง createSuspended) และที่หกคือการส่งคืนรหัสเธรด
ทีนี้มาดูแกนของ tthread: ฟังก์ชั่นเธรดเธรดเธรด ที่น่าสนใจคือแกนหลักของคลาสเธรดนี้ไม่ได้เป็นสมาชิกของเธรด แต่เป็นฟังก์ชันทั่วโลก (เนื่องจากอนุสัญญาพารามิเตอร์ของกระบวนการเริ่มต้นใช้งานสามารถใช้ฟังก์ชั่นส่วนกลางเท่านั้น) นี่คือรหัส:
functionThreadProc (เธรด: tthread): จำนวนเต็ม;
วาจา
Freethread: บูลีน;
เริ่ม
พยายาม
ifnotthread.erminatedthen
พยายาม
thread.execute;
ยกเว้น
thread.ffatalexception: = AcquireeExceptionObject;
จบ;
ในที่สุด
freethread: = thread.ffreeontermate;
ผลลัพธ์: = thread.freturnvalue;
Thread.doterminate;
thread.Ffinished: = true;
สัญญาณซินซีเวนท์;
iffreethreadthenthread.free;
endthread (ผลลัพธ์);
จบ;
จบ;
แม้ว่าจะมีรหัสไม่มาก แต่ก็เป็นส่วนที่สำคัญที่สุดของ TTHREAD ทั้งหมดเพราะรหัสนี้เป็นรหัสที่ดำเนินการจริงในเธรด ต่อไปนี้เป็นคำอธิบายแบบทีละบรรทัดของรหัส:
ขั้นแรกให้ตัดสินธงที่ถูกยกเลิกของคลาสเธรด กำลังดำเนินการรหัสดำเนินการในคลาสที่ได้รับ
ดังนั้นการดำเนินการเป็นฟังก์ชันเธรดในคลาสเธรด
หากมีข้อยกเว้นเกิดขึ้นในการดำเนินการวัตถุข้อยกเว้นจะได้รับผ่าน AcquireeExceptionObject และเก็บไว้ในสมาชิก ffatalexception ของคลาสเธรด
ในที่สุดก็มีงานตกแต่งบางอย่างก่อนที่เธรดจะสิ้นสุดลง ตัวแปรท้องถิ่น freethread บันทึกการตั้งค่าของคุณสมบัติ freeonterminated ของคลาสเธรดจากนั้นตั้งค่าเธรดส่งคืนค่าเป็นค่าของแอตทริบิวต์ค่าคืนคลาสเธรด จากนั้นเรียกใช้วิธี doterminate ของคลาสเธรด
รหัสของวิธี doterminate มีดังนี้:
ProceduretThread.doterminate;
เริ่ม
ifassigned (fonterminate) thensynchronize (callonterminate);
จบ;
มันง่ายมากคือเรียกใช้วิธีการ callonterminate ผ่านการซิงโครไนซ์และรหัสของวิธี callontermate มีดังนี้ซึ่งก็คือการโทรหาเหตุการณ์ onterminate:
ProcedureTTHREAD.CALLONTERMINATE;
เริ่ม
ifassigned (fontermate) thenfontermate (ตัวเอง);
จบ;
เนื่องจากเหตุการณ์ onterminate ถูกดำเนินการในการซิงโครไนซ์จึงไม่ใช่รหัสเธรด แต่เป็นหลัก แต่รหัสเธรดหลัก (ดูการวิเคราะห์การซิงโครไนซ์ในภายหลัง)
หลังจากดำเนินการ onterminate ให้ตั้งค่าสถานะ ffinished ของคลาสเธรดเป็น TRUE
จากนั้นดำเนินการตามกระบวนการสัญญาณ SignalSynceVent และรหัสของมันมีดังนี้:
Proceduresignalsyncevent;
เริ่ม
setEvent (syncevent);
จบ;
นอกจากนี้ยังง่ายมากคือการตั้งค่ากิจกรรมระดับโลก: SynceVent