ธุรกิจของฉันคือแฟรนไชส์ การให้คะแนน เรื่องราวความสำเร็จ ไอเดีย การทำงานและการศึกษา
ค้นหาไซต์

วัตถุการซิงโครไนซ์ใดมีตัวนับทรัพยากร เครื่องมือซิงโครไนซ์เธรดใน Windows OS (ส่วนสำคัญ, mutexes, เซมาฟอร์, เหตุการณ์)

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

แนวคิดหลักเบื้องหลังการซิงโครไนซ์เธรดใน Win32 คือการใช้วัตถุการซิงโครไนซ์และฟังก์ชันรอ วัตถุสามารถอยู่ในสถานะใดสถานะหนึ่งจากสองสถานะ - มีสัญญาณหรือไม่มีสัญญาณ รอฟังก์ชันบล็อกการดำเนินการเธรดตราบใดที่วัตถุที่ระบุอยู่ในสถานะไม่มีการส่งสัญญาณ ดังนั้น เธรดที่ต้องการการเข้าถึงทรัพยากรแบบเอกสิทธิ์เฉพาะบุคคลจะต้องตั้งค่าออบเจ็กต์การซิงโครไนซ์บางส่วนให้เป็นสถานะที่ไม่มีสัญญาณ และเมื่อเสร็จสิ้น ให้รีเซ็ตเป็นสถานะส่งสัญญาณ เธรดอื่นๆ จะต้องเรียกใช้ฟังก์ชันรอก่อนจึงจะเข้าถึงทรัพยากรนี้ได้ ซึ่งจะทำให้สามารถรอให้ทรัพยากรพร้อมใช้งานได้

มาดูกันว่าออบเจ็กต์และฟังก์ชันการซิงโครไนซ์ใดบ้างที่ Win32 API มอบให้เรา

ฟังก์ชั่นการซิงโครไนซ์

ฟังก์ชันการซิงโครไนซ์แบ่งออกเป็นสองประเภทหลัก: ฟังก์ชันที่รอบนออบเจ็กต์เดียว และฟังก์ชันที่รอบนออบเจ็กต์หนึ่งในหลาย ๆ ออบเจ็กต์

ฟังก์ชั่นที่คาดหวังวัตถุเดียว

ฟังก์ชันรอที่ง่ายที่สุดคือฟังก์ชัน WaitForSingleObject:

