560 likes | 945 Views
Operating System. บทที่ 6 การประสานงานของกระบวนการ (PROCESS SYNCHRONIZATION). ความเป็นมา (Background).
E N D
Operating System บทที่ 6 การประสานงานของกระบวนการ (PROCESS SYNCHRONIZATION)
ความเป็นมา (Background) • ในบทที่ 4 ในเรื่องของการแก้ปัญหาหน่วยความร่วม ซึ่งมีที่พักข้อมูลแบบมีขอบเขต (bounded-buffer) ซึ่งเราพูดไปแล้ว จะเห็นว่าโปรแกรมดังกล่าวสามารถเก็บข้อมูลในที่พักได้มากที่สุด n – 1 ตัวเท่านั้น ถ้าเราปรับปรุงอัลกอริทึมโดยเพิ่มตัวแปร count ซึ่งเป็นตัวแปรชนิดตัวเลข (integer) เริ่มจาก 0 count จะถูกเพิ่มค่าทุกครั้งที่เราเพิ่มข้อมูลใหม่เข้าไปในที่พักข้อมูล และลดครั้งทุกครั้งที่นำข้อมูลออกจากที่พักข้อมูล
โปรแกรมผู้ผลิตจะกลายเป็นดังนี้โปรแกรมผู้ผลิตจะกลายเป็นดังนี้ • while (true) { • while (count == BUFFER_SIZE) • ; //do-nothing • buffer[in] = nextp; • in = (in + 1) % BUFFER_SIZE; • //add an item to the buffer • count++; • }
โปรแกรมผู้ใช้ กลายเป็นดังนี้ • while (true) { • while (count == 0) • ; //do-nothing • nextc = buffer[out]; • out = (out + 1) % BUFFER_SIZE; • // remove an item from the buffer • count--; • }
ถึงแม้ว่าโปรแกรมข้างต้นจะดูถูกต้อง แต่อาจมีการผิดพลาดได้ • ในสภาวะที่มีการทำงานขนานกัน (concurrent) • สมมติว่าขณะนี้ counter = 5 • ผู้ผลิตกำลังจะทำงานในประโยค counter := counter + 1; และ • ผู้ใช้กำลังจะทำงานในประโยค counter := counter – 1; ในเวลาเดียวกัน • ผลก็คือ counter อาจจะกลายเป็น 4, 5 หรือ 6 ก็ได้ โดยค่าที่ถูกต้องก็คือ 5 (ถ้าทำงานแบบตามลำดับ)
ทำไมจึงเป็นเช่นนี้ • ดูประโยค counter := counter + 1; ในความเป็นจริงในภาษาเครื่องแยกการทำงานเป็น 3 ขั้นตอน ดังต่อไปนี้คือ • register1 := counter; • register1 := register1 + 1; • counter := register1;
ในทำนองเดียวกัน • ประโยค counter := counter – 1; แยกได้เป็น • register2 := counter; • register2 := register2 - 1; • counter := register2;
เมื่อมีการขัดจังหวะการทำงานเมื่อมีการขัดจังหวะการทำงาน • การทำงานแบบขนาน (concurrent) ของประโยค counter := counter + 1; และ counter := counter - 1; ก็คือ การทำงานในระดับเครื่อง แยกเป็น 6 คำสั่งข้างต้น เรียงลำดับกัน (อาจมีการสลับกันระหว่างคำสั่ง 2 กระบวนการ) การทำงานจริงอาจมีลำดับดังนี้
T0 : producer execute register1 := counter [register1 = 5] • T1 : producer execute register1 := register1 + 1 [register1 = 6] • T2 : consumer execute register2 := counter [register2 = 5] • T3 : consumer execute register2 := register1 - 1 [register1 = 4] • T4 : producer execute counter := register1 [counter = 6] • T5 : consumer execute counter := register2 [counter = 4]
จะเห็นได้ว่า ผลลัพธ์สุดท้าย counter = 4 ซึ่งไม่ถูกต้อง (ที่จริงควรเป็น 5) ถ้า เวลา T4 กับ T5 กลับกัน ผลลัพธ์ก็จะกลายเป็น counter = 6 ซึ่งก็ไม่ถูกต้องเช่นกัน • ที่เป็นเช่นนี้เพราะกระบวนการ 2 กระบวนการ อ่านและเขียนข้อมูลตัวเดียวกัน (คือ counter) ในเวลาขนานกัน สถานการณ์เช่นนี้ซึ่งหมายถึงกระบวนการหลาย ๆ กระบวนการเข้าถึง (access) และจัดการ (manipulate) ข้อมูลเดียวกันในเวลาขนานกัน แล้วผลลัพธ์ของการทำงานขึ้นอยู่กับคำสั่งที่กำลังเข้าถึงข้อมูลอยู่ ถูกเรียกว่า เงื่อนไขแข่งขัน (race condition)
ปัญหาเขตวิกฤต (The Critical-Section Problem) • เมื่อกระบวนการหนึ่งกำลังทำงานในเขตวิกฤต ห้ามมีกระบวนการอื่นเข้ามาทำงานในเขตวิกฤตนั้น ดังนั้นการที่กระบวนการทำงานในเขตวิกฤต คือ การห้ามอยู่พร้อมกัน (mutually exclusive) • เขตที่ใช้จริง ๆ มี 3 เขต คือ • ช่วงร้องขอเข้าเรียกว่า entry section • ถ้าอยู่ในเขตวิกฤตแล้วต้องการออก เรียกว่า exit section • ช่วงหลังเขตวิกฤต เรียกว่า remainder section
วิธีแก้ปัญหาเขตวิกฤตต้องแก้ 3 ข้อ ดังนี้ • การห้ามอยู่พร้อมกัน (Mutual Exclusion)ถ้ากระบวนการ P1 ทำงานอยู่ในเขตวิกฤต ห้ามกระบวนการอื่นทำงานในเขตนั้น • ความก้าวหน้า (Progress)ถ้ายังไม่มีกระบวนการใดทำงานในเขตวิกฤต และมีบางกระบวนการต้องการเข้าไปในเขตนั้น กระบวนการเหล่านั้นก็จะถูกนำมาตัดสินว่าใครจะได้เข้าไปในเขตวิกฤตเป็นลำดับถัดไป การคัดเลือกนี้ต้องชัดเจน เลื่อนสลับที่กันไม่ได้
วิธีแก้ปัญหาเขตวิกฤตต้องแก้ 3 ข้อ ดังนี้ • การรอคอยอย่างมีขอบเขต (Bounded Waiting)ต้องมีขอบเขตของเวลาให้กระบวนการอื่นได้เข้าไปในเขตวิกฤตของตนเอง หลังจากกระบวนการได้ร้องขอเข้าเขตวิกฤต และก่อนหน้านั้นการร้องขอดังกล่าวต้องได้รับอนุญาตก่อนแล้ว • repeat • entry section • critical section • exit section • remainder section • until false;
วิธีแก้ปัญหา 2 กระบวนการ(Two-Process Solution) • ในหัวข้อนี้มุ่งประเด็นไปที่ในช่วงเวลาหนึ่งมีกระบวนการแค่ 2 กระบวนการเท่านั้น คือ P0และ P1 • เพื่อความสะดวก เราใช้ Piและ Pjซึ่ง j = 1 – i
Algorithm 1 • ขั้นแรกให้ทั้ง 2 กระบวนการร่วมกันใช้ตัวแปร turn ซึ่งเริ่มจาก 0 (หรือ 1) ถ้า turn = i กระบวนการ Piจะได้ทำงานในเขตวิกฤตของมัน โครงสร้างของ Piแสดงได้ดังนี้ • do { • while (turn != i) ; //do nothing • critical section • turn = j; • remainder section • } while (true);
Algorithm 1 • นี้สามารถประกันได้ว่า จะมีเพียงกระบวนการเดียวเท่านั้น ในเขตวิกฤต แต่ไม่ผ่านคุณสมบัติ ความก้าวหน้า (Progress) เพราะกระบวนการทั้งสอง ต้องสลับกันเข้าใช้เขตวิกฤต คนละครั้งเป็นวงกลม เช่น ถ้า turn = 0 และ P1พร้อมที่จะเข้าเขตวิกฤต ก็จะไม่สามารถเข้าได้ แม้ว่า P0จะอยู่หลังเขตวิกฤต (remainder section) ก็ตาม
Algorithm 2 • ปัญหาของ Algorithm 1 คือ ไม่มีการจัดเก็บสถานะของแต่ละกระบวนการไว้ มีเพียงตัวแปรเก็บค่า ว่าขณะนี้เป็นคราวของกระบวนการใด เราจึงอาจใช้ flag แทน turn ดังนี้ • ซึ่ง boolean flag[2]; • ให้ flag[0] := false; • flag[1] := false; เมื่อเริ่มต้น • เมื่อ Piต้องการจะเข้าเขตวิกฤต flag[i] = true พิจารณารูปถัดไป
Algorithm 2 • do { • flag[i] = true; • while flag[j] ; //do nothing • critical section • flag[i] = false; • remainder section • } while (true);
Algorithm 2 • ในขั้นตอน algorithm นี้ Piให้ flag[i] = true เพื่อที่แสดงว่า ต้องการจะเข้าเขตวิกฤต แล้วตรวจดูว่า Pjไม่ได้ต้องการจะเข้าเขตวิกฤตด้วย โดยดู flag[j] ถ้า Pjต้องการเข้าเขตวิกฤต (flag[j] = true) Piก็จะรอ (ทำคำสั่ง do nothing) จนกระทั่ง Pjออกจากเขตวิกฤต แล้วเปลี่ยน flag[j] = false Piจึงจะเข้าเขตวิกฤตได้ • วิธีนี้ ผ่านคุณสมบัติ การห้ามอยู่พร้อมกัน (mutually exclusion) แต่ไม่ผ่านคุณสมบัติ ความก้าวหน้า (Progress) ดังตัวอย่างเช่น • ณ เวลา T0: P0ทำคำสั่ง flag[0] = true • ณ เวลา T1: P1ทำคำสั่ง flag[1] = true • ถ้าเป็นเช่นนี้แล้วทั้ง P0และ P1จะติดอยู่ในวงจรรอบของ while ตลอดไป
Algorithm 3 • โดยการรวมแนวคิดของ Algorithm 1 และ 2 เข้าด้วยกัน จะได้วิธีที่สมบูรณ์แบบสามารถผ่านคุณสมบัติทั้ง 3 ประการในปัญหาเขตวิกฤตได้ โดยกำหนดให้ ตัวแปรร่วมเป็นดังนี้ • boolean flag[2]; • int turn; • เริ่มต้น flag[0] = flag[1] = false และ turn อาจเป็น 0 หรือ 1 และโปรแกรมสำหรับกระบวนการ Piเป็น
Algorithm 3 • do { • flag[i] = true; //กระบวนการแรกกำลังจะได้เข้าเขตวิกฤต • turn = j; //ให้ counter ของอีกกระบวนการหนึ่งเตรียมตัวเข้า CS เป็นลำดับถัดไป • while (flag[j] && turn == j) • ; do nothing //ถ้ามีกระบวนการใดอยู่ใน CS กระบวนการหลังต้องรอก่อน • critical section • flag[i] = false; //กระบวนการแรกออกมาแล้วต้อง set ตัวเองเป็น false • remainder section • } while (true);
Algorithm 3 • เมื่อกระบวนการ Piต้องการเข้าเขตวิกฤต flag[i] = true และให้ turn เป็นของอีกกระบวนการหนึ่ง คือ j (turn=j) ถ้าทั้งสองกระบวนการต้องการเข้าเขตวิกฤตในเวลาเดียวกัน ค่า turn จะถูกกำหนดเป็น i และ j ในเวลาใกล้เคียงกัน turn จะกลายเป็นค่าสุดท้าย (i หรือ j) ซึ่งจะอนุญาตให้เพียงกระบวนการเดียวเข้าเขตวิกฤต • เราจะต้องพิสูจน์ว่า ขั้นตอน algorithm 3 นี้สามารถผ่านคุณสมบัติทั้ง 3 ประการ คือ • การห้ามอยู่พร้อมกัน (Mutually Exclusion) • ความก้าวหน้า (Progress) • การรอคอยอย่างมีขอบเขต (Bounded Waiting)
Algorithm 3 • จะสังเกตเห็นว่า Piเข้าเขตวิกฤตได้ก็ต่อเมื่อ flag[j] = false หรือ turn = 1 และถ้ากระบวนการทั้งสอง สามารถอยู่ในเขตวิกฤตได้พร้อมกัน แล้ว flag[0] = flag[1] = true จากข้อสังเกต 2 ข้อที่กล่าวมานี้ สรุปได้ว่า P0และ P1ไม่สามารถผ่านประโยค while ในขณะเดียวกันได้ เพราะค่าของ turn มีได้เพียงค่าเดียวเท่านั้น (0 หรือ 1) ในขณะหนึ่ง ๆ ดังนั้นจะมีเพียงกระบวนการเดียวเท่านั้น (สมมติให้เป็น Pi) ที่ผ่านประโยค while ได้ ขณะที่ Pjยังคงติดใน while เพราะ turn = j และ flag[j] = true และจะติดอยู่ตราบเท่าที่ Piยังคงอยู่ในเขตวิกฤต สรุปได้ว่าวิธีนี้ผ่านคุณสมบัติ การห้ามอยู่พร้อมกัน (mutually exclusion)
Algorithm 3 • Pi จะติดอยู่ในประโยค while ก็ต่อเมื่อ flag[j] = true และ turn = j ก่อนที่จะเข้าเขตวิกฤต ถ้า Pjไม่ต้องการเข้าเขตวิกฤตแล้ว flag[j] = false Piก็สามารถเข้าเขตวิกฤตได้ ถ้า Pj ต้องการเข้าโดยให้ flag[j] = true และทำคำสั่ง while ค่าของ turn จะต้องเป็น i หรือ j ค่าใดค่าหนึ่งเท่านั้น ถ้า turn = i Piก็จะเข้าเขตวิกฤตได้ ถ้า turn = j Pjก็จะเข้าเขตวิกฤตได้ หลังจากที่ Pjออกจากเขตวิกฤต Pjก็จะให้ flag[j] = false ซึ่งเป็นผลให้ Piสามารถผ่าน while เข้าเขตวิกฤตได้ แต่ถ้า Pjทำงานเร็วมาก วนไปให้ flag[j] = true (ก่อนเข้าเขตวิกฤต)ได้ ก็จะทำให้ turn = i ดังนั้น Piซึ่งไม่ได้เปลี่ยนค่า turn ในขณะที่ติดอยู่ที่ while ก็จะเข้าเขตวิกฤตได้ (ผ่านคุณสมบัติ ความก้าวหน้า (Progress) โดยรอคอยอย่างมากไม่เกิน 1 รอบที่ Pjเข้าเขตวิกฤต (ผ่านคุณสมบัติ การรอคอยอย่างมีขอบเขต (Bounded Waiting)
การแก้ปัญหาในระบบที่มีหลายกระบวนการ (Multiple-Process Solutions) • Algorithm นี้ เรียกว่า bakery algorithm วิธีนี้มีแนวคิดมาจาก การจัดลำดับลูกค้าที่มาใช้บริการในร้านขายขนมปัง • เมื่อลูกค้าเข้ามาในร้าน จะได้รับหมายเลขบัตรคิว ลูกค้าที่มีหมายเลขน้อยที่สุด จะได้รับบริการก่อน ในกรณีที่ลูกค้า 2 คน อาจได้รับหมายเลขเดียวกัน (เพราะเข้าร้านพร้อมกัน) ให้เรียงลำดับ ด้วยชื่อลูกค้าแทน เช่น ถ้า Piและ Pjได้รับหมายเลขเดียวกัน และ i < j แล้ว Piจะได้รับบริการก่อน
bakery algorithm • โครงสร้างข้อมูลโดยทั่วไปคือ • boolean choosing[n]; • int number[n]; • ค่าเริ่มต้นของ choosing ทั้งหมดเป็น false • และ number = 0 • เพื่อความสะดวก เรากำหนดสัญลักษณ์ดังนี้ • (a,b) < (c,d) ถ้า a < c หรือ ถ้า a = c และ b < d • max(a0 , … , an-1) เป็นฟังก์ชันให้ค่ามากที่สุดจาก a0ถึง an-1
do { • choosing[i] = true; //กระบวนการแรกจะเข้าเขตวิกฤต • number[i] := max(number[0], number[1], … , number[n-1]) + 1; • //ให้ priority (บัตรคิว) คนถัดไป • choosing[i] = false; //นั่งรอก่อน • for (j = 0; j < n; j++) { • while (choosing[j]) • ; //do nothing //ใครอยู่ในเขตวิกฤตก่อนหน้าหรือเปล่า ถ้ามีต้องรอก่อน • while (number [j] != 0 //กระบวนการที่มาทีหลังต้องไม่เป็น 0 ถึงจะได้เข้าเขตวิกฤต • ใครมีหมายเลขบัตรคิวสูงกว่ากัน • && (number[j],j) < (number[i],i)) • ; //do nothing • ถ้าบัตรคิวเบอร์เดียวกัน ดูชื่อของกระบวนการว่าชื่อใครสิทธิสูงกว่า • } • critical section • number[i] = 0; //ออกจากเขตวิกฤต • remainder section • } while (true);
ซีมาฟอร์ (Semaphores) • วิธีการแก้ปัญหาเขตวิกฤต ที่กล่าวมาแล้วทั้งหมด ยังคงไม่สะดวกในการใช้งานกับปัญหาที่ซับซ้อนมากขึ้น เราจึงต้องหาวิธีใหม่ เรียกว่า semaphore semaphore S คือ ตัวแปรชนิด integer ซึ่งสามารถกำหนดค่าเริ่มต้นได้ และใช้งานได้ โดยผ่านคำสั่ง 2 คำสั่ง เท่านั้น คือ wait และ signal คำสั่งทั้งสองนี้ มีนิยามดั้งเดิมดังนี้ • wait(S) { while S 0 • ; //do no-op; • S --; • } • signal(S) { S ++; }
ซีมาฟอร์ (Semaphores) • การใช้งาน(Usage) • เราสามารถใช้ semaphore กับปัญหาเขตวิกฤต ที่มีกระบวนการ n กระบวนการได้ โดยให้ตัวแปรร่วมเป็น • semaphore mutex =1; //mutex เป็น semaphore มีค่า 1; • โปรแกรมสำหรับกระบวนการ Piเป็นดังนี้ • do { • wait(mutex); • critical section • signal(mutex); • remainder section • } while (true);
ซีมาฟอร์ (Semaphores) • เรายังสามารถใช้ semaphore แก้ปัญหาการประสานงาน (synchronization problem)แบบต่าง ๆ ได้อีก เช่น กระบวนการ P1ทำประโยคคำสั่ง S1 และกระบวนการ P2ทำประโยคคำสั่ง S2 โดยทำงานขนานกัน แต่เราต้องการให้ประโยค S2 ทำงานหลังจากที่ประโยค S1 เสร็จเรียบร้อยแล้ว
ซีมาฟอร์ (Semaphores) • เราสามารถใช้ตัวแปรร่วม • semaphore synch = 0; • และเพิ่มคำสั่ง • S1; • signal(synch); • ลงในโปรแกรมของกระบวนการ P1และเพิ่มคำสั่ง • wait(synch); • S2; ลงในกระบวนการ P2 • เนื่องจากค่าเริ่มต้นของ synch เป็น 0 ดังนั้น P2จะทำประโยค S2 ได้ ก็ต่อเมื่อ P1ทำคำสั่ง signal(synch); (ซึ่งหมายความว่า P1 ทำประโยค S1 เสร็จแล้ว
การสร้าง semaphore (Implementation) • วิธีแก้ปัญหาเขตวิกฤตทั้งหมดที่กล่าวมา มีข้อเสียหลักตรงที่ มี “การวนเวียนรอคอย” (busy waiting) เมื่อมีกระบวนการหนึ่ง อยู่ในเขตวิกฤต กระบวนการอื่น ๆ ที่ต้องการเข้าเขตวิกฤตบ้าง ต้องวนเวียนทดสอบค่าก่อนเข้าเขตวิกฤตไปเรื่อย ๆ ย่อมเป็นเหตุให้เวลาของหน่วยประมวลผลกลางเสียไปโดยเปล่าประโยชน์ • เวลาเหล่านี้อาจยกให้กระบวนการอื่น ได้ใช้ประโยชน์คุ้มค่ากว่า semaphore ประเภทนี้ เรียกว่า spinlock คือกระบวนการวนเวียนทำงาน (spin) อยู่ ขณะที่กำลังรอคอยให้เปิดล็อค
การสร้าง semaphore (Implementation) • เราสามารถปรับปรุงนิยามของคำสั่ง wait และ signal ใหม่ เพื่อให้ ไม่มีปัญหา การวนเวียนรอคอย (busy waiting) เมื่อกระบวนการทำคำสั่ง wait และพบว่า ค่าของ semaphore น้อยกว่าหรือเท่ากับ 0 กระบวนการจะต้องรอคอย โดยการหยุดชั่วขณะ (block) แทนที่จะวนเวียนรอคอย การหยุดชั่วขณะนี้ กระบวนการจะเข้าไปรอในแถวคอย (waiting queue) ที่เกี่ยวข้องกับ semaphore นั้น ๆ และเปลี่ยนสถานะเป็น สถานะรอคอย (waiting state) การควบคุมจะถูกย้ายกลับไปที่ตัวจัดตารางการทำงานของหน่วยประมวลผลกลาง เพื่อจัดให้กระบวนการอื่นทำงานต่อไป
การสร้าง semaphore (Implementation) • กระบวนการที่หยุดชั่วขณะ (block) โดยรอ semaphore S นี้ จะสามารถดำเนินต่อได้ เมื่อมีกระบวนการอื่นทำคำสั่ง signal กับ semaphore S ภายในคำสั่ง signal จะมีการปลุก (wakeup) ให้กระบวนการ ที่หยุดชั่วขณะ กลับดำเนินการต่อได้ โดยเปลี่ยนสถานะของกระบวนการนั้นเป็น สถานะพร้อม (ready state) แล้วย้ายไปต่อแถวพร้อม
การสร้าง semaphore (Implementation) • การสร้าง semaphore ตามนิยามใหม่นี้ กำหนดให้ semaphore เป็น struct ในภาษา c • typedef struct { • int value; • struct process *L; • } semaphore;
การสร้าง semaphore (Implementation) • semaphore จะประกอบด้วย ตัวเลขจำนวนเต็ม (ตัวแปร value) และแถวคอยของกระบวนการ (ตัวแปร L) เมื่อกระบวนการหนึ่งต้องคอย semaphore นี้ มันก็จะถูกใส่ชื่อลงในแถวคอย L การใช้คำสั่ง signal จะดึงกระบวนการออกจากหัวแถวคอยนี้ และปลุกกระบวนการที่ได้ ให้กลับไปทำงานต่อ เราจึงแก้ไขโปรแกรมคำสั่งเป็นดังนี้
การสร้าง semaphore (Implementation) • void wait(semaphore S) { • S.value - -; • if (S.value < 0) { • add this process to S.L (list); • block(); • } • } • void signal(semaphore S) { • S.value ++; • if (S.value <= 0) { • remove a process P from S.L (list); • wakeup(P); • } • }
การสร้าง semaphore (Implementation) • คำสั่ง block จะทำให้กระบวนการที่ทำคำสั่งนี้ หยุดชั่วขณะ • คำสั่ง wakeup(P) จะปลุกกระบวนการ P ให้กลับทำงานต่อ • จะสังเกตได้ว่า นิยามดั้งเดิมของ semaphore ซึ่งมีการวนเวียนรอคอยนั้น ค่าของ semaphore จะไม่ติดลบ • แต่ในนิยามใหม่นี้ ค่าของ semaphore มีค่าเป็นลบได้ ค่าที่เป็นลบแสดงถึงจำนวนกระบวนการที่รอคอย semaphore ตัวนั้น ๆ ด้วย • เราต้องไม่ให้มีกระบวนการสองตัวทำคำสั่ง wait และ signal บน semaphore ตัวเดียวกัน ในเวลาเดียวกัน ซึ่งกรณีนี้ ก็คือ ปัญหาเขตวิกฤตแบบหนึ่งนั่นเอง โดยสามารถเลือกวิธีจัดการได้ 2 วิธี คือ
การสร้าง semaphore (Implementation) • ถ้าเป็นระบบที่มีหน่วยประมวลผลเพียงตัวเดียว เราก็เพียงแต่ห้ามขัดจังหวะ (interrupt) ขณะที่กำลังทำงานคำสั่ง wait และ signal ดังนั้นกระบวนการอื่น จะไม่สามารถมาขัดจังหวะการทำงานได้ • ถ้าเป็นระบบที่มีหน่วยประมวลผลหลายตัว การห้ามขัดจังหวะ จะไม่เพียงพอเพราะกระบวนการอื่น อาจทำงานอยู่ในหน่วยประมวลผล คนละตัวกับ กระบวนการที่ห้ามขัดจังหวะ เราอาจจัดการ โดยใช้ขั้นตอนวิธี ในหัวข้อ 6.2 (critical section) แล้วเอาคำสั่ง wait และ signal ใส่ไว้ในเขตวิกฤต ก็จะสามารถป้องกันได้
แม่แบบของปัญหาในการประสานงาน (Classical Problems of Synchronization) • ปัญหาที่พักข้อมูลขนาดจำกัด • (The Bounded-Buffer Problem) • ปัญหาผู้อ่าน-ผู้เขียน • (The Readers and Writers Problem) • ปัญหาอาหารเย็นของนักปราชญ์ • (The Dining-Philosopher’ s Problem)
ปัญหาที่พักข้อมูลขนาดจำกัด (The Bounded-Buffer Problem) • กำหนดให้ ที่พักข้อมูลมีขนาด n ช่อง แต่ละช่องเก็บข้อมูลได้ 1 ตัว ให้ semaphore mutex มีค่าเริ่มต้นเป็น 1 ตัวป้องกันการใช้ที่พักข้อมูลคือ semaphore emptyและ fullเป็นตัวนับว่า ที่พักข้อมูล มีจำนวนช่องว่าง และ ช่องเต็ม(มีข้อมูลอยู่) กี่ช่อง โดย emptyมีค่าเริ่มต้นเป็น n และ fullมีค่าเริ่มต้น เป็น 0
ปัญหาที่พักข้อมูลขนาดจำกัด (The Bounded-Buffer Problem) • do { • … • produce an item in nextp • … • wait(empty); • wait(mutex); • … • add nextp to buffer • … • signal(mutex); • signal(full); • } while (true); • โครงสร้างของกระบวนการผู้ผลิต
ปัญหาที่พักข้อมูลขนาดจำกัด (The Bounded-Buffer Problem) • do { • wait(full); • wait(mutex); • … • remove an item from buffer to nextc • … • signal(mutex); • signal(empty); • … • consume the item in nextc • … • } while (true); • โครงสร้างของกระบวนการผู้ใช้
ปัญหาผู้อ่าน-ผู้เขียน(The Readers and Writers Problem) • กลุ่มของกระบวนการเหล่านี้ ออกเป็น 2 พวก คือ • พวกที่ต้องการอ่านอย่างเดียว เรียกว่า “ผู้อ่าน” (readers) • ส่วนพวกที่เหลือ เรียกว่า “ผู้เขียน” (writers) • จะเห็นได้ว่า ผู้อ่านหลายคน อาจอ่านข้อมูลร่วมกันได้พร้อม ๆ กัน โดยไม่เสียหาย แต่ผู้เขียน 1 คน กับอีกคนหนึ่ง (ซึ่งอาจเป็น ผู้อ่าน หรือ ผู้เขียนก็ได้) ไม่อาจทำการพร้อม ๆ กัน บนข้อมูลร่วมนี้ได้
ปัญหาผู้อ่าน-ผู้เขียน(The Readers and Writers Problem) • เพื่อป้องกันปัญหานี้ เราต้องให้ผู้เขียน ใช้ข้อมูลร่วม เพียงคนเดียวในขณะหนึ่ง ๆ ปัญหาการประสานงานแบบนี้ เราเรียกว่า ปัญหาผู้อ่าน-ผู้เขียน (Readers-Writers Problem) • หมายความว่า ถ้าข้อมูลร่วม มีผู้อ่านคนอื่น กำลังอ่านอยู่ ผู้ที่เข้ามาใหม่สามารถใช้ข้อมูลร่วมได้เลย แม้ว่า จะมีผู้เขียนรอใช้อยู่ก็ตาม • แบบที่ 2 เมื่อผู้เขียนพร้อม ผู้เขียนจะสามารถใช้ข้อมูลร่วมได้เร็วที่สุดเท่าที่จะทำได้ หมายความว่า เมื่อมีผู้เขียนรออยู่ ผู้อ่านใหม่ห้ามเข้าไปอ่านร่วม
ปัญหาผู้อ่าน-ผู้เขียน(The Readers and Writers Problem) • ในการแก้ปัญหาผู้อ่าน-ผู้เขียนแบบแรก กำหนดให้ตัวแปรร่วมเป็นดังนี้ • semaphore mutex=1, wrt =1; • int readcount = 0; • ตัวแปร readcount เป็นตัวนับจำนวน ผู้อ่านที่กำลังอ่านอยู่ semaphore mutexใช้ป้องกันตัวแปร readcount semaphore wrt ใช้ป้องกันผู้เขียน ผู้อ่านคนแรก และคนสุดท้าย ที่เข้าและออกจากเขตวิกฤตต้องใช้ semaphore wrtด้วย ส่วนผู้อ่านคนกลาง ๆ ไม่ต้องใช้ semaphore wrt
ปัญหาผู้อ่าน-ผู้เขียน(The Readers and Writers Problem) • wait(wrt); • … • writing is performed • … • signal(wrt); • โครงสร้างของกระบวนการผู้เขียน
ปัญหาผู้อ่าน-ผู้เขียน(The Readers and Writers Problem) • wait(mutex); • readcount ++; • if (readcount == 1) • wait(wrt); • signal(mutex); • … • reading is performed • … • wait(mutex); • readcount --; • if (readcount == 0) • signal(wrt); • signal(mutex); • โครงสร้างของกระบวนการผู้อ่าน
ปัญหาผู้อ่าน-ผู้เขียน(The Readers and Writers Problem) • สังเกตว่า ถ้าผู้เขียนอยู่ในเขตวิกฤต และมีผู้อ่านรออยู่ n คน ผู้อ่าน 1 คนจะรออยู่ที่ semaphore wrtส่วนผู้อ่าน n – 1 คน จะรออยู่ที่ semaphore mutexและเมื่อผู้เขียนทำคำสั่ง signal(wrt) อาจไปปลุกผู้อ่านทั้งหมด หรือผู้เขียน 1 คน ซึ่งรออยู่ก็ได้ ขึ้นอยู่กับการออกแบบตัวจัดตารางแถวคอย
ปัญหาอาหารเย็นของนักปราชญ์The Dining-Philosopher’ s Problem)