ฟังก์ชั่น WaitForSingleObject (hHandle: THandle; // ตัวระบุวัตถุ dwMilliseconds: DWORD // ระยะเวลารอ): DWORD; stdcall;

ฟังก์ชันรอ dwMilliseconds มิลลิวินาทีสำหรับวัตถุhHandleเพื่อเข้าสู่สถานะสัญญาณ ถ้าคุณส่ง INFINITE เป็นพารามิเตอร์ dwMilliseconds ฟังก์ชันจะรออย่างไม่มีกำหนด ถ้า dwMilliseconds เป็น 0 ฟังก์ชันจะตรวจสอบสถานะของออบเจ็กต์และส่งคืนทันที

ฟังก์ชันส่งคืนค่าใดค่าหนึ่งต่อไปนี้:

ข้อมูลโค้ดต่อไปนี้ปฏิเสธการเข้าถึง Action1 จนกว่าวัตถุ ObjectHandle จะอยู่ในสถานะส่งสัญญาณ (ตัวอย่างเช่น วิธีนี้คุณสามารถรอให้กระบวนการเสร็จสมบูรณ์โดยส่ง ObjectHandle เป็นตัวระบุที่ได้รับโดยฟังก์ชัน CreateProcess):

เหตุผล: DWORD; รหัสข้อผิดพลาด: DWORD; การดำเนินการ1.เปิดใช้งานแล้ว:= FALSE; ลองทำซ้ำ Application.ProcessMessages; เหตุผล:= WailForSingleObject (ObjectHandle, 10); ถ้าเหตุผล = WAIT_FAILED ให้เริ่ม ErrorCode:= GetLastError; เพิ่ม Exception.CreateFmt ('รอวัตถุล้มเหลวโดยมีข้อผิดพลาด: % d', ); จบ; จนกระทั่งมีเหตุผล<>WAIT_TIMEOUT; ในที่สุด Actionl.Enabled:= TRUE; จบ;

ในกรณีที่จำเป็นต้องทำให้วัตถุอื่นอยู่ในสถานะสัญญาณไปพร้อมๆ กับการรอวัตถุ คุณสามารถใช้ฟังก์ชัน SignalObjectAndWait ได้:

ฟังก์ชั่น SignalObjectAndWait(hObjectToSignal: THandle; // วัตถุที่จะถูกถ่ายโอนไปยัง // สถานะสัญญาณ hObjectToWaitOn: THandle; // วัตถุ, ที่ฟังก์ชันคาดหวัง dwMilliseconds: DWORD; // ระยะเวลารอ bAlertable: BOOL // ระบุว่าฟังก์ชันควรส่งคืน // ควบคุมในกรณีที่มีการร้องขอให้ // ดำเนินการ I/O ให้เสร็จสิ้น): DWORD; stdcall;

ค่าที่ส่งคืนจะเหมือนกับฟังก์ชัน WaitForSingleObject

อ็อบเจ็กต์hObjectToSignalอาจเป็นเซมาฟอร์ เหตุการณ์ หรือ mutex พารามิเตอร์ bAlertable กำหนดว่าการรออ็อบเจ็กต์จะถูกขัดจังหวะหรือไม่ หากระบบปฏิบัติการร้องขอเธรดเพื่อทำการดำเนินการ I/O แบบอะซิงโครนัส หรือการเรียกโพรซีเดอร์แบบอะซิงโครนัส เราจะกล่าวถึงรายละเอียดเพิ่มเติมด้านล่าง

ฟังก์ชั่นที่คาดหวังหลายวัตถุ

บางครั้งจำเป็นต้องชะลอการดำเนินการของเธรดจนกระทั่งกลุ่มของวัตถุหนึ่งหรือทั้งหมดถูกทริกเกอร์ เพื่อแก้ไขปัญหานี้ มีการใช้ฟังก์ชันต่อไปนี้:

พิมพ์ TWOHandleArray = อาร์เรย์ของ THandle; PWOHandleArray = ^TWOHandleArray; ฟังก์ชั่น WaitForMultipleObjects (nCount: DWORD; // ตั้งค่าจำนวนอ็อบเจ็กต์ lpHandles: PWOHandleArray; // ที่อยู่ของอาร์เรย์ของอ็อบเจ็กต์ bWaitAll: BOOL; // ตั้งค่าว่าจะรอทั้งหมด // อ็อบเจ็กต์หรือ dwMilliseconds ใด ๆ: DWORD // ระยะเวลารอ ): DWORD; stdcall;

ฟังก์ชันส่งคืนค่าใดค่าหนึ่งต่อไปนี้:

จำนวนตั้งแต่

WAIT_OBJECT_0 ถึง WAIT_OBJECT_0 + nนับ – 1

หาก bWaitAll เป็นจริง ตัวเลขนี้หมายความว่าออบเจ็กต์ทั้งหมดเข้าสู่สถานะสัญญาณแล้ว ถ้าเป็น FALSE แล้วลบ WAIT_OBJECT_0 จากค่าที่ส่งคืน เราจะได้ดัชนีของอ็อบเจ็กต์ในอาร์เรย์ lpHandles

จำนวนตั้งแต่

WAIT_ABANDONED_0 ถึง WAIT_ABANDONED_0 + nนับ – 1

หาก bWaitAll เป็น TRUE หมายความว่าอ็อบเจ็กต์ทั้งหมดเข้าสู่สถานะ signaled แต่อย่างน้อยหนึ่งเธรดที่เป็นเจ้าของเธรดเหล่านั้นออกโดยไม่ทำให้อ็อบเจ็กต์ส่งสัญญาณ ถ้า FALSE ให้ลบออกจากค่าที่ส่งคืน ค่านิยม WAIT_ABANDONED_0 เราจะได้รับดัชนีของวัตถุในอาร์เรย์ lpHandles ในขณะที่เธรด เจ้าของทรัพย์สินนี้จบลงโดยไม่สร้างสัญญาณใดๆ
WAIT_TIMEOUT ระยะเวลารอคอยสิ้นสุดลงแล้ว
WAIT_FAILED เกิดข้อผิดพลาด

ตัวอย่างเช่น ในส่วนของโค้ดต่อไปนี้ โปรแกรมจะพยายามแก้ไขทรัพยากรที่แตกต่างกันสองรายการที่ใช้ร่วมกันระหว่างเธรด:

Var Handles: อาร์เรย์ของ THandle; เหตุผล: DWORD; RestIndex: จำนวนเต็ม; ... จัดการ := OpenMutex(SYNCHRONIZE, FALSE, 'FirstResource'); จัดการ := OpenMutex (SYNCHRONIZE, FALSE, 'SecondResource'); // กำลังรอวัตถุเหตุผลแรก:= WaitForMultipleObjects(2, @Handles, FALSE, INFINITE); กรณีเหตุผลของ WAIT_FAILED: RaiseLastWin32Error; WAIT_OBJECT_0, WAIT_ABANDONED_0: เริ่ม ModifyFirstResource; ดัชนีส่วนที่เหลือ:= 1; จบ; WAIT_OBJECT_0 + 1, WAIT_ABANDONED_0 + 1: เริ่ม ModifySecondResource; ดัชนีส่วนที่เหลือ:= 0; จบ; // WAIT_TIMEOUT ไม่สามารถสิ้นสุดได้ // ตอนนี้รอให้วัตถุถัดไปถูกปลดปล่อยถ้า WailForSingleObject(Handles, INFINITE) = WAIT_FAILED แล้ว RaiseLastWin32Error; // รอสักครู่ แก้ไขทรัพยากรที่เหลือถ้า RestIndex = 0 จากนั้น ModifyFirstResource มิฉะนั้น ModifySecondResource;

สามารถใช้เทคนิคที่อธิบายไว้ข้างต้นได้หากคุณรู้แน่ว่าความล่าช้าในการรอวัตถุนั้นไม่มีนัยสำคัญ มิฉะนั้นโปรแกรมของคุณจะถูกค้างและจะไม่สามารถวาดหน้าต่างใหม่ได้ หากระยะเวลาล่าช้ามีนัยสำคัญ คุณจะต้องให้โอกาสโปรแกรมในการตอบกลับข้อความ Windows วิธีแก้ไขอาจเป็นการใช้ฟังก์ชันที่มีระยะเวลารอคอยที่จำกัด (และโทรอีกครั้งหากส่งคืน WAIT_TIMEOUT) หรือฟังก์ชัน MsgWaitForMultipleObjects:

ฟังก์ชั่น MsgWaitForMultipleObjects(nCount: DWORD; // จำนวนวัตถุการซิงโครไนซ์ var pHandles; // ที่อยู่ของอาร์เรย์ของวัตถุ fWaitAll: BOOL; // ตั้งค่าว่าจะรอทั้งหมด // วัตถุหรือ dwMilliseconds ใด ๆ // ระยะเวลารอ dwWakeMask: DWORD // ประเภทเหตุการณ์ ขัดจังหวะการรอ): DWORD; stdcall;

ข้อแตกต่างที่สำคัญระหว่างฟังก์ชันนี้กับฟังก์ชันก่อนหน้าคือพารามิเตอร์ dwWakeMask ซึ่งเป็นการรวมกันของแฟล็กบิต QS_XXX และระบุประเภทของข้อความที่ขัดจังหวะการรอของฟังก์ชัน โดยไม่คำนึงถึงสถานะของออบเจ็กต์ที่รอ ตัวอย่างเช่น หน้ากาก QS_KEY ช่วยให้คุณสามารถขัดจังหวะการรอเมื่อข้อความ WM_KEYUP, WM_KEYDOWN, WM_SYSKEYUP หรือ WM_SYSKEYDOWN ปรากฏในคิว และหน้ากาก QS_PAINT ช่วยให้คุณสามารถขัดจังหวะข้อความ WM_PAINT สำหรับรายการค่าทั้งหมดที่อนุญาตสำหรับ dwWakeMask โปรดดูเอกสารประกอบ Windows SDK เมื่อข้อความที่สอดคล้องกับมาสก์ที่ระบุปรากฏในคิวของเธรดที่เรียกใช้ฟังก์ชัน ฟังก์ชันจะส่งกลับค่า WAIT_OBJECT_0 + nCount เมื่อโปรแกรมของคุณได้รับค่านี้แล้ว โปรแกรมก็สามารถประมวลผลและเรียกใช้ฟังก์ชันรออีกครั้งได้ ลองพิจารณาตัวอย่างด้วยการเปิดตัวแอปพลิเคชันภายนอก (จำเป็นที่โปรแกรมที่เรียกจะไม่ตอบสนองต่ออินพุตของผู้ใช้ในขณะที่กำลังทำงานอยู่ แต่หน้าต่างจะต้องถูกวาดใหม่ต่อไป):

ขั้นตอน TForm1.Button1Click (ผู้ส่ง: TObject); var PI: TProcessInformation; SI: TStartupInfo; เหตุผล: DWORD; ข่าวสารเกี่ยวกับ:TMsg; เริ่มต้น // เริ่มต้นโครงสร้าง TStartupInfo FillChar (SI, SizeOf (SI), 0); SI.cb:= ขนาดของ(SI); // เรียกใช้โปรแกรมภายนอก Win32Check(CreateProcess(NIL, "COMMAND.COM", NIL, NIL, FALSE, 0, NIL, NIL, SI, PI)); //************************************************ ** // ลองแทนที่โค้ดด้านล่างด้วยบรรทัด // WaitForSingleObject(PI.hProcess, INFINITE); // และดูว่าโปรแกรมจะตอบสนองอย่างไร // ย้ายหน้าต่างอื่นไว้เหนือหน้าต่าง //****************************** ******************** ทำซ้ำ // รอให้กระบวนการย่อยหรือข้อความเสร็จสิ้น // วาด WM_PAINT เหตุผล:= MsgWaitForMultipleObjects(1, PI.hProcess, FALSE, INFINITE, QS_PAINT) ; ถ้าเหตุผล = WAIT_OBJECT_0 + 1 ให้เริ่ม // WM_PAINT ปรากฏในคิวข้อความ - Windows // ต้องอัปเดตหน้าต่างโปรแกรม // ลบข้อความออกจากคิว PeekMessage(Msg, 0, WM_PAINT, WM_PAINT, PM_REMOVE); // และวาดหน้าต่างอัปเดตของเราใหม่ จบ; // ทำซ้ำลูปจนกว่ากระบวนการลูกจะออกจนกว่าเหตุผล = WAIT_OBJECT_0; // ลบข้อความที่สะสมไว้ออกจากคิวในขณะที่ PeekMessage (Msg, 0, 0, 0, PM_REMOVE) ทำ; CloseHandle(PI.hProcess); สิ้นสุด CloseHandle (PI.hThread);

ถ้าหน้าต่าง Windows ถูกสร้างขึ้นอย่างชัดเจน (โดยใช้ฟังก์ชัน CreateWindow) หรือโดยนัย (โดยใช้ TForm, DDE, COM) ในเธรดที่เรียกใช้ฟังก์ชันรอ เธรดต้องประมวลผลข้อความ เนื่องจากข้อความออกอากาศถูกส่งไปยังหน้าต่างทั้งหมดในระบบ เธรดที่ไม่ได้ประมวลผลข้อความอาจทำให้เกิดการหยุดชะงัก (ระบบรอให้เธรดประมวลผลข้อความ เธรดสำหรับระบบหรือเธรดอื่น ๆ เพื่อปล่อยอ็อบเจ็กต์) และ ทำให้ Windows ค้าง ถ้าโปรแกรมของคุณมีแฟรกเมนต์ดังกล่าว คุณต้องใช้ MsgWaitForMultipleObjects หรือ MsgWaitForMultipleObjectsEx และอนุญาตให้รอการประมวลผลข้อความถูกขัดจังหวะ อัลกอริทึมจะคล้ายกับตัวอย่างข้างต้น

ขัดจังหวะขณะรอคำขอเพื่อดำเนินการ I/O หรือ APC ให้เสร็จสิ้น

Windows รองรับการเรียกขั้นตอนแบบอะซิงโครนัส เมื่อแต่ละเธรดถูกสร้างขึ้น คิวของการเรียกขั้นตอนแบบอะซิงโครนัส (คิว APC) จะเชื่อมโยงอยู่ด้วย ระบบปฏิบัติการ (หรือแอปพลิเคชันผู้ใช้ - โดยใช้ฟังก์ชัน QueueUserAPC) สามารถส่งคำขอเพื่อดำเนินการฟังก์ชันในบริบทของเธรดที่กำหนด ไม่สามารถดำเนินการฟังก์ชันเหล่านี้ได้ทันทีเนื่องจากเธรดอาจไม่ว่าง ดังนั้น ระบบปฏิบัติการจะเรียกมันเมื่อเธรดเรียกฟังก์ชันรออย่างใดอย่างหนึ่งต่อไปนี้:

ฟังก์ชั่น SleepEx(dwMilliseconds: DWORD; // ระยะเวลารอ bAlertable: BOOL // ระบุว่าฟังก์ชันควรส่งคืน // ควบคุมในกรณีที่มีการร้องขอ // การเรียกขั้นตอนแบบอะซิงโครนัส): DWORD; stdcall; ฟังก์ชั่น WaitForSingleObjectEx (hHandle: THandle; // ตัวระบุวัตถุ dwMilliseconds: DWORD; // ระยะเวลารอ bAlertable: BOOL // ตั้งค่าว่าฟังก์ชันควรส่งคืน // ควบคุมในกรณีที่มีการร้องขอ // การเรียกขั้นตอนแบบอะซิงโครนัส): DWORD; stdcall; ฟังก์ชั่น WaitForMultipleObjectsEx (nCount: DWORD; // จำนวนอ็อบเจ็กต์ lpHandles: PWOHandleArray; // ที่อยู่ของอาร์เรย์ของตัวระบุอ็อบเจ็กต์ bWaitAll: BOOL; // ตั้งค่าว่าจะรอทั้งหมด // อ็อบเจ็กต์หรือ dwMilliseconds ใด ๆ: DWORD; // ระยะเวลารอ bAlertable : BOOL / / ตั้งค่าว่าฟังก์ชันควรส่งคืน // ควบคุมในกรณีของการร้องขอ // การเรียกขั้นตอนแบบอะซิงโครนัสหรือไม่): DWORD; stdcall; function SignalObjectAndWait(hObjectToSignal: THandle; // object that will be put into // signal state hObjectToWaitOn: THandle; // object that the function is Waiting for dwMilliseconds: DWORD; // wait period bAlertable: BOOL // ระบุว่าฟังก์ชันควร กลับ / / ควบคุมในกรณีของการร้องขอ // การเรียกขั้นตอนแบบอะซิงโครนัส): DWORD; stdcall; ฟังก์ชั่น MsgWaitForMultipleObjectsEx (nCount: DWORD; // จำนวนวัตถุการซิงโครไนซ์ var pHandles; // ที่อยู่ของอาร์เรย์ของวัตถุ fWaitAll: BOOL; // ตั้งค่าว่าจะรอทั้งหมด // วัตถุหรือ dwMilliseconds ใด ๆ // ระยะเวลารอ dwWakeMask: DWORD // ประเภทเหตุการณ์ ขัดจังหวะการรอ dwFlags: DWORD // การตั้งค่าสถานะเพิ่มเติม): DWORD; stdcall;

ถ้า bAlertable เป็นจริง (หรือถ้า dwFlags ในฟังก์ชัน MsgWaitForMultipleObjectsEx ประกอบด้วย MWMO_ALERTABLE) จากนั้น เมื่อคำขอสำหรับการเรียกขั้นตอนแบบอะซิงโครนัสปรากฏขึ้นในคิว APC ระบบปฏิบัติการจะทำการเรียกไปยังขั้นตอนทั้งหมดในคิว หลังจากนั้นฟังก์ชันจะส่งกลับค่า WAIT_IO_COMPLETION

กลไกนี้ทำให้สามารถนำไปใช้ได้ เช่น Asynchronous I/O เธรดสามารถเริ่มต้นการดำเนินการเบื้องหลังของการดำเนินการ I/O อย่างน้อยหนึ่งรายการด้วยฟังก์ชัน ReadFileEx หรือ WriteFileEx โดยการส่งต่อที่อยู่ของฟังก์ชันตัวจัดการความสมบูรณ์ของการดำเนินการ เมื่อเสร็จสิ้น การเรียกใช้ฟังก์ชันเหล่านี้จะถูกจัดคิวเพื่อเรียกขั้นตอนแบบอะซิงโครนัส ในทางกลับกัน เธรดที่เริ่มต้นการดำเนินการ เมื่อพร้อมที่จะประมวลผลผลลัพธ์ สามารถใช้ฟังก์ชันรออย่างใดอย่างหนึ่งข้างต้น อนุญาตให้ระบบปฏิบัติการเรียกใช้ฟังก์ชันตัวจัดการได้ เนื่องจากคิว APC ถูกนำไปใช้ในระดับเคอร์เนล OS จึงมีประสิทธิภาพมากกว่าคิวข้อความ และช่วยให้ I/O มีประสิทธิภาพมากกว่ามาก

เหตุการณ์

กิจกรรมช่วยให้คุณแจ้งเตือนเธรดที่รอตั้งแต่หนึ่งเธรดขึ้นไปว่ามีเหตุการณ์เกิดขึ้น เหตุการณ์เกิดขึ้น:

หากต้องการสร้างวัตถุ ให้ใช้ฟังก์ชัน CreateEvent:

ฟังก์ชั่น CreateEvent(lpEventAttributes: PSecurityAttributes; // ที่อยู่ของโครงสร้าง // TSecurityAttributes bManualReset, // ตั้งค่าว่าเหตุการณ์จะถูกสลับ // ด้วยตนเอง (TRUE) หรือโดยอัตโนมัติ (FALSE) bInitialState: BOOL; // ตั้งค่าสถานะเริ่มต้น ถ้า TRUE - // วัตถุในสถานะสัญญาณ lpName: PChar // ชื่อหรือ NIL หากไม่จำเป็นต้องใช้ชื่อ): THandle; stdcall; // ส่งกลับตัวระบุของ // วัตถุที่สร้างขึ้น โครงสร้าง TSecurityAttributes อธิบายว่า: TSecurityAttributes = บันทึก nLength: DWORD; // ขนาดของโครงสร้างควร // เริ่มต้นเป็น // SizeOf (TSecurityAttributes) lpSecurityDescriptor: ตัวชี้; // ที่อยู่ตัวอธิบายความปลอดภัย ละเว้นใน // Windows 95 และ 98 // โดยปกติคุณสามารถระบุ NIL bInheritHandle: BOOL; // ตั้งค่าว่าเด็ก // กระบวนการสามารถสืบทอดจุดสิ้นสุดของวัตถุได้หรือไม่

ถ้าคุณไม่จำเป็นต้องตั้งค่าสิทธิ์การเข้าถึงแบบพิเศษภายใต้ Windows NT หรือความสามารถสำหรับกระบวนการลูกในการสืบทอดวัตถุ คุณสามารถส่งผ่าน NIL เป็นพารามิเตอร์lpEventAttributes ในกรณีนี้ วัตถุไม่สามารถสืบทอดโดยกระบวนการลูกและได้รับตัวบอกเกี่ยวกับความปลอดภัย "เริ่มต้น"

พารามิเตอร์ lpName ช่วยให้คุณสามารถแบ่งใช้อ็อบเจ็กต์ระหว่างกระบวนการได้ หาก lpName ตรงกับชื่อของออบเจ็กต์ที่มีอยู่ประเภท Event ซึ่งสร้างขึ้นโดยกระบวนการปัจจุบันหรือกระบวนการอื่น ๆ ฟังก์ชันจะไม่สร้างออบเจ็กต์ใหม่ แต่จะส่งคืนตัวระบุของออบเจ็กต์ที่มีอยู่ สิ่งนี้จะละเว้นพารามิเตอร์ bManualReset, bInitialState และ lpSecurityDescriptor คุณสามารถตรวจสอบว่าวัตถุถูกสร้างขึ้นหรือวัตถุที่มีอยู่ถูกใช้ดังนี้:

HEvent:= CreateEvent(ไม่มี, จริง, เท็จ, 'ชื่อเหตุการณ์'); ถ้า hEvent = 0 แล้ว RaiseLastWin32Error; ถ้า GetLastError = ERROR_ALREADY_EXISTS ให้เริ่มต้น // ใช้จุดสิ้นสุดของวัตถุที่สร้างขึ้นก่อนหน้านี้

หากใช้วัตถุสำหรับการซิงโครไนซ์ภายในกระบวนการเดียว วัตถุนั้นสามารถประกาศเป็นตัวแปรร่วมและสร้างโดยไม่มีชื่อได้

ชื่ออ็อบเจ็กต์ต้องไม่เหมือนกับชื่อของอ็อบเจ็กต์ Semaphore, Mutex, Job, Waitable Timer หรือ FileMapping ที่มีอยู่ หากชื่อตรงกัน ฟังก์ชันจะส่งกลับข้อผิดพลาด

หากคุณทราบว่ามีการสร้างกิจกรรมแล้ว คุณสามารถใช้ฟังก์ชัน OpenEvent แทน CreateEvent เพื่อเข้าถึงได้:

ฟังก์ชั่น OpenEvent(dwDesiredAccess: DWORD; // ตั้งค่าสิทธิ์การเข้าถึงวัตถุ bInheritHandle: BOOL; // ตั้งค่าว่าวัตถุสามารถสืบทอดได้หรือไม่ // โดยกระบวนการลูก lpName: PChar // ชื่อวัตถุ): THandle; stdcall;

ฟังก์ชันส่งคืนตัวระบุวัตถุหรือ 0 ในกรณีที่มีข้อผิดพลาด พารามิเตอร์dwDesiredAccessสามารถเป็นค่าใดค่าหนึ่งต่อไปนี้:

หลังจากได้รับตัวระบุแล้ว คุณสามารถเริ่มใช้งานได้ ฟังก์ชั่นต่อไปนี้มีให้สำหรับสิ่งนี้:

ฟังก์ชั่น SetEvent (hEvent: THandle): BOOL; stdcall;

ตั้งค่าวัตถุให้เป็นสถานะการเตือน

ฟังก์ชั่น ResetEvent (hEvent: THandle): BOOL; stdcall;

รีเซ็ตวัตถุ โดยทำให้วัตถุอยู่ในสถานะที่ไม่มีสัญญาณ

ฟังก์ชั่น PulseEvent (hEvent: THandle): BOOL; stdcall

ตั้งค่าออบเจ็กต์เป็นสถานะสัญญาณ ปล่อยให้ฟังก์ชันรอทั้งหมดที่รอออบเจ็กต์นั้นทำงาน จากนั้นรีเซ็ตอีกครั้ง

Windows API ใช้เหตุการณ์เพื่อดำเนินการ I/O แบบอะซิงโครนัส ตัวอย่างต่อไปนี้แสดงวิธีที่แอปพลิเคชันเริ่มต้นการเขียนไปยังสองไฟล์พร้อมกัน จากนั้นรอให้การเขียนเสร็จสิ้นก่อนดำเนินการต่อ วิธีการนี้สามารถให้ประสิทธิภาพที่ดีกว่าที่ความเข้มของ I/O สูงกว่าการเขียนตามลำดับ:

เหตุการณ์ Var: อาร์เรย์ของ THandle; // อาร์เรย์ของวัตถุการซิงโครไนซ์ ทับซ้อนกัน: อาร์เรย์ของ TOverlapped; ... // สร้างเหตุการณ์วัตถุการซิงโครไนซ์ := CreateEvent(NIL, TRUE, FALSE, NIL); เหตุการณ์ := CreateEvent(NIL, TRUE, FALSE, NIL); // เริ่มต้นโครงสร้าง TOverlapped FillChar (ทับซ้อนกัน, SizeOf (ทับซ้อนกัน), 0); Overlapped.hEvent:= กิจกรรม; Overlapped.hEvent:= กิจกรรม; // เริ่มเขียนแบบอะซิงโครนัสไปยังไฟล์ WriteFile (hFirstFile, FirstBuffer, SizeOf (FirstBuffer), FirstFileWritten, @Overlapped); WriteFile (hSecondFile, SecondBuffer, SizeOf (SecondBuffer), SecondFileWritten, @Overlapped); // รอให้การเขียนทั้งสองไฟล์เสร็จสิ้น WaitForMultipleObjects(2, @Events, TRUE, INFINITE); // ทำลายวัตถุการซิงโครไนซ์ CloseHandle (เหตุการณ์); CloseHandle(กิจกรรม)

เมื่อทำงานกับวัตถุเสร็จสมบูรณ์ จะต้องถูกทำลายโดยฟังก์ชัน CloseHandle

Delphi จัดให้มีคลาส TEvent ที่สรุปการทำงานของวัตถุเหตุการณ์ คลาสนี้อยู่ในโมดูล SyncObjs.pas และมีการประกาศดังต่อไปนี้:

พิมพ์ TWaitResult = (wrSignaled, wrTimeout, wrAbandoned, wrError); TEvent = คลาส (THandleObject) ตัวสร้างสาธารณะสร้าง (EventAttributes: PSecurityAttributes; ManualReset, InitialState: Boolean; const Name: string); ฟังก์ชั่น WaitFor (หมดเวลา: DWORD): TWaitResult; ขั้นตอน SetEvent; ขั้นตอน ResetEvent; จบ;

วัตถุประสงค์ของวิธีการนั้นชัดเจนจากชื่อของพวกเขา การใช้คลาสนี้ช่วยให้คุณไม่ต้องยุ่งยากในการใช้งานฟังก์ชัน Windows API ที่เรียกว่า สำหรับกรณีที่ง่ายที่สุด จะมีการประกาศคลาสอื่นที่มีตัวสร้างแบบง่าย:

พิมพ์ TSimpleEvent = ตัวสร้างสาธารณะคลาส (TEvent) สร้าง; จบ; ... ตัวสร้าง TSimpleEvent.Create; เริ่มต้น FHandle:= CreateEvent(ไม่มี, จริง, เท็จ, ไม่มี); จบ;

Mutex (เอกสิทธิ์ร่วมกัน)

mutex คืออ็อบเจ็กต์การซิงโครไนซ์ที่ส่งสัญญาณเฉพาะเมื่อไม่ได้เป็นเจ้าของโดยกระบวนการใดๆ เมื่ออย่างน้อยหนึ่งกระบวนการร้องขอความเป็นเจ้าของ mutex กระบวนการจะเข้าสู่สถานะที่ไม่มีการส่งสัญญาณและคงเป็นเช่นนั้นจนกว่าเจ้าของจะปล่อย ลักษณะการทำงานนี้ช่วยให้คุณใช้ mutexes เพื่อซิงโครไนซ์การเข้าถึงที่ใช้ร่วมกันของกระบวนการต่าง ๆ ไปยังทรัพยากรที่ใช้ร่วมกัน หากต้องการสร้าง mutex ให้ใช้ฟังก์ชัน:

ฟังก์ชั่น CreateMutex(lpMutexAttributes: PSecurityAttributes; // ที่อยู่ของโครงสร้าง // TSecurityAttributes bInitialOwner: BOOL; // ตั้งค่าว่ากระบวนการจะเป็นเจ้าของหรือไม่ // mutex ทันทีหลังจากสร้าง lpName: PChar // ชื่อ Mutex): THandle; stdcall;

ฟังก์ชันส่งคืนตัวระบุของวัตถุที่สร้างขึ้นหรือ 0 หาก mutex ที่มีชื่อที่กำหนดได้ถูกสร้างขึ้นแล้ว ตัวระบุจะถูกส่งกลับ ในกรณีนี้ ฟังก์ชัน GetLastError จะส่งกลับรหัสข้อผิดพลาด ERROR_ALREDY_EXISTS ชื่อต้องไม่เหมือนกับชื่อของอ็อบเจ็กต์ Semaphore, Event, Job, Waitable Timer หรือ FileMapping ที่มีอยู่

หากไม่ทราบว่ามี mutex ที่มีชื่อเดียวกันอยู่แล้ว โปรแกรมจะต้องไม่ร้องขอความเป็นเจ้าของอ็อบเจ็กต์เมื่อสร้าง (นั่นคือ ต้องส่ง bInitialOwner เป็น FALSE)

หากมี mutex อยู่แล้ว แอปพลิเคชันสามารถรับตัวระบุได้โดยใช้ฟังก์ชัน OpenMutex:

ฟังก์ชั่น OpenMutex (dwDesiredAccess: DWORD; // ตั้งค่าสิทธิ์การเข้าถึงวัตถุ bInheritHandle: BOOL; // ตั้งค่าว่าวัตถุสามารถสืบทอดได้หรือไม่ // โดยกระบวนการลูก lpName: PChar // ชื่อวัตถุ): THandle; stdcall;

ฟังก์ชันส่งคืนตัวระบุของ mutex แบบเปิดหรือ 0 ในกรณีที่มีข้อผิดพลาด mutex จะเข้าสู่สถานะสัญญาณหลังจากฟังก์ชันรอซึ่งตัวระบุถูกส่งผ่านไป หากต้องการกลับสู่สถานะที่ไม่มีสัญญาณ ให้ใช้ฟังก์ชัน ReleaseMutex:

ฟังก์ชั่น ReleaseMutex (hMutex: THandle): BOOL; stdcall;

หากมีการสื่อสารหลายกระบวนการ เช่น ผ่านไฟล์ที่แมปหน่วยความจำ แต่ละกระบวนการจะต้องมีรหัสต่อไปนี้เพื่อให้แน่ใจว่ามีการเข้าถึงทรัพยากรที่ใช้ร่วมกันอย่างถูกต้อง:

Var Mutex: THandle; // เมื่อเริ่มต้นโปรแกรม Mutex:= CreateMutex(NIL, FALSE, 'UniqueMutexName'); ถ้า Mutex = 0 ดังนั้น RaiseLastWin32Error; ... // เข้าถึงทรัพยากร WaitForSingleObject(Mutex, INFINITE); ลอง // การเข้าถึงทรัพยากรการจับ mutex ทำให้แน่ใจได้ว่า // กระบวนการอื่นที่พยายามเข้าถึง // จะหยุดที่ฟังก์ชัน WaitForSingleObject ... ในที่สุด // การทำงานกับทรัพยากรเสร็จสิ้น ให้ปล่อยมัน // สำหรับกระบวนการอื่น ReleaseMutex (มิวเท็กซ์); จบ; ... // เมื่อโปรแกรมสิ้นสุด CloseHandle(Mutex);

สะดวกในการสรุปโค้ดเช่นนี้ในคลาสที่สร้างทรัพยากรที่ได้รับการป้องกัน mutex มีคุณสมบัติและวิธีการในการทำงานบนทรัพยากร โดยปกป้องโดยใช้ฟังก์ชันการซิงโครไนซ์

แน่นอนว่า หากการทำงานกับทรัพยากรอาจใช้เวลานานพอสมควร คุณควรใช้ฟังก์ชัน MsgWaitForSingleObject หรือเรียก WaitForSingleObject ในลูปการหมดเวลาเป็นศูนย์ โดยตรวจสอบโค้ดส่งคืน มิฉะนั้น ใบสมัครของคุณจะถูกระงับ ปกป้องการรับและปล่อยออบเจ็กต์การซิงโครไนซ์เสมอด้วยการลอง ... บล็อกในที่สุด มิฉะนั้นข้อผิดพลาดขณะทำงานกับทรัพยากรจะบล็อกกระบวนการทั้งหมดที่รอการเปิดตัว

สัญญาณ (สัญญาณ)

เซมาฟอร์คือตัวนับที่มีจำนวนเต็มตั้งแต่ 0 ถึงค่าสูงสุดที่ระบุเมื่อสร้างขึ้น ตัวนับจะลดลงทุกครั้งที่เธรดเสร็จสิ้นฟังก์ชันรอที่ใช้เซมาฟอร์เสร็จเรียบร้อยแล้ว และเพิ่มขึ้นโดยการเรียกฟังก์ชันReleaseSemaphore เมื่อเซมาฟอร์ถึงค่า 0 สัญญาณจะเข้าสู่สถานะไม่มีการส่งสัญญาณ สำหรับค่าตัวนับอื่น ๆ สถานะของสัญญาณจะถูกส่งสัญญาณ ลักษณะการทำงานนี้ช่วยให้เซมาฟอร์ถูกใช้เป็นตัวจำกัดการเข้าถึงทรัพยากรที่รองรับจำนวนการเชื่อมต่อที่กำหนดไว้ล่วงหน้า

หากต้องการสร้างเซมาฟอร์ ให้ใช้ฟังก์ชัน CreateSemaphore:

ฟังก์ชั่น CreateSemaphore (lpSemaphoreAttributes: PSecurityAttributes; // ที่อยู่โครงสร้าง // TSecurityAttributes lInitialCount, // ค่าตัวนับเริ่มต้น lMaximumCount: Longint; // ค่าตัวนับสูงสุด lpName: PChar // ชื่อวัตถุ): THandle; stdcall;

ฟังก์ชันส่งคืนตัวระบุของเซมาฟอร์ที่สร้างขึ้นหรือ 0 หากไม่สามารถสร้างวัตถุได้

พารามิเตอร์ lMaximumCount ระบุค่าสูงสุดของตัวนับสัญญาณ lInitialCount ระบุค่าเริ่มต้นของตัวนับ และต้องอยู่ในช่วงตั้งแต่ 0 ถึง lMaximumCount lpNameระบุชื่อของเซมาฟอร์ หากระบบมีเซมาฟอร์ที่มีชื่อเดียวกันอยู่แล้ว ระบบจะไม่สร้างเซมาฟอร์ใหม่ แต่จะส่งคืนตัวระบุของเซมาฟอร์ที่มีอยู่ ถ้าเซมาฟอร์ถูกใช้ภายในกระบวนการเดียว คุณสามารถสร้างได้โดยไม่ต้องระบุชื่อโดยส่ง NIL เป็น lpName ชื่อเซมาฟอร์ต้องไม่เหมือนกับชื่อของเหตุการณ์ที่มีอยู่ mutex ตัวจับเวลาที่รอได้ งาน หรืออ็อบเจ็กต์การแมปไฟล์

ID ของเซมาฟอร์ที่สร้างขึ้นก่อนหน้านี้สามารถรับได้โดยฟังก์ชัน OpenSemaphore:

ฟังก์ชั่น OpenSemaphore (dwDesiredAccess: DWORD; // ตั้งค่าสิทธิ์การเข้าถึงวัตถุ bInheritHandle: BOOL; // ตั้งค่าว่าวัตถุสามารถสืบทอดได้หรือไม่ // โดยกระบวนการลูก lpName: PChar // ชื่อวัตถุ): THandle; stdcall;

พารามิเตอร์dwDesiredAccessสามารถเป็นค่าใดค่าหนึ่งต่อไปนี้:

เมื่อต้องการเพิ่มตัวนับเซมาฟอร์ ให้ใช้ฟังก์ชัน ReleaseSemaphore:

ฟังก์ชั่น ReleaseSemaphore(hSemaphore: THandle; // Semaphore identifier lReleaseCount: Longint; // ตัวนับจะเพิ่มขึ้นตามค่านี้ lpPreviousCount: Pointer // ที่อยู่ของตัวแปร 32 บิตที่ // รับค่าก่อนหน้า // ของตัวนับ) : บูล; stdcall;

หากค่าตัวนับหลังจากเรียกใช้ฟังก์ชันเกินค่าสูงสุดที่ระบุไว้โดยฟังก์ชัน CreateSemaphore ดังนั้น ReleaseSemaphore จะส่งกลับ FALSE และค่าเซมาฟอร์จะไม่เปลี่ยนแปลง เราสามารถส่ง NIL เป็นพารามิเตอร์ lpPreviousCount ได้ หากเราไม่ต้องการค่านี้

ลองพิจารณาตัวอย่างแอปพลิเคชันที่ทำงานหลายอย่างแยกกัน (เช่น โปรแกรมสำหรับการดาวน์โหลดไฟล์จากอินเทอร์เน็ตในเบื้องหลัง) หากจำนวนงานที่รันพร้อมกันมากเกินไป จะทำให้เกิดภาระที่ไม่จำเป็นบนช่องสัญญาณ ดังนั้นเราจึงใช้เธรดที่งานจะถูกดำเนินการในลักษณะที่เมื่อจำนวนเกินค่าที่กำหนดไว้ เธรดจะหยุดและรอให้งานที่รันอยู่ก่อนหน้านี้เสร็จสิ้น:

ยูนิตจำกัดด้าย; อินเทอร์เฟซใช้คลาส พิมพ์ TLimitedThread = class (TThread) ขั้นตอนดำเนินการ; แทนที่; จบ; การใช้งานใช้ Windows; ค่าคงที่ MAX_THREAD_COUNT = 10; var Semaphore: THandle; ขั้นตอน TLimitedThread.Execute; start // ลดค่าตัวนับเซมาฟอร์ หากในขณะนี้ MAX_THREAD_COUNT เธรดกำลังทำงานอยู่ ตัวนับจะเป็น 0 และเซมาฟอร์จะเป็น // อยู่ในสถานะที่ไม่มีสัญญาณ เธรดจะถูกแช่แข็งจนกว่าหนึ่งใน // ที่ทำงานก่อนหน้านี้จะเสร็จสิ้น WaitForSingleObject (สัญญาณ, INFINITE); // นี่คือโค้ดที่รับผิดชอบการทำงานของเธรด // ตัวอย่างการโหลดไฟล์... // เธรดได้เสร็จสิ้นการทำงาน เพิ่มตัวนับเซมาฟอร์ และอนุญาตให้ // เธรดอื่นเริ่มการประมวลผล ปล่อยสัญญาณ(สัญญาณ, 1, NIL); จบ; การเริ่มต้น // สร้างเซมาฟอร์เมื่อโปรแกรมเริ่มเซมาฟอร์:= CreateSemaphore(NIL, MAX_THREAD_COUNT, MAX_THREAD_COUNT, NIL); การสรุป // ทำลายเซมาฟอร์เมื่อเสร็จสิ้นโปรแกรม CloseHandle(Semaphore); จบ;

การบรรยายครั้งที่ 9 การซิงโครไนซ์กระบวนการและเธรด

1. เป้าหมายและวิธีการซิงโครไนซ์

2. กลไกการซิงโครไนซ์

1.เป้าหมายและวิธีการซิงโครไนซ์

มีเครื่องมือระบบปฏิบัติการในระดับที่ค่อนข้างกว้างซึ่งรับประกันการซิงโครไนซ์กระบวนการและเธรดร่วมกัน ความจำเป็นในการซิงโครไนซ์เธรดเกิดขึ้นเฉพาะในการเขียนโปรแกรมหลายโปรแกรมเท่านั้น ระบบปฏิบัติการและเกี่ยวข้องกับการใช้ฮาร์ดแวร์ร่วมกันและ แหล่งข้อมูลระบบคอมพิวเตอร์ การซิงโครไนซ์เป็นสิ่งจำเป็นเพื่อหลีกเลี่ยงการแข่งขันและการหยุดชะงักเมื่อแลกเปลี่ยนข้อมูลระหว่างเธรด การแชร์ข้อมูล และการเข้าถึงโปรเซสเซอร์และอุปกรณ์ I/O

ในระบบปฏิบัติการหลายระบบ เครื่องมือเหล่านี้เรียกว่าเครื่องมือสื่อสารระหว่างกระบวนการ (IPC) ซึ่งสะท้อนถึงความเป็นอันดับหนึ่งทางประวัติศาสตร์ของแนวคิดของ "กระบวนการ" ที่เกี่ยวข้องกับแนวคิดของ "เธรด" โดยทั่วไปแล้ว เครื่องมือ IPC ไม่เพียงแต่ประกอบด้วยเครื่องมือการซิงโครไนซ์ระหว่างกระบวนการเท่านั้น แต่ยังรวมถึงเครื่องมือการแลกเปลี่ยนข้อมูลระหว่างกระบวนการด้วย

การดำเนินการของเธรดในสภาพแวดล้อมแบบหลายโปรแกรมเป็นแบบอะซิงโครนัสเสมอ เป็นเรื่องยากมากที่จะพูดด้วยความมั่นใจว่าขั้นตอนการดำเนินการของกระบวนการจะเป็นอย่างไร ณ จุดใดจุดหนึ่ง แม้จะอยู่ในโหมดโปรแกรมเดียว ก็ไม่สามารถประมาณเวลาที่ใช้ในการทำงานให้สำเร็จได้อย่างแม่นยำเสมอไป ในหลายกรณีครั้งนี้ขึ้นอยู่กับค่าของข้อมูลต้นฉบับอย่างมีนัยสำคัญ ซึ่งส่งผลต่อจำนวนรอบ ทิศทางของการแตกแขนงโปรแกรม เวลาดำเนินการของการดำเนินการ I/O ฯลฯ เนื่องจากข้อมูลต้นฉบับในเวลาที่ต่างกันเมื่อทำงาน การเปิดตัวอาจแตกต่างกัน ดังนั้นเวลาในการดำเนินการแต่ละขั้นตอนและงานโดยรวมจึงเป็นค่าที่ไม่แน่นอนอย่างมาก


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

ดังนั้นในกรณีทั่วไป เธรด (เมื่อโปรแกรมเมอร์ไม่ได้ใช้มาตรการพิเศษในการซิงโครไนซ์) จะไหลอย่างอิสระแบบอะซิงโครนัสระหว่างกัน สิ่งนี้เป็นจริงทั้งสำหรับเธรดในกระบวนการเดียวกันที่รันโค้ดโปรแกรมทั่วไป และสำหรับเธรดในกระบวนการที่แตกต่างกัน แต่ละเธรดจะรันโปรแกรมของตัวเอง

การโต้ตอบระหว่างกระบวนการหรือเธรดใด ๆ ที่เกี่ยวข้องกับกระบวนการหรือเธรด การซิงโครไนซ์,ซึ่งประกอบไปด้วยการประสานความเร็วโดยระงับการไหลจนกว่าจะเกิดเหตุการณ์บางอย่างขึ้นแล้วจึงเปิดใช้งานเมื่อเหตุการณ์นี้เกิดขึ้น การซิงโครไนซ์เป็นแกนหลักของการโต้ตอบกับเธรด ไม่ว่าจะเกี่ยวข้องกับการแชร์ทรัพยากรหรือการแลกเปลี่ยนข้อมูล ตัวอย่างเช่น เธรดที่ได้รับควรเข้าถึงข้อมูลหลังจากที่เธรดที่ส่งบัฟเฟอร์แล้วเท่านั้น หากเธรดที่ได้รับเข้าถึงข้อมูลก่อนที่จะเข้าสู่บัฟเฟอร์ จะต้องถูกระงับ

เมื่อแบ่งปันทรัพยากรฮาร์ดแวร์ การซิงโครไนซ์ก็เป็นสิ่งจำเป็นอย่างยิ่งเช่นกัน ตัวอย่างเช่น เมื่อเธรดที่ใช้งานอยู่จำเป็นต้องเข้าถึงพอร์ตอนุกรม และเธรดอื่นในโหมดเอกสิทธิ์เฉพาะบุคคลกำลังทำงานกับพอร์ตนี้ ช่วงเวลานี้ในสถานะรอระบบปฏิบัติการจะระงับเธรดที่ใช้งานอยู่และจะไม่เปิดใช้งานจนกว่าพอร์ตที่ต้องการจะว่าง การซิงโครไนซ์กับเหตุการณ์ภายนอกระบบคอมพิวเตอร์ เช่น ปฏิกิริยาต่อการกดคีย์ผสม Ctrl+C ก็มักจำเป็นเช่นกัน

ทุก ๆ วินาที มีเหตุการณ์หลายร้อยเหตุการณ์เกิดขึ้นในระบบที่เกี่ยวข้องกับการจัดสรรและปล่อยทรัพยากร และระบบปฏิบัติการต้องมีวิธีการที่เชื่อถือได้และมีประสิทธิภาพที่จะอนุญาตให้ซิงโครไนซ์เธรดกับเหตุการณ์ที่เกิดขึ้นในระบบ

เพื่อซิงโครไนซ์เธรด แอพพลิเคชั่นโปรแกรมเมอร์สามารถใช้ได้ทั้งสองอย่าง เงินทุนของตัวเองทั้งเทคนิคการซิงโครไนซ์และเครื่องมือระบบปฏิบัติการ ตัวอย่างเช่น สองเธรดของกระบวนการแอปพลิเคชันเดียวกันสามารถประสานงานงานของตนโดยใช้ตัวแปรบูลีนส่วนกลางที่มีให้ทั้งสองเธรด ซึ่งจะถูกตั้งค่าเป็นเธรดหนึ่งเมื่อมีเหตุการณ์บางอย่างเกิดขึ้น ตัวอย่างเช่น เธรดหนึ่งจะสร้างข้อมูลที่จำเป็นสำหรับอีกเธรดหนึ่งเพื่อให้ทำงานต่อไปได้ อย่างไรก็ตาม ในหลายกรณี สิ่งอำนวยความสะดวกการซิงโครไนซ์ที่ระบบปฏิบัติการมอบให้ในรูปแบบของการเรียกของระบบนั้นมีประสิทธิภาพมากกว่า หรือแม้กระทั่งสิ่งที่เป็นไปได้เท่านั้น ดังนั้นเธรดที่เป็นของกระบวนการที่แตกต่างกันจึงไม่สามารถรบกวนการทำงานของกันและกันในทางใดทางหนึ่ง หากปราศจากการไกล่เกลี่ยระบบปฏิบัติการ พวกเขาจะไม่สามารถระงับกันและกันหรือแจ้งให้กันและกันทราบเกี่ยวกับเหตุการณ์ที่เกิดขึ้นได้ ระบบปฏิบัติการใช้เครื่องมือการซิงโครไนซ์ไม่เพียง แต่จะซิงโครไนซ์กระบวนการแอปพลิเคชันเท่านั้น แต่ยังรวมถึงความต้องการภายในด้วย

โดยทั่วไปแล้ว นักพัฒนาระบบปฏิบัติการจะจัดเตรียมเครื่องมือการซิงโครไนซ์ที่หลากหลายไว้คอยบริการแก่แอปพลิเคชันและโปรแกรมเมอร์ระบบ เครื่องมือเหล่านี้สามารถสร้างลำดับชั้นได้ เมื่อเครื่องมือที่ซับซ้อนมากขึ้นถูกสร้างขึ้นบนพื้นฐานของเครื่องมือที่เรียบง่ายกว่า และยังสามารถใช้งานเฉพาะทางได้ เช่น เครื่องมือสำหรับการซิงโครไนซ์เธรดของกระบวนการเดียว เครื่องมือสำหรับการซิงโครไนซ์เธรด กระบวนการที่แตกต่างกันเมื่อมีการแลกเปลี่ยนข้อมูล เป็นต้น บ่อยครั้ง ฟังก์ชั่นการเรียกระบบการซิงโครไนซ์ที่แตกต่างกันซ้อนทับกัน เพื่อให้โปรแกรมเมอร์สามารถใช้การเรียกหลายครั้งเพื่อแก้ไขปัญหาเดียว ขึ้นอยู่กับความชอบส่วนตัวของเขา


ความจำเป็นในการซิงโครไนซ์และการแข่งขัน

การละเลยปัญหาการซิงโครไนซ์ในระบบแบบมัลติเธรดอาจนำไปสู่การแก้ปัญหาที่ไม่ถูกต้องหรือแม้กระทั่งระบบล่ม พิจารณาตัวอย่างเช่น (รูปที่ 4.16) งานดูแลรักษาฐานข้อมูลของลูกค้าขององค์กรบางแห่ง ลูกค้าแต่ละรายได้รับการกำหนดบันทึกแยกต่างหากในฐานข้อมูล ซึ่งประกอบด้วยฟิลด์คำสั่งซื้อและการชำระเงิน นอกเหนือจากฟิลด์อื่นๆ โปรแกรมที่ดูแลรักษาฐานข้อมูลได้รับการออกแบบให้เป็นกระบวนการเดียวที่มีหลายเธรด รวมถึงเธรด A ซึ่งป้อนข้อมูลเกี่ยวกับคำสั่งซื้อที่ได้รับจากลูกค้าลงในฐานข้อมูล และเธรด B ซึ่งบันทึกข้อมูลฐานข้อมูลเกี่ยวกับการชำระเงินของลูกค้าสำหรับใบแจ้งหนี้ เธรดทั้งสองนี้ทำงานร่วมกันบนไฟล์ฐานข้อมูลทั่วไปโดยใช้อัลกอริธึมเดียวกัน ซึ่งมีสามขั้นตอน

2. ป้อนค่าใหม่ในฟิลด์คำสั่งซื้อ (สำหรับขั้นตอน A) หรือการชำระเงิน (สำหรับขั้นตอน B)

3. ส่งคืนบันทึกที่แก้ไขไปยังไฟล์ฐานข้อมูล

https://pandia.ru/text/78/239/images/image002_238.gif" width="505" height="374 src=">

ข้าว. 4.17.อิทธิพลของความเร็วการไหลสัมพัทธ์ต่อผลลัพธ์ของการแก้ปัญหา

ส่วนที่สำคัญ

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

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

วิธีที่ง่ายที่สุดและในเวลาเดียวกันที่ไม่มีประสิทธิภาพมากที่สุดเพื่อให้แน่ใจว่ามีการแยกร่วมกันคือให้ระบบปฏิบัติการอนุญาตให้เธรดปิดใช้งานการขัดจังหวะใดๆ ขณะที่อยู่ในส่วนวิกฤติ อย่างไรก็ตามวิธีนี้ไม่ได้ใช้งานจริงเนื่องจากการเชื่อถือเธรดผู้ใช้เพื่อควบคุมระบบเป็นอันตราย - มันสามารถครอบครองโปรเซสเซอร์เป็นเวลานานและหากเธรดขัดข้องในส่วนที่สำคัญระบบทั้งหมดจะพังเนื่องจาก การหยุดชะงักจะไม่ได้รับอนุญาต

2. กลไกการซิงโครไนซ์

การบล็อกตัวแปร

ในการซิงโครไนซ์เธรดของกระบวนการเดียว โปรแกรมเมอร์แอปพลิเคชันสามารถใช้ global ได้ การปิดกั้นตัวแปรโปรแกรมเมอร์ทำงานร่วมกับตัวแปรเหล่านี้ ซึ่งเธรดทั้งหมดของกระบวนการสามารถเข้าถึงได้โดยตรง โดยไม่ต้องใช้การเรียกของระบบ OS

ซึ่งจะปิดการใช้งานการขัดจังหวะตลอดการดำเนินการตรวจสอบและการติดตั้งทั้งหมด

การใช้การแยกออกร่วมกันในลักษณะที่อธิบายไว้ข้างต้นมีข้อเสียเปรียบที่สำคัญ: ในระหว่างเวลาที่เธรดหนึ่งอยู่ในส่วนที่สำคัญ เธรดอื่นที่ต้องการทรัพยากรเดียวกัน โดยมีสิทธิ์เข้าถึงโปรเซสเซอร์ จะสำรวจตัวแปรการบล็อกอย่างต่อเนื่อง ทำให้เสียเวลาของโปรเซสเซอร์ จัดสรรให้กับมัน ซึ่งสามารถนำไปใช้เพื่อรันเธรดอื่นได้ เพื่อกำจัดข้อเสียเปรียบนี้ ระบบปฏิบัติการหลายระบบจึงจัดให้มีการเรียกระบบพิเศษสำหรับการทำงานในส่วนที่สำคัญ

ในรูป รูปที่ 4.19 แสดงให้เห็นว่าฟังก์ชันเหล่านี้ใช้การยกเว้นร่วมกันในระบบปฏิบัติการ Windows NT อย่างไร ก่อนที่จะเริ่มแก้ไขข้อมูลสำคัญ เธรดจะทำการเรียกระบบ EnterCriticalSection() การเรียกนี้จะดำเนินการก่อน ดังเช่นในกรณีก่อนหน้านี้ การตรวจสอบตัวแปรการบล็อกที่สะท้อนถึงสถานะของทรัพยากรที่สำคัญ หากการเรียกของระบบกำหนดว่ารีซอร์สไม่ว่าง (F(D) = 0) ไม่เหมือนกับกรณีก่อนหน้านี้ จะไม่ทำการโพลแบบวนรอบ แต่จะทำให้เธรดอยู่ในสถานะรอ (D) และจดบันทึกว่า กระแสนี้จะต้องเปิดใช้งานเมื่อทรัพยากรที่เกี่ยวข้องพร้อมใช้งาน เธรดที่ใช้อยู่ในปัจจุบัน ทรัพยากรนี้หลังจากออกจากส่วนวิกฤติแล้ว จะต้องดำเนินการฟังก์ชันระบบ LeaveCriticalSectionO ซึ่งเป็นผลมาจากการที่ตัวแปรการบล็อกรับค่าที่สอดคล้องกับสถานะว่างของทรัพยากร (F(D) = 1) และระบบปฏิบัติการจะตรวจสอบผ่านคิว ของเธรดที่รอทรัพยากรนี้ และถ่ายโอนเธรดแรกจากคิวไปยังสถานะความพร้อม

ต้นทุนค่าโสหุ้ย" href="/text/category/nakladnie_rashodi/" rel="bookmark">ต้นทุนค่าโสหุ้ยระบบปฏิบัติการสำหรับการใช้ฟังก์ชันการเข้าและออกจากส่วนสำคัญอาจเกินกว่าเงินออมที่ได้รับ

เซมาฟอร์ส

ลักษณะทั่วไปของตัวแปรการบล็อกเรียกว่า สัญญาณ Dijkstraแทนที่จะเป็นตัวแปรไบนารี่ Dijkstra เสนอให้ใช้ตัวแปรที่สามารถรับค่าจำนวนเต็มที่ไม่เป็นลบได้ ตัวแปรดังกล่าวที่ใช้ในการซิงโครไนซ์กระบวนการคำนวณเรียกว่าเซมาฟอร์

ในการทำงานกับเซมาฟอร์ จะมีการแนะนำไพรมิทีฟสองตัว ซึ่งแต่เดิมหมายถึง P และ V โดยให้ตัวแปร S เป็นตัวแทนของเซมาฟอร์ จากนั้นการกระทำ V(S) และ P(S) จะถูกกำหนดดังนี้

* V(S): ตัวแปร S เพิ่มขึ้น 1 เป็นการกระทำครั้งเดียว การสุ่มตัวอย่าง การสร้าง และการจัดเก็บไม่สามารถหยุดชะงักได้ เธรดอื่นไม่สามารถเข้าถึงตัวแปร S ในขณะที่ดำเนินการนี้

* P(S): ลด S ลง 1 ถ้าเป็นไปได้ หาก 5=0 และเป็นไปไม่ได้ที่จะลด S ขณะที่ยังคงอยู่ในขอบเขตของค่าจำนวนเต็มที่ไม่เป็นลบ ดังนั้นการดำเนินการเรียกเธรด P จะรอจนกว่าการลดนี้จะเป็นไปได้ การตรวจสอบและลดผลสำเร็จถือเป็นการดำเนินการที่แบ่งแยกไม่ได้เช่นกัน

ไม่อนุญาตให้มีการขัดจังหวะระหว่างการดำเนินการของ V และ P primitives

ในกรณีพิเศษที่เซมาฟอร์ S สามารถรับได้เฉพาะค่า 0 และ 1 เท่านั้น มันจะกลายเป็นตัวแปรการบล็อก ซึ่งด้วยเหตุนี้จึงมักเรียกว่าเซมาฟอร์ไบนารี กิจกรรม P มีศักยภาพที่จะทำให้เธรดที่กำลังดำเนินการอยู่ในสถานะรอ ในขณะที่กิจกรรม V อาจปลุกเธรดอื่นที่ถูกระงับโดยกิจกรรม P ในบางกรณี ในบางกรณี

มาดูการใช้เซมาฟอร์โดยใช้ตัวอย่างคลาสสิกของการโต้ตอบของสองเธรดที่ทำงานในโหมดมัลติโปรแกรม ซึ่งหนึ่งในนั้นเขียนข้อมูลลงในบัฟเฟอร์พูล และอีกอันอ่านจากบัฟเฟอร์พูล ให้บัฟเฟอร์พูลประกอบด้วยบัฟเฟอร์ N ซึ่งแต่ละบัฟเฟอร์สามารถมีได้หนึ่งรายการ โดยทั่วไป เธรดตัวเขียนและเธรดตัวอ่านสามารถมีความเร็วที่แตกต่างกัน และเข้าถึงพูลบัฟเฟอร์ด้วยความเข้มข้นที่แตกต่างกัน ในช่วงเวลาหนึ่ง ความเร็วในการเขียนอาจเกินความเร็วในการอ่าน ในอีกช่วงเวลาหนึ่ง - ในทางกลับกัน สำหรับด้านขวา การทำงานร่วมกันเธรดตัวเขียนต้องหยุดชั่วคราวเมื่อบัฟเฟอร์ทั้งหมดไม่ว่าง และตื่นขึ้นเมื่อมีการว่างบัฟเฟอร์อย่างน้อยหนึ่งตัว ในทางตรงกันข้าม เธรดตัวอ่านควรหยุดชั่วคราวเมื่อบัฟเฟอร์ทั้งหมดว่างเปล่า และกลับมาทำงานอีกครั้งเมื่อมีการเขียนอย่างน้อยหนึ่งรายการปรากฏขึ้น

ขอแนะนำสองเซมาฟอร์: e - จำนวนบัฟเฟอร์ว่าง และ f - จำนวนบัฟเฟอร์ที่เติม และในสถานะเริ่มต้น e = N, a f = 0 จากนั้น การทำงานของเธรดที่มีพูลบัฟเฟอร์ทั่วไปสามารถอธิบายได้ดังต่อไปนี้ (รูปที่ 4.20)

เธรดตัวเขียนดำเนินการดำเนินการ P(e) ก่อน โดยจะตรวจสอบว่ามีบัฟเฟอร์ว่างในพูลบัฟเฟอร์หรือไม่ ตามความหมายของการดำเนินการ P หากเซมาฟอร์ e เท่ากับ 0 (นั่นคือ ไม่มีบัฟเฟอร์ว่างในขณะนี้) เธรดตัวเขียนจะเข้าสู่สถานะรอ หากค่าของ e เป็นจำนวนบวก มันจะลดจำนวนบัฟเฟอร์ที่ว่าง เขียนข้อมูลไปยังบัฟเฟอร์ที่ว่างถัดไป จากนั้นจึงเพิ่มจำนวนบัฟเฟอร์ที่ถูกครอบครองด้วยการดำเนินการ V(f) เธรดตัวอ่านทำงานในลักษณะเดียวกัน โดยมีความแตกต่างที่เริ่มต้นด้วยการตรวจสอบบัฟเฟอร์เต็ม และหลังจากอ่านข้อมูลแล้ว เธรดตัวอ่านจะเพิ่มจำนวนบัฟเฟอร์ที่ว่าง

DIV_ADBLOCK860">

เซมาฟอร์ยังสามารถใช้เป็นตัวแปรการบล็อกได้ ในตัวอย่างที่กล่าวถึงข้างต้น เพื่อกำจัดการชนกันเมื่อทำงานกับพื้นที่หน่วยความจำที่ใช้ร่วมกัน เราจะถือว่าการเขียนและการอ่านจากบัฟเฟอร์เป็นส่วนที่สำคัญ เราจะตรวจสอบให้แน่ใจว่ามีการยกเว้นร่วมกันโดยใช้สัญญาณไบนารี b (รูปที่ 4.21) หลังจากตรวจสอบความพร้อมใช้งานของบัฟเฟอร์แล้ว ทั้งสองเธรดจะต้องตรวจสอบความพร้อมใช้งานของส่วนที่สำคัญ

https://pandia.ru/text/78/239/images/image007_110.jpg" width="495" height="639 src=">

ข้าว. 4.22.การเกิดการหยุดชะงักระหว่างการทำงานของโปรแกรม

บันทึก

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

ในตัวอย่างที่พิจารณา การหยุดชะงักเกิดขึ้นจากสองเธรด แต่มีเธรดจำนวนมากขึ้นที่สามารถบล็อกซึ่งกันและกันได้ ในรูป รูปที่ 2.23 แสดงการกระจายทรัพยากร Ri ระหว่างหลายเธรด Tj ซึ่งนำไปสู่การเกิดการหยุดชะงัก ลูกศรระบุความต้องการทรัพยากรของโฟลว์ ลูกศรทึบหมายความว่าทรัพยากรที่เกี่ยวข้องได้รับการจัดสรรให้กับเธรดแล้ว และลูกศรประเชื่อมต่อเธรดกับทรัพยากรที่จำเป็น แต่ยังไม่สามารถจัดสรรได้เนื่องจากถูกครอบครองโดยเธรดอื่น ตัวอย่างเช่น เธรด T1 ต้องการทรัพยากร R1 และ R2 เพื่อทำงาน โดยมีเพียงทรัพยากรเดียวเท่านั้นที่ได้รับการจัดสรร - R1 และทรัพยากร R2 ถูกเก็บไว้โดยเธรด T2 ไม่มีเธรดทั้งสี่ที่แสดงในรูปภาพที่สามารถทำงานต่อได้เนื่องจากไม่มีทรัพยากรทั้งหมดที่จำเป็นสำหรับสิ่งนี้

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

เจ้าของ" href="/text/category/vladeetc/" rel="bookmark">เจ้าของ ตั้งค่าเป็นสถานะที่ไม่ได้ส่งสัญญาณ และเข้าสู่ส่วนที่สำคัญ หลังจากที่เธรดทำงานกับข้อมูลที่สำคัญเสร็จสิ้นแล้ว มัน "ให้ up” mutex โดยตั้งค่าให้อยู่ในสถานะ signaled ในขณะนี้ mutex นั้นว่างและไม่ได้เป็นของ thread ใด ๆ หาก thread ใดกำลังรอให้มันถูก release ก็จะกลายเป็นเจ้าของ mutex คนต่อไปที่ ในเวลาเดียวกัน mutex จะเข้าสู่สถานะที่ไม่มีสัญญาณ

วัตถุเหตุการณ์ (ในกรณีนี้คำว่า "เหตุการณ์" ถูกใช้ในความหมายแคบเป็นการกำหนด ประเภทเฉพาะออบเจ็กต์การซิงโครไนซ์) โดยทั่วไปจะใช้ไม่เข้าถึงข้อมูล แต่เพื่อแจ้งเธรดอื่น ๆ ว่าการดำเนินการบางอย่างได้เสร็จสิ้นแล้ว ตัวอย่างเช่น ในบางแอปพลิเคชัน งานจะถูกจัดระเบียบในลักษณะที่เธรดหนึ่งอ่านข้อมูลจากไฟล์ไปยังบัฟเฟอร์หน่วยความจำ และเธรดอื่นประมวลผลข้อมูลนี้ จากนั้นเธรดแรกจะอ่านส่วนใหม่ของข้อมูล และเธรดอื่น ๆ ประมวลผลอีกครั้ง และอื่นๆ เมื่อเริ่มต้นการดำเนินการ เธรดแรกจะตั้งค่าออบเจ็กต์เหตุการณ์ให้เป็นสถานะที่ไม่มีสัญญาณ เธรดอื่นๆ ทั้งหมดได้ทำการเรียกไปยัง Wait(X) โดยที่ X เป็นตัวชี้เหตุการณ์ และอยู่ในสถานะหยุดชั่วคราว รอให้เหตุการณ์นั้นเกิดขึ้น ทันทีที่บัฟเฟอร์เต็ม เธรดแรกจะรายงานสิ่งนี้ไปยังระบบปฏิบัติการโดยการเรียก Set(X) ระบบปฏิบัติการสแกนคิวของเธรดที่รอ และเปิดใช้งานเธรดใดๆ ที่กำลังรอเหตุการณ์นี้

สัญญาณ

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

ตัวอย่างของสัญญาณอะซิงโครนัสคือสัญญาณจากเทอร์มินัล ระบบปฏิบัติการหลายระบบจัดให้มีการลบกระบวนการออกจากการดำเนินการทันที ในการดำเนินการนี้ ผู้ใช้สามารถกดคีย์ผสมบางคีย์ (Ctrl+C, Ctrl+Break) ซึ่งเป็นผลมาจากการที่ระบบปฏิบัติการสร้างสัญญาณและส่งไปยังกระบวนการที่ใช้งานอยู่ สัญญาณสามารถมาถึงได้ตลอดเวลาระหว่างการดำเนินการของกระบวนการ (นั่นคือ เป็นแบบอะซิงโครนัส) ซึ่งกำหนดให้กระบวนการยุติทันที ในกรณีนี้ การตอบสนองต่อสัญญาณคือการทำให้กระบวนการเสร็จสมบูรณ์โดยไม่มีเงื่อนไข

สามารถกำหนดชุดสัญญาณในระบบได้ รหัสโปรแกรมของกระบวนการที่รับสัญญาณสามารถละเว้นหรือตอบสนองด้วยการดำเนินการมาตรฐาน (เช่น ออก) หรือดำเนินการเฉพาะที่กำหนดโดยโปรแกรมเมอร์แอปพลิเคชัน ในกรณีหลังนี้ จำเป็นต้องจัดให้มีการเรียกระบบพิเศษในโค้ดโปรแกรม โดยให้ระบบปฏิบัติการทราบว่าควรดำเนินการขั้นตอนใดเพื่อตอบสนองต่อการรับสัญญาณเฉพาะ

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

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

ออบเจ็กต์การซิงโครไนซ์นี้สามารถใช้ได้เฉพาะภายในกระบวนการที่สร้างขึ้นเท่านั้น อ็อบเจ็กต์ที่เหลือสามารถใช้เพื่อซิงโครไนซ์เธรดของกระบวนการต่างๆ ชื่อของวัตถุ "ส่วนวิกฤต" เกี่ยวข้องกับการเลือกนามธรรมบางส่วนของโค้ดโปรแกรม (ส่วน) ที่ดำเนินการบางอย่างซึ่งไม่สามารถละเมิดลำดับได้ นั่นคือความพยายามของสองเธรดที่แตกต่างกันในการรันโค้ดของส่วนนี้พร้อมกันจะส่งผลให้เกิดข้อผิดพลาด

ตัวอย่างเช่น การปกป้องฟังก์ชันตัวเขียนด้วยส่วนดังกล่าวอาจสะดวก เนื่องจากควรป้องกันการเข้าถึงพร้อมกันโดยผู้เขียนหลายคน

สำหรับส่วนสำคัญ จะมีการแนะนำการดำเนินการสองประการ:

เข้าสู่ส่วน;ตราบใดที่เธรดใดๆ อยู่ในส่วนวิกฤติ เธรดอื่นๆ ทั้งหมดที่พยายามป้อนจะหยุดและรอโดยอัตโนมัติ เธรดที่เข้ามาในส่วนนี้แล้วสามารถเข้าได้หลายครั้งโดยไม่ต้องรอให้ปล่อยออกมา

ออกจากส่วน;เมื่อเธรดออกจากส่วน การนับจำนวนครั้งที่เธรดเข้าสู่ส่วนจะลดลง เพื่อให้ส่วนนั้นว่างสำหรับเธรดอื่นก็ต่อเมื่อเธรดออกจากส่วนหลายครั้งตามที่เข้ามา เมื่อส่วนสำคัญถูกปล่อยออกมา จะมีเพียงเธรดเดียวเท่านั้นที่จะถูกปลุก เพื่อรอการอนุญาตเพื่อเข้าสู่ส่วนนั้น

โดยทั่วไปแล้ว ใน API ที่ไม่ใช่ Win32 อื่นๆ (เช่น OS/2) ส่วนที่สำคัญจะไม่ถือเป็นออบเจ็กต์การซิงโครไนซ์ แต่เป็นส่วนของโค้ดโปรแกรมที่สามารถดำเนินการได้ด้วยเธรดแอปพลิเคชันเดียวเท่านั้น นั่นคือการเข้าสู่ส่วนที่สำคัญถือเป็นการปิดกลไกการสลับเธรดชั่วคราวจนกว่าจะออกจากส่วนนี้ ใน Win32 API ส่วนที่สำคัญจะถือเป็นออบเจ็กต์ ซึ่งทำให้เกิดความสับสน - มีคุณสมบัติคล้ายกันมากกับออบเจ็กต์พิเศษที่ไม่มีชื่อ ( มิวเท็กซ์, ดูด้านล่าง)

เมื่อใช้ส่วนที่มีความสำคัญ คุณต้องแน่ใจว่าไม่มีการจัดสรรส่วนของโค้ดที่ใหญ่เกินไปให้กับส่วนนั้น เนื่องจากอาจทำให้เกิดความล่าช้าอย่างมากในการดำเนินการของเธรดอื่น

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

มีหลายกรณีของการใช้ส่วนสำคัญอย่างมีประสิทธิภาพ:

ผู้อ่านไม่ขัดแย้งกับนักเขียน (เฉพาะนักเขียนเท่านั้นที่ต้องได้รับการปกป้อง)

เธรดทั้งหมดมีสิทธิ์การเข้าถึงที่เท่าเทียมกันโดยประมาณ (ตัวอย่างเช่น ไม่สามารถแยกแยะผู้เขียนและผู้อ่านที่บริสุทธิ์ได้)

เมื่อสร้างออบเจ็กต์การซิงโครไนซ์แบบคอมโพสิตซึ่งประกอบด้วยวัตถุมาตรฐานหลายรายการ เพื่อป้องกันการดำเนินการตามลำดับบนออบเจ็กต์คอมโพสิต

ระบบปฏิบัติการ Windows รองรับออบเจ็กต์การซิงโครไนซ์สี่ประเภท

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

ควรสังเกตว่าการซิงโครไนซ์เธรดโดยใช้ส่วนสำคัญสามารถดำเนินการได้ภายในกระบวนการเดียวเท่านั้น เนื่องจากเป็นไปไม่ได้ที่จะถ่ายโอนที่อยู่ของส่วนสำคัญจากกระบวนการหนึ่งไปยังอีกกระบวนการหนึ่ง เนื่องจากส่วนวิกฤติอยู่ในพื้นที่ตัวแปรส่วนกลางของ ​กระบวนการทำงาน

เซมาฟอร์ถูกสร้างขึ้นโดยใช้ฟังก์ชัน CreateSemaphore จำนวนงานที่สามารถเข้าถึงทรัพยากรบางอย่างพร้อมกันนั้นถูกกำหนดโดยพารามิเตอร์ฟังก์ชันตัวใดตัวหนึ่ง หากค่าของฟังก์ชันนี้คือ 1 แสดงว่าเซมาฟอร์ทำหน้าที่เป็นเซมาฟอร์พิเศษ ที่ การสร้างที่ประสบความสำเร็จเซมาฟอร์ส่งคืนหมายเลขอ้างอิง มิฉะนั้นจะเป็นโมฆะ ฟังก์ชันWaitForSingleObjectมีโหมดรอสัญญาณ พารามิเตอร์ตัวใดตัวหนึ่งระบุเวลารอเป็นมิลลิวินาที หากค่าของพารามิเตอร์นี้เป็น INFINITE แสดงว่าการหมดเวลานั้นไม่มีกำหนด เมื่อฟังก์ชันเสร็จสมบูรณ์ ค่าของตัวนับที่เกี่ยวข้องกับเซมาฟอร์จะลดลงมากกว่าหนึ่งค่า ฟังก์ชัน ReleaseSemaphore() จะปล่อยเซมาฟอร์ เพื่อให้เธรดอื่นใช้งานได้

เซมาฟอร์ mutex พิเศษถูกสร้างขึ้นโดยใช้ฟังก์ชัน CreateMutex() ซึ่งจะส่งคืนตัวระบุของวัตถุที่สร้างขึ้นหรือค่าว่างเมื่อมีข้อผิดพลาด หากจำเป็น วัตถุจะถูกปล่อยโดยใช้ฟังก์ชันทั่วไป CloseHandle() เมื่อทราบชื่อของออบเจ็กต์ mutex คุณสามารถเปิดได้โดยใช้ฟังก์ชัน OpenMutex() ด้วยฟังก์ชันนี้ หลายเธรดสามารถเปิดออบเจ็กต์เดียวกันแล้วรอพร้อมกันได้ เมื่อชื่อของวัตถุเป็นที่รู้จักในเธรด สามารถรับได้โดยใช้ฟังก์ชัน WaitForSingleObject หรือ WaitForMultipleObjects วัตถุ mutex ถูกปล่อยออกมาโดยใช้ฟังก์ชัน ReleaseMutex()

เหตุการณ์ถูกสร้างขึ้นโดยใช้ฟังก์ชัน CreateEvent มันจะส่งคืนหมายเลขอ้างอิงให้กับเหตุการณ์ที่สร้างขึ้น หรือเป็นโมฆะหากล้มเหลว หลังจากสร้างเหตุการณ์แล้ว เธรดจะรอให้เกิดเหตุการณ์โดยใช้ฟังก์ชัน WaitForSingleObject โดยระบุหมายเลขอ้างอิงของเหตุการณ์นี้เป็นพารามิเตอร์แรก ดังนั้น การดำเนินการเธรดจึงถูกระงับจนกว่าเหตุการณ์ที่เกี่ยวข้องจะเกิดขึ้น หลังจากเรียกฟังก์ชัน SetEvent กระบวนการที่กำลังรอเหตุการณ์นี้โดยใช้ฟังก์ชัน WaitForSingleObject จะดำเนินการต่อไป เหตุการณ์สามารถรีเซ็ตได้โดยใช้ฟังก์ชัน ResetEvent

เธรดสามารถอยู่ในสถานะใดสถานะหนึ่งได้:

    พร้อม(พร้อม) – อยู่ในกลุ่มเธรดที่รอการดำเนินการ

    วิ่ง(การดำเนินการ) - ทำงานบนโปรเซสเซอร์

    ซึ่งรอคอย(กำลังรอ) เรียกอีกอย่างว่าไม่ได้ใช้งานหรือถูกระงับ ถูกระงับ - อยู่ในสถานะรอซึ่งลงท้ายด้วยเธรดที่เริ่มดำเนินการ (สถานะกำลังทำงาน) หรือเข้าสู่สถานะ พร้อม;

    สิ้นสุด (เสร็จสิ้น) - การดำเนินการคำสั่งเธรดทั้งหมดเสร็จสมบูรณ์ สามารถลบออกได้ในภายหลัง หากสตรีมไม่ถูกลบ ระบบสามารถรีเซ็ตเป็นสถานะดั้งเดิมเพื่อใช้ในภายหลังได้

การซิงโครไนซ์เธรด

การรันเธรดมักจำเป็นต้องสื่อสารไม่ทางใดก็ทางหนึ่ง ตัวอย่างเช่น หากหลายเธรดพยายามเข้าถึงข้อมูลส่วนกลาง แต่ละเธรดจะต้องปกป้องข้อมูลไม่ให้ถูกเปลี่ยนแปลงโดยเธรดอื่น บางครั้งเธรดหนึ่งจำเป็นต้องรู้ว่าเมื่อเธรดอื่นจะทำงานเสร็จเมื่อใด การโต้ตอบดังกล่าวจำเป็นระหว่างเธรดของกระบวนการเดียวกันและกระบวนการที่แตกต่างกัน

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

ใน Win32 มีหลายวิธีในการซิงโครไนซ์เธรด มันเกิดขึ้นว่าในสถานการณ์เฉพาะวิธีหนึ่งดีกว่าวิธีอื่น ลองมาดูวิธีการเหล่านี้อย่างรวดเร็ว

ส่วนที่สำคัญ

วิธีหนึ่งในการซิงโครไนซ์เธรดคือการใช้ส่วนที่สำคัญ นี่เป็นวิธีการซิงโครไนซ์เธรดเดียวที่ไม่ต้องใช้เคอร์เนล Windows (ส่วนสำคัญไม่ใช่วัตถุเคอร์เนล) อย่างไรก็ตาม วิธีการนี้สามารถใช้เพื่อซิงโครไนซ์เธรดของกระบวนการเดียวเท่านั้น

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

ก่อนที่จะใช้ส่วนที่สำคัญ คุณต้องเริ่มต้นโดยใช้ขั้นตอน Win32 API InitializeCriticalSection() ซึ่งกำหนดไว้ (ใน Delphi) ดังนี้:

ขั้นตอน InitializeCriticalSection (var IpCriticalSection: TRTLCriticalSection); stdcall;

พารามิเตอร์ IpCriticalSection คือเรกคอร์ดประเภท TRTLCriticalSection ที่ถูกส่งผ่านโดยการอ้างอิง คำจำกัดความที่แท้จริงของรายการ TRTLCriticalSection นั้นไม่สำคัญมากนัก เนื่องจากคุณไม่จำเป็นต้องดูเนื้อหาในรายการเลย สิ่งที่คุณต้องทำคือส่งรายการที่ไม่ได้เตรียมใช้งานไปยังพารามิเตอร์ IpCtitical Section และรายการนี้จะถูกเติมตามขั้นตอนทันที

หลังจากกรอกรายการในโปรแกรมแล้ว คุณสามารถสร้างส่วนสำคัญได้โดยการวางข้อความบางส่วนไว้ระหว่างการเรียกฟังก์ชัน EnterCriticalSection() และ LeaveCriticalSection() ขั้นตอนเหล่านี้ถูกกำหนดไว้ดังนี้:

ขั้นตอน EnterCriticalSection (var IpCriticalSection: TRTLCriticalSection); stdcall;

ขั้นตอน LeaveCriticalSection (var IpCriticalSection: TRTLCriticalSection); stdcall;

พารามิเตอร์ IpCriticalSection ที่ถูกส่งผ่านไปยังขั้นตอนเหล่านี้ไม่มีอะไรมากไปกว่ารายการที่สร้างขึ้นโดยขั้นตอน InitializeCriticalSection()

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

เมื่อทำหน้าที่ ออกจากCriticalSectionเรียกโดยเธรดที่ขณะนี้ได้รับอนุญาตให้ดำเนินการส่วนสำคัญของโค้ดที่เกี่ยวข้องกับออบเจ็กต์ส่วนวิกฤติที่กำหนด ระบบสามารถตรวจสอบเพื่อดูว่ามีเธรดอื่นในคิวที่รอให้วัตถุนั้นถูกปล่อยออกมาหรือไม่ จากนั้นระบบสามารถลบเธรดที่รอออกจากสถานะรอ และจะทำงานต่อ (ในช่วงเวลาที่จัดสรรไว้)

เมื่อคุณทำงานกับเรกคอร์ด TRTLCriticalSection เสร็จแล้ว คุณต้องทำให้ว่างโดยการเรียกกระบวนงาน DeleteCriticalSection() ซึ่งกำหนดไว้ดังนี้:

ขั้นตอน DeleteCriticalSection (var IpCriticalSection: TRTLCriticalSection); stdcall;