การทำงานพร้อมกันใน Python - คู่มือฉบับย่อ

ในบทนี้เราจะเข้าใจแนวคิดของการทำงานพร้อมกันใน Python และเรียนรู้เกี่ยวกับเธรดและกระบวนการต่างๆ

Concurrency คืออะไร?

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

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

การทบทวนประวัติศาสตร์ของภาวะพร้อมกัน

ประเด็นต่อไปนี้จะทำให้เราได้รับการทบทวนประวัติศาสตร์โดยย่อเกี่ยวกับภาวะพร้อมกัน

จากแนวคิดของทางรถไฟ

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

การประมวลผลพร้อมกันในสถาบันการศึกษา

ความสนใจในวิทยาการคอมพิวเตอร์พร้อมกันเริ่มจากงานวิจัยที่ตีพิมพ์โดย Edsger W. Dijkstra ในปี 1965 ในบทความนี้เขาระบุและแก้ไขปัญหาการกีดกันซึ่งกันและกันซึ่งเป็นคุณสมบัติของการควบคุมภาวะพร้อมกัน

อนุพันธ์ของภาวะพร้อมกันระดับสูง

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

ปรับปรุงการทำงานพร้อมกันด้วยภาษาโปรแกรม

ภาษาโปรแกรมเช่น Golang, Rust และ Python ของ Google ได้พัฒนาอย่างไม่น่าเชื่อในด้านต่างๆซึ่งช่วยให้เราได้รับโซลูชันพร้อมกันที่ดีขึ้น

เธรดและมัลติเธรดคืออะไร?

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

เธรดประกอบด้วยส่วนประกอบต่อไปนี้ -

  • ตัวนับโปรแกรมซึ่งประกอบด้วยที่อยู่ของคำสั่งปฏิบัติการถัดไป

  • Stack

  • ชุดทะเบียน

  • รหัสเฉพาะ

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

ตัวอย่าง

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

กระบวนการและการประมวลผลหลายขั้นตอนคืออะไร?

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

แผนภาพต่อไปนี้แสดงขั้นตอนต่างๆของกระบวนการ -

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

Multiprocessing,ในทางกลับกันคือการใช้ซีพียูสองหน่วยหรือมากกว่าภายในระบบคอมพิวเตอร์เครื่องเดียว เป้าหมายหลักของเราคือการได้รับศักยภาพสูงสุดจากฮาร์ดแวร์ของเรา เพื่อให้บรรลุเป้าหมายนี้เราจำเป็นต้องใช้คอร์ CPU เต็มจำนวนที่มีอยู่ในระบบคอมพิวเตอร์ของเรา การประมวลผลหลายขั้นตอนเป็นแนวทางที่ดีที่สุดในการดำเนินการดังกล่าว

Python เป็นหนึ่งในภาษาโปรแกรมยอดนิยม ต่อไปนี้เป็นเหตุผลบางประการที่ทำให้เหมาะสำหรับการใช้งานพร้อมกัน -

น้ำตาลสังเคราะห์

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

ชุมชนขนาดใหญ่

ภาษา Python ได้เห็นอัตราการยอมรับอย่างมากในหมู่นักวิทยาศาสตร์ข้อมูลและนักคณิตศาสตร์ซึ่งทำงานในด้าน AI, การเรียนรู้ของเครื่อง, การเรียนรู้เชิงลึกและการวิเคราะห์เชิงปริมาณ

API ที่มีประโยชน์สำหรับการเขียนโปรแกรมพร้อมกัน

Python 2 และ 3 มี API จำนวนมากเฉพาะสำหรับการเขียนโปรแกรมแบบขนาน / พร้อมกัน ที่นิยมมากที่สุดคือthreading, concurrent.features, multiprocessing, asyncio, gevent and greenlets, เป็นต้น

ข้อ จำกัด ของ Python ในการใช้งานแอปพลิเคชันพร้อมกัน

Python มีข้อ จำกัด สำหรับการใช้งานพร้อมกัน ข้อ จำกัด นี้เรียกว่าGIL (Global Interpreter Lock)มีอยู่ใน Python GIL ไม่อนุญาตให้เราใช้ CPU หลายคอร์และด้วยเหตุนี้เราจึงสามารถพูดได้ว่าไม่มีเธรดที่แท้จริงใน Python เราสามารถเข้าใจแนวคิดของ GIL ได้ดังนี้ -

GIL (Global Interpreter Lock)

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

อย่างไรก็ตามมีไลบรารีและการใช้งานบางอย่างใน Python เช่น Numpy, Jpython และ IronPytbhon. ไลบรารีเหล่านี้ทำงานโดยไม่มีการโต้ตอบใด ๆ กับ GIL

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

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

ความพร้อมกันในรายละเอียด

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

ระดับของภาวะพร้อมกัน

ในส่วนนี้เราจะพูดถึงระดับที่สำคัญสามระดับของการทำงานพร้อมกันในแง่ของการเขียนโปรแกรม -

ภาวะพร้อมกันระดับต่ำ

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

การทำงานพร้อมกันระดับกลาง

ในภาวะพร้อมกันนี้ไม่มีการใช้การดำเนินการเกี่ยวกับปรมาณูอย่างชัดเจน มันใช้การล็อกที่ชัดเจน Python และภาษาโปรแกรมอื่น ๆ รองรับการทำงานพร้อมกันประเภทนี้ โปรแกรมเมอร์แอปพลิเคชันส่วนใหญ่ใช้การทำงานพร้อมกันนี้

การทำงานพร้อมกันระดับสูง

ในภาวะพร้อมกันนี้จะไม่มีการใช้การดำเนินการเกี่ยวกับอะตอมอย่างชัดเจนหรือการล็อกอย่างชัดเจน Python มีconcurrent.futures โมดูลเพื่อรองรับการทำงานพร้อมกันประเภทนี้

คุณสมบัติของระบบพร้อมกัน

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

คุณสมบัติความถูกต้อง

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

คุณสมบัติด้านความปลอดภัย

คุณสมบัติด้านความปลอดภัยหมายความว่าโปรแกรมหรือระบบจะต้องอยู่ในไฟล์ “good” หรือ “safe” รัฐและไม่เคยทำอะไรเลย “bad”.

ทรัพย์สินมีชีวิต

คุณสมบัตินี้หมายความว่าโปรแกรมหรือระบบต้อง “make progress” และมันจะไปถึงสถานะที่พึงปรารถนา

นักแสดงของระบบพร้อมกัน

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

ทรัพยากรของระบบพร้อมกัน

ผู้แสดงจะต้องใช้ทรัพยากรเช่นหน่วยความจำดิสก์เครื่องพิมพ์ ฯลฯ เพื่อทำงานของตน

ชุดของกฎบางอย่าง

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

อุปสรรคของระบบพร้อมกัน

การแบ่งปันข้อมูล

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

ข้อ จำกัด การแบ่งปันข้อมูล

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

ความช่วยเหลือด้านโครงสร้างข้อมูล

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

การถ่ายโอนข้อมูลที่ไม่เปลี่ยนรูป

บางครั้งโครงสร้างข้อมูลที่เราใช้พูดว่า concurrency que ไม่เหมาะสมเราสามารถส่งผ่านข้อมูลที่ไม่เปลี่ยนรูปได้โดยไม่ต้องล็อก

การถ่ายโอนข้อมูลที่ไม่แน่นอน

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

การแบ่งปันทรัพยากร I / O

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

สคริปต์ Python ต่อไปนี้ใช้สำหรับขอหน้าเว็บและรับเวลาที่เครือข่ายของเราใช้ในการรับหน้าที่ร้องขอ -

import urllib.request
import time
ts = time.time()
req = urllib.request.urlopen('http://www.tutorialspoint.com')
pageHtml = req.read()
te = time.time()
print("Page Fetching Time : {} Seconds".format (te-ts))

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

เอาต์พุต

Page Fetching Time: 1.0991398811340332 Seconds

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

Parallelism คืออะไร?

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

หากต้องการทราบข้อมูลเพิ่มเติมเกี่ยวกับความแตกต่างระหว่างภาวะพร้อมกันและความขนานให้พิจารณาประเด็นต่อไปนี้ -

พร้อมกัน แต่ไม่ขนานกัน

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

ขนานกัน แต่ไม่พร้อมกัน

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

ไม่ขนานหรือพร้อมกัน

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

ทั้งแบบขนานและพร้อมกัน

แอปพลิเคชันสามารถเป็นได้ทั้งแบบขนานและพร้อมกันหมายความว่าทั้งสองทำงานได้หลายงานพร้อมกันและงานจะแบ่งออกเป็นงานย่อยสำหรับการดำเนินการแบบขนาน

ความจำเป็นของ Parallelism

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

พิจารณาประเด็นสำคัญต่อไปนี้เพื่อทำความเข้าใจว่าเหตุใดจึงจำเป็นต้องบรรลุความเท่าเทียมกัน -

การเรียกใช้รหัสที่มีประสิทธิภาพ

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

เร็วกว่าการคำนวณตามลำดับ

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

เวลาดำเนินการน้อยลง

การประมวลผลแบบขนานช่วยลดเวลาในการดำเนินการของโค้ดโปรแกรม

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

ความเข้าใจเกี่ยวกับโปรเซสเซอร์สำหรับการนำไปใช้งาน

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

โปรเซสเซอร์ single-core

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

โปรเซสเซอร์แบบ Single-core มาพร้อมกับข้อดีมากมาย โปรเซสเซอร์เหล่านี้ต้องการพลังงานน้อยลงและไม่มีโปรโตคอลการสื่อสารที่ซับซ้อนระหว่างหลายคอร์ ในทางกลับกันความเร็วของโปรเซสเซอร์ single-core มี จำกัด และไม่เหมาะสำหรับการใช้งานขนาดใหญ่

โปรเซสเซอร์แบบมัลติคอร์

โปรเซสเซอร์แบบมัลติคอร์มีหน่วยประมวลผลอิสระหลายหน่วยที่เรียกว่า cores.

โปรเซสเซอร์ดังกล่าวไม่จำเป็นต้องมีกลไกการสลับบริบทเนื่องจากแต่ละคอร์มีทุกสิ่งที่จำเป็นในการดำเนินการตามลำดับของคำสั่งที่จัดเก็บไว้

Fetch-Decode-Execute Cycle

แกนของโปรเซสเซอร์แบบมัลติคอร์เป็นไปตามวงจรสำหรับการดำเนินการ วงจรนี้เรียกว่าFetch-Decode-Executeวงจร มันเกี่ยวข้องกับขั้นตอนต่อไปนี้ -

ดึงข้อมูล

นี่เป็นขั้นตอนแรกของวงจรซึ่งเกี่ยวข้องกับการดึงคำสั่งจากหน่วยความจำโปรแกรม

ถอดรหัส

คำแนะนำที่ดึงมาล่าสุดจะถูกแปลงเป็นชุดของสัญญาณที่จะเรียกใช้ส่วนอื่น ๆ ของ CPU

ดำเนินการ

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

ข้อดีอย่างหนึ่งของที่นี่คือการดำเนินการในโปรเซสเซอร์แบบมัลติคอร์นั้นเร็วกว่าโปรเซสเซอร์แบบ single-core เหมาะสำหรับการใช้งานขนาดใหญ่ ในทางกลับกันโปรโตคอลการสื่อสารที่ซับซ้อนระหว่างหลายคอร์เป็นปัญหา หลายคอร์ต้องการพลังงานมากกว่าโปรเซสเซอร์แบบ single-core

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

สถาปัตยกรรมระบบคอมพิวเตอร์ที่รองรับการทำงานพร้อมกัน

Michael Flynn ในปีพ. ศ. 2515 ให้อนุกรมวิธานสำหรับการจัดประเภทสถาปัตยกรรมระบบคอมพิวเตอร์ในลักษณะต่างๆ อนุกรมวิธานนี้กำหนดรูปแบบที่แตกต่างกันสี่แบบดังนี้ -

  • สตรีมคำสั่งเดี่ยวสตรีมข้อมูลเดียว (SISD)
  • สตรีมคำสั่งเดียวสตรีมข้อมูลหลายรายการ (SIMD)
  • สตรีมคำสั่งหลายสตรีมข้อมูลเดียว (MISD)
  • สตรีมคำสั่งหลายสตรีมข้อมูลหลายรายการ (MIMD)

สตรีมคำสั่งเดี่ยวสตรีมข้อมูลเดียว (SISD)

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

ข้อดีของ SISD

ข้อดีของสถาปัตยกรรม SISD มีดังนี้ -

  • ต้องใช้พลังงานน้อยลง
  • ไม่มีปัญหาของโปรโตคอลการสื่อสารที่ซับซ้อนระหว่างหลายคอร์

ข้อเสียของ SISD

ข้อเสียของสถาปัตยกรรม SISD มีดังนี้ -

  • ความเร็วของสถาปัตยกรรม SISD ถูก จำกัด เช่นเดียวกับโปรเซสเซอร์ single-core
  • ไม่เหมาะสำหรับการใช้งานขนาดใหญ่

สตรีมคำสั่งเดียวสตรีมข้อมูลหลายรายการ (SIMD)

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

ตัวอย่างที่ดีที่สุดสำหรับ SIMD คือกราฟิกการ์ด การ์ดเหล่านี้มีหน่วยประมวลผลเดี่ยวหลายร้อยหน่วย ถ้าเราพูดถึงความแตกต่างในการคำนวณระหว่าง SISD และ SIMD สำหรับอาร์เรย์การเพิ่ม[5, 15, 20] และ [15, 25, 10],สถาปัตยกรรม SISD จะต้องดำเนินการเพิ่มสามแบบ ในทางกลับกันด้วยสถาปัตยกรรม SIMD เราสามารถเพิ่มได้ในการดำเนินการเพิ่มเพียงครั้งเดียว

ข้อดีของ SIMD

ข้อดีของสถาปัตยกรรม SIMD มีดังนี้ -

  • การดำเนินการเดียวกันกับหลายองค์ประกอบสามารถทำได้โดยใช้คำสั่งเดียวเท่านั้น

  • ทรูพุตของระบบสามารถเพิ่มขึ้นได้โดยการเพิ่มจำนวนคอร์ของโปรเซสเซอร์

  • ความเร็วในการประมวลผลสูงกว่าสถาปัตยกรรม SISD

ข้อเสียของ SIMD

ข้อเสียของสถาปัตยกรรม SIMD มีดังนี้ -

  • มีการสื่อสารที่ซับซ้อนระหว่างจำนวนคอร์ของโปรเซสเซอร์
  • ต้นทุนสูงกว่าสถาปัตยกรรม SISD

สตรีมข้อมูลคำสั่งเดียว (MISD) หลายคำสั่ง

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

ตัวแทนของสถาปัตยกรรม MISD ยังไม่มีในเชิงพาณิชย์

สตรีมคำสั่งหลายคำสั่งหลายข้อมูล (MIMD)

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

มัลติโปรเซสเซอร์ปกติใช้สถาปัตยกรรม MIMD โดยพื้นฐานแล้วสถาปัตยกรรมเหล่านี้ถูกนำไปใช้ในด้านการใช้งานต่างๆเช่นการออกแบบโดยใช้คอมพิวเตอร์ช่วย / การผลิตโดยใช้คอมพิวเตอร์ช่วยจำลองการสร้างแบบจำลองสวิตช์การสื่อสารเป็นต้น

สถาปัตยกรรมหน่วยความจำที่รองรับการทำงานพร้อมกัน

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

UMA (การเข้าถึงหน่วยความจำแบบสม่ำเสมอ)

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

เมื่อโปรเซสเซอร์ทั้งหมดสามารถเข้าถึงอุปกรณ์ต่อพ่วงทั้งหมดได้อย่างเท่าเทียมกันระบบจะเรียกว่าไฟล์ symmetric multiprocessor. เมื่อโปรเซสเซอร์เพียงตัวเดียวหรือสองสามตัวสามารถเข้าถึงอุปกรณ์ต่อพ่วงได้ระบบจะเรียกว่าไฟล์asymmetric multiprocessor.

การเข้าถึงหน่วยความจำแบบไม่สม่ำเสมอ (NUMA)

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

สถาปัตยกรรมหน่วยความจำแคชเท่านั้น (COMA)

รุ่น COMA เป็นรุ่นพิเศษของรุ่น NUMA ที่นี่ความทรงจำหลักที่แจกจ่ายทั้งหมดจะถูกแปลงเป็นความทรงจำแคช

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

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

สถานะของเธรด

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

กระทู้ใหม่

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

รันได้

เมื่อเธรดเกิดใหม่เริ่มต้นเธรดจะรันได้นั่นคือกำลังรอรัน ในสถานะนี้มีทรัพยากรทั้งหมด แต่ตัวกำหนดตารางเวลางานยังไม่ได้กำหนดเวลาให้รัน

วิ่ง

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

ไม่ทำงาน / รอ

ในสถานะนี้เธรดจะหยุดชั่วคราวเนื่องจากกำลังรอการตอบสนองของคำร้องขอ I / O บางอย่างหรือรอให้การดำเนินการของเธรดอื่นเสร็จสิ้น

ตาย

เธรดที่รันได้จะเข้าสู่สถานะสิ้นสุดเมื่อทำงานเสร็จสมบูรณ์หรือถูกยกเลิก

แผนภาพต่อไปนี้แสดงวงจรชีวิตที่สมบูรณ์ของเธรด -

ประเภทของเธรด

ในส่วนนี้เราจะเห็นประเภทต่างๆของเธรด ประเภทมีคำอธิบายด้านล่าง -

เธรดระดับผู้ใช้

นี่คือเธรดที่จัดการโดยผู้ใช้

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

ตัวอย่างของเธรดระดับผู้ใช้ ได้แก่ -

  • เธรด Java
  • เธรด POSIX

ข้อดีของเธรดระดับผู้ใช้

ต่อไปนี้เป็นข้อดีที่แตกต่างกันของเธรดระดับผู้ใช้ -

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

ข้อเสียของเธรดระดับผู้ใช้

ต่อไปนี้เป็นข้อเสียที่แตกต่างกันของเธรดระดับผู้ใช้ -

  • ในระบบปฏิบัติการทั่วไปการเรียกระบบส่วนใหญ่จะบล็อก
  • แอปพลิเคชันมัลติเธรดไม่สามารถใช้ประโยชน์จากการประมวลผลหลายขั้นตอนได้

เธรดระดับเคอร์เนล

เธรดที่ถูกจัดการของระบบปฏิบัติการทำงานบนเคอร์เนลซึ่งเป็นแกนหลักของระบบปฏิบัติการ

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

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

ข้อดีของหัวข้อระดับเคอร์เนล

ต่อไปนี้เป็นข้อดีที่แตกต่างกันของเธรดระดับเคอร์เนล -

  • เคอร์เนลสามารถกำหนดเวลาเธรดหลายเธรดได้พร้อมกันจากกระบวนการเดียวกันในหลายกระบวนการ

  • หากเธรดหนึ่งเธรดในกระบวนการถูกบล็อกเคอร์เนลสามารถกำหนดเวลาเธรดอื่นของกระบวนการเดียวกันได้

  • กิจวัตรของเคอร์เนลเองสามารถเป็นแบบมัลติเธรดได้

ข้อเสียของหัวข้อระดับเคอร์เนล

  • โดยทั่วไปเคอร์เนลเธรดจะสร้างและจัดการได้ช้ากว่าเธรดผู้ใช้

  • การถ่ายโอนการควบคุมจากเธรดหนึ่งไปยังอีกเธรดภายในกระบวนการเดียวกันจำเป็นต้องมีการสลับโหมดไปที่เคอร์เนล

บล็อกควบคุมเธรด - TCB

Thread Control Block (TCB) อาจถูกกำหนดให้เป็นโครงสร้างข้อมูลในเคอร์เนลของระบบปฏิบัติการที่มีข้อมูลเกี่ยวกับเธรดเป็นหลัก ข้อมูลเฉพาะเธรดที่จัดเก็บใน TCB จะเน้นข้อมูลสำคัญบางอย่างเกี่ยวกับแต่ละกระบวนการ

พิจารณาประเด็นต่อไปนี้ที่เกี่ยวข้องกับเธรดที่มีอยู่ใน TCB -

  • Thread identification - เป็นรหัสเธรดเฉพาะ (tid) ที่กำหนดให้กับเธรดใหม่ทุกรายการ

  • Thread state - มีข้อมูลที่เกี่ยวข้องกับสถานะ (Running, Runnable, Non-Running, Dead) ของเธรด

  • Program Counter (PC) - ชี้ไปที่คำสั่งโปรแกรมปัจจุบันของเธรด

  • Register set - ประกอบด้วยค่ารีจิสเตอร์ของเธรดที่กำหนดให้สำหรับการคำนวณ

  • Stack Pointer- ชี้ไปที่สแต็กของเธรดในกระบวนการ ประกอบด้วยตัวแปรโลคัลภายใต้ขอบเขตของเธรด

  • Pointer to PCB - ประกอบด้วยตัวชี้ไปยังกระบวนการที่สร้างเธรดนั้น

ความสัมพันธ์ระหว่างกระบวนการและเธรด

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

ตารางต่อไปนี้แสดงการเปรียบเทียบระหว่างกระบวนการและเธรด -

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

แนวคิดของมัลติเธรด

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

สามารถเข้าใจแนวคิดของมัลติเธรดได้ด้วยความช่วยเหลือของตัวอย่างต่อไปนี้

ตัวอย่าง

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

แผนภาพต่อไปนี้ช่วยให้เราเข้าใจว่ามีเธรดจำนวนมากในหน่วยความจำอย่างไร -

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

ข้อดีของมัลติเธรด

ตอนนี้ให้เราดูข้อดีบางประการของมัลติเธรด ข้อดีมีดังนี้ -

  • Speed of communication - Multithreading ช่วยเพิ่มความเร็วในการคำนวณเนื่องจากแต่ละคอร์หรือโปรเซสเซอร์จัดการเธรดที่แยกจากกันพร้อมกัน

  • Program remains responsive - ช่วยให้โปรแกรมยังคงตอบสนองเนื่องจากเธรดหนึ่งรออินพุตและอีกเธรดหนึ่งรัน GUI ในเวลาเดียวกัน

  • Access to global variables - ในมัลติเธรดเธรดทั้งหมดของกระบวนการเฉพาะสามารถเข้าถึงตัวแปรโกลบอลและหากมีการเปลี่ยนแปลงใด ๆ ในตัวแปรโกลบอลเธรดอื่น ๆ ก็จะมองเห็นได้เช่นกัน

  • Utilization of resources - การรันหลายเธรดในแต่ละโปรแกรมทำให้ใช้ CPU ได้ดีขึ้นและเวลาว่างของ CPU จะน้อยลง

  • Sharing of data - ไม่มีความต้องการพื้นที่เพิ่มเติมสำหรับแต่ละเธรดเนื่องจากเธรดภายในโปรแกรมสามารถแชร์ข้อมูลเดียวกันได้

จุดด้อยของมัลติเธรด

ตอนนี้ให้เราดูข้อเสียบางประการของการมัลติเธรด ข้อเสียมีดังนี้ -

  • Not suitable for single processor system - มัลติเธรดพบว่ายากที่จะบรรลุประสิทธิภาพในแง่ของความเร็วในการคำนวณบนระบบโปรเซสเซอร์เดี่ยวเมื่อเทียบกับประสิทธิภาพของระบบมัลติโปรเซสเซอร์

  • Issue of security - อย่างที่เราทราบกันดีว่าเธรดทั้งหมดในโปรแกรมแชร์ข้อมูลเดียวกันดังนั้นจึงมีปัญหาด้านความปลอดภัยอยู่เสมอเนื่องจากเธรดที่ไม่รู้จักสามารถเปลี่ยนแปลงข้อมูลได้

  • Increase in complexity - มัลติเธรดสามารถเพิ่มความซับซ้อนของโปรแกรมและการดีบักกลายเป็นเรื่องยาก

  • Lead to deadlock state - มัลติเธรดสามารถนำโปรแกรมไปสู่ความเสี่ยงที่อาจจะเข้าสู่สภาวะชะงักงัน

  • Synchronization required- ต้องมีการซิงโครไนซ์เพื่อหลีกเลี่ยงการกีดกันซึ่งกันและกัน สิ่งนี้นำไปสู่การใช้หน่วยความจำและ CPU มากขึ้น

ในบทนี้เราจะเรียนรู้วิธีการใช้งานเธรดใน Python

โมดูล Python สำหรับการใช้งานเธรด

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

  • <_thread>module

  • <threading>module

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

<_thread> โมดูล

ใน Python เวอร์ชันก่อนหน้าเรามีไฟล์ <thread>โมดูล แต่ได้รับการพิจารณาว่า "เลิกใช้งาน" เป็นเวลานานแล้ว ผู้ใช้ได้รับการสนับสนุนให้ใช้ไฟล์<threading>โมดูลแทน ดังนั้นใน Python 3 โมดูล "เธรด" จึงไม่สามารถใช้งานได้อีกต่อไป ได้รับการเปลี่ยนชื่อเป็น "<_thread>"สำหรับความเข้ากันไม่ได้ย้อนหลังใน Python3

ในการสร้างเธรดใหม่ด้วยความช่วยเหลือของไฟล์ <_thread> เราต้องเรียกไฟล์ start_new_threadวิธีการของมัน การทำงานของวิธีนี้สามารถเข้าใจได้ด้วยความช่วยเหลือของไวยากรณ์ต่อไปนี้ -

_thread.start_new_thread ( function, args[, kwargs] )

ที่นี่ -

  • args เป็นข้อโต้แย้งที่ทวีคูณ

  • kwargs เป็นพจนานุกรมทางเลือกของอาร์กิวเมนต์คำหลัก

หากเราต้องการเรียกใช้ฟังก์ชันโดยไม่ส่งอาร์กิวเมนต์เราจำเป็นต้องใช้อาร์กิวเมนต์ที่ว่างเปล่าใน args.

การเรียกเมธอดนี้จะส่งคืนทันทีเธรดลูกเริ่มต้นและเรียกใช้ฟังก์ชันด้วยรายการที่ส่งผ่านถ้ามีของอาร์กิวเมนต์ เธรดจะสิ้นสุดเมื่อและเมื่อฟังก์ชันกลับมา

ตัวอย่าง

ต่อไปนี้เป็นตัวอย่างสำหรับการสร้างเธรดใหม่โดยใช้ไฟล์ <_thread>โมดูล. เรากำลังใช้ start_new_thread () วิธีการที่นี่

import _thread
import time

def print_time( threadName, delay):
   count = 0
   while count < 5:
      time.sleep(delay)
      count += 1
      print ("%s: %s" % ( threadName, time.ctime(time.time()) ))

try:
   _thread.start_new_thread( print_time, ("Thread-1", 2, ) )
   _thread.start_new_thread( print_time, ("Thread-2", 4, ) )
except:
   print ("Error: unable to start thread")
while 1:
   pass

เอาต์พุต

ผลลัพธ์ต่อไปนี้จะช่วยให้เราเข้าใจการสร้างเธรดใหม่ด้วยความช่วยเหลือของไฟล์ <_thread> โมดูล.

Thread-1: Mon Apr 23 10:03:33 2018
Thread-2: Mon Apr 23 10:03:35 2018
Thread-1: Mon Apr 23 10:03:35 2018
Thread-1: Mon Apr 23 10:03:37 2018
Thread-2: Mon Apr 23 10:03:39 2018
Thread-1: Mon Apr 23 10:03:39 2018
Thread-1: Mon Apr 23 10:03:41 2018
Thread-2: Mon Apr 23 10:03:43 2018
Thread-2: Mon Apr 23 10:03:47 2018
Thread-2: Mon Apr 23 10:03:51 2018

โมดูล <threading>

<threading>โมดูลดำเนินการในลักษณะเชิงวัตถุและถือว่าทุกเธรดเป็นวัตถุ ดังนั้นจึงให้การสนับสนุนเธรดระดับสูงที่ทรงพลังและมีประสิทธิภาพมากกว่าโมดูล <_thread> โมดูลนี้มาพร้อมกับ Python 2.4

วิธีการเพิ่มเติมในโมดูล <threading>

<threading> โมดูลประกอบด้วยวิธีการทั้งหมดของไฟล์ <_thread>โมดูล แต่ก็มีวิธีการเพิ่มเติมเช่นกัน วิธีการเพิ่มเติมมีดังนี้ -

  • threading.activeCount() - วิธีนี้จะคืนค่าจำนวนของวัตถุเธรดที่ใช้งานอยู่

  • threading.currentThread() - วิธีนี้ส่งคืนจำนวนของวัตถุเธรดในการควบคุมเธรดของผู้โทร

  • threading.enumerate() - วิธีนี้ส่งคืนรายการของวัตถุเธรดทั้งหมดที่ใช้งานอยู่

  • สำหรับการใช้เธรดไฟล์ <threading> โมดูลมีไฟล์ Thread คลาสซึ่งมีวิธีการดังต่อไปนี้ -

    • run() - วิธีการ run () เป็นจุดเริ่มต้นสำหรับเธรด

    • start() - เมธอด start () เริ่มต้นเธรดโดยเรียกใช้เมธอด run

    • join([time]) - การเข้าร่วม () รอให้เธรดสิ้นสุดลง

    • isAlive() - วิธี isAlive () ตรวจสอบว่าเธรดยังคงทำงานอยู่หรือไม่

    • getName() - เมธอด getName () ส่งคืนชื่อของเธรด

    • setName() - เมธอด setName () ตั้งชื่อของเธรด

จะสร้างเธรดโดยใช้โมดูล <threading> ได้อย่างไร?

ในส่วนนี้เราจะเรียนรู้วิธีสร้างเธรดโดยใช้ไฟล์ <threading>โมดูล. ทำตามขั้นตอนเหล่านี้เพื่อสร้างเธรดใหม่โดยใช้โมดูล <threading> -

  • Step 1 - ในขั้นตอนนี้เราจำเป็นต้องกำหนดคลาสย่อยใหม่ของไฟล์ Thread ชั้นเรียน.

  • Step 2 - จากนั้นในการเพิ่มอาร์กิวเมนต์เพิ่มเติมเราจำเป็นต้องลบล้างไฟล์ __init__(self [,args]) วิธี.

  • Step 3 - ในขั้นตอนนี้เราจำเป็นต้องลบล้างเมธอด run (self [, args]) เพื่อใช้สิ่งที่เธรดควรทำเมื่อเริ่มต้น

  • ตอนนี้หลังจากสร้างไฟล์ Thread คลาสย่อยเราสามารถสร้างอินสแตนซ์ของมันแล้วเริ่มเธรดใหม่โดยเรียกใช้ไฟล์ start()ซึ่งจะเรียกไฟล์ run() วิธี.

ตัวอย่าง

ลองพิจารณาตัวอย่างนี้เพื่อเรียนรู้วิธีสร้างเธรดใหม่โดยใช้ไฟล์ <threading> โมดูล.

import threading
import time
exitFlag = 0

class myThread (threading.Thread):
   def __init__(self, threadID, name, counter):
      threading.Thread.__init__(self)
      self.threadID = threadID
      self.name = name
      self.counter = counter
   def run(self):
      print ("Starting " + self.name)
      print_time(self.name, self.counter, 5)
      print ("Exiting " + self.name)
def print_time(threadName, delay, counter):
   while counter:
      if exitFlag:
         threadName.exit()
      time.sleep(delay)
      print ("%s: %s" % (threadName, time.ctime(time.time())))
      counter -= 1

thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("Exiting Main Thread")
Starting Thread-1
Starting Thread-2

เอาต์พุต

ตอนนี้พิจารณาผลลัพธ์ต่อไปนี้ -

Thread-1: Mon Apr 23 10:52:09 2018
Thread-1: Mon Apr 23 10:52:10 2018
Thread-2: Mon Apr 23 10:52:10 2018
Thread-1: Mon Apr 23 10:52:11 2018
Thread-1: Mon Apr 23 10:52:12 2018
Thread-2: Mon Apr 23 10:52:12 2018
Thread-1: Mon Apr 23 10:52:13 2018
Exiting Thread-1
Thread-2: Mon Apr 23 10:52:14 2018
Thread-2: Mon Apr 23 10:52:16 2018
Thread-2: Mon Apr 23 10:52:18 2018
Exiting Thread-2
Exiting Main Thread

โปรแกรม Python สำหรับสถานะเธรดต่างๆ

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

โปรแกรม Python ต่อไปนี้โดยใช้เมธอด start (), sleep () และ join () จะแสดงให้เห็นว่าเธรดเข้ามาในสถานะการทำงานการรอและการตายตามลำดับอย่างไร

Step 1 - นำเข้าโมดูลที่จำเป็น <threading> และ <time>

import threading
import time

Step 2 - กำหนดฟังก์ชันซึ่งจะถูกเรียกใช้ในขณะที่สร้างเธรด

def thread_states():
   print("Thread entered in running state")

Step 3 - เราใช้วิธีการ sleep () ของโมดูลเวลาเพื่อให้เธรดของเรารอการพูด 2 วินาที

time.sleep(2)

Step 4 - ตอนนี้เรากำลังสร้างเธรดชื่อ T1 ซึ่งใช้อาร์กิวเมนต์ของฟังก์ชันที่กำหนดไว้ด้านบน

T1 = threading.Thread(target=thread_states)

Step 5- ตอนนี้ด้วยความช่วยเหลือของฟังก์ชัน start () เราสามารถเริ่มเธรดของเราได้ มันจะสร้างข้อความซึ่งกำหนดโดยเราในขณะที่กำหนดฟังก์ชัน

T1.start()
Thread entered in running state

Step 6 - ในที่สุดเราสามารถฆ่าเธรดด้วยเมธอด join () ได้หลังจากเสร็จสิ้นการดำเนินการ

T1.join()

เริ่มต้นเธรดใน Python

ใน python เราสามารถเริ่มเธรดใหม่ได้หลายวิธี แต่วิธีที่ง่ายที่สุดคือกำหนดเป็นฟังก์ชันเดียว หลังจากกำหนดฟังก์ชันแล้วเราสามารถส่งผ่านสิ่งนี้เป็นเป้าหมายสำหรับไฟล์threading.Threadวัตถุและอื่น ๆ รันโค้ด Python ต่อไปนี้เพื่อทำความเข้าใจว่าฟังก์ชันทำงานอย่างไร -

import threading
import time
import random
def Thread_execution(i):
   print("Execution of Thread {} started\n".format(i))
   sleepTime = random.randint(1,4)
   time.sleep(sleepTime)
   print("Execution of Thread {} finished".format(i))
for i in range(4):
   thread = threading.Thread(target=Thread_execution, args=(i,))
   thread.start()
   print("Active Threads:" , threading.enumerate())

เอาต์พุต

Execution of Thread 0 started
Active Threads:
   [<_MainThread(MainThread, started 6040)>,
      <HistorySavingThread(IPythonHistorySavingThread, started 5968)>,
      <Thread(Thread-3576, started 3932)>]

Execution of Thread 1 started
Active Threads:
   [<_MainThread(MainThread, started 6040)>,
      <HistorySavingThread(IPythonHistorySavingThread, started 5968)>,
      <Thread(Thread-3576, started 3932)>,
      <Thread(Thread-3577, started 3080)>]

Execution of Thread 2 started
Active Threads:
   [<_MainThread(MainThread, started 6040)>,
      <HistorySavingThread(IPythonHistorySavingThread, started 5968)>,
      <Thread(Thread-3576, started 3932)>,
      <Thread(Thread-3577, started 3080)>,
      <Thread(Thread-3578, started 2268)>]

Execution of Thread 3 started
Active Threads:
   [<_MainThread(MainThread, started 6040)>,
      <HistorySavingThread(IPythonHistorySavingThread, started 5968)>,
      <Thread(Thread-3576, started 3932)>,
      <Thread(Thread-3577, started 3080)>,
      <Thread(Thread-3578, started 2268)>,
      <Thread(Thread-3579, started 4520)>]
Execution of Thread 0 finished
Execution of Thread 1 finished
Execution of Thread 2 finished
Execution of Thread 3 finished

เธรด Daemon ใน Python

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

import threading
import time

def nondaemonThread():
   print("starting my thread")
   time.sleep(8)
   print("ending my thread")
def daemonThread():
   while True:
   print("Hello")
   time.sleep(2)
if __name__ == '__main__':
   nondaemonThread = threading.Thread(target = nondaemonThread)
   daemonThread = threading.Thread(target = daemonThread)
   daemonThread.setDaemon(True)
   daemonThread.start()
   nondaemonThread.start()

ในโค้ดด้านบนมีสองฟังก์ชันคือ >nondaemonThread() และ >daemonThread(). ฟังก์ชันแรกจะพิมพ์สถานะและเข้าสู่โหมดสลีปหลังจาก 8 วินาทีในขณะที่ฟังก์ชัน deamonThread () พิมพ์สวัสดีหลังจากทุกๆ 2 วินาทีไปเรื่อย ๆ เราสามารถเข้าใจความแตกต่างระหว่างเธรด nondaemon และ daemon ด้วยความช่วยเหลือของเอาต์พุตต่อไปนี้ -

Hello

starting my thread
Hello
Hello
Hello
Hello
ending my thread
Hello
Hello
Hello
Hello
Hello

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

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

ปัญหาในการซิงโครไนซ์เธรด

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

  • Deadlock
  • สภาพการแข่งขัน

สภาพการแข่งขัน

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

ตัวอย่าง

ลองพิจารณาตัวอย่างนี้เพื่อทำความเข้าใจแนวคิดเรื่องสภาพการแข่งขัน -

Step 1 - ในขั้นตอนนี้เราต้องนำเข้าโมดูลเธรด -

import threading

Step 2 - ตอนนี้กำหนดตัวแปรส่วนกลางพูด x พร้อมกับค่าเป็น 0 -

x = 0

Step 3 - ตอนนี้เราต้องกำหนดไฟล์ increment_global() ซึ่งจะเพิ่มขึ้นทีละ 1 ในฟังก์ชันโกลบอลนี้ x -

def increment_global():

   global x
   x += 1

Step 4 - ในขั้นตอนนี้เราจะกำหนดไฟล์ taskofThread()ฟังก์ชันซึ่งจะเรียกใช้ฟังก์ชัน Increment_global () ตามจำนวนครั้งที่ระบุ สำหรับตัวอย่างของเราคือ 50000 ครั้ง -

def taskofThread():

   for _ in range(50000):
      increment_global()

Step 5- ตอนนี้กำหนดฟังก์ชัน main () ที่สร้างเธรด t1 และ t2 ทั้งสองอย่างจะเริ่มต้นด้วยความช่วยเหลือของฟังก์ชัน start () และรอจนกว่าพวกเขาจะทำงานเสร็จด้วยความช่วยเหลือของฟังก์ชัน join ()

def main():
   global x
   x = 0
   
   t1 = threading.Thread(target= taskofThread)
   t2 = threading.Thread(target= taskofThread)

   t1.start()
   t2.start()

   t1.join()
   t2.join()

Step 6- ตอนนี้เราต้องระบุช่วงสำหรับจำนวนการวนซ้ำที่เราต้องการเรียกใช้ฟังก์ชัน main () นี่เราเรียกมันมา 5 ครั้งแล้ว

if __name__ == "__main__":
   for i in range(5):
      main()
      print("x = {1} after Iteration {0}".format(i,x))

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

เอาต์พุต

x = 100000 after Iteration 0
x = 54034 after Iteration 1
x = 80230 after Iteration 2
x = 93602 after Iteration 3
x = 93289 after Iteration 4

การจัดการกับสภาพการแข่งขันโดยใช้การล็อก

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

ได้รับ () วิธีการ

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

  • With value set to True - ถ้าเมธอด acquire () ถูกเรียกใช้ด้วย True ซึ่งเป็นอาร์กิวเมนต์เริ่มต้นการดำเนินการเธรดจะถูกบล็อกจนกว่าการล็อกจะถูกปลดล็อก

  • With value set to False - ถ้าเมธอด acquire () ถูกเรียกใช้ด้วย False ซึ่งไม่ใช่อาร์กิวเมนต์เริ่มต้นการดำเนินการเธรดจะไม่ถูกบล็อกจนกว่าจะตั้งค่าเป็น true กล่าวคือจนกว่าจะถูกล็อก

ปล่อย () วิธีการ

วิธีนี้ใช้เพื่อคลายล็อก ต่อไปนี้เป็นงานสำคัญบางประการที่เกี่ยวข้องกับวิธีนี้ -

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

  • มันจะเพิ่มไฟล์ ThreadError หากล็อคถูกปลดล็อคแล้ว

ตอนนี้เราสามารถเขียนโปรแกรมข้างต้นใหม่ด้วยคลาสล็อคและวิธีการเพื่อหลีกเลี่ยงสภาวะการแข่งขัน เราจำเป็นต้องกำหนดเมธอด taskofThread () ด้วยอาร์กิวเมนต์ lock จากนั้นจำเป็นต้องใช้เมธอด acquire () และ release () ในการบล็อกและไม่บล็อกการล็อกเพื่อหลีกเลี่ยงสภาวะการแข่งขัน

ตัวอย่าง

ต่อไปนี้เป็นตัวอย่างของโปรแกรม python เพื่อทำความเข้าใจแนวคิดของการล็อกเพื่อจัดการกับสภาพการแข่งขัน -

import threading

x = 0

def increment_global():

   global x
   x += 1

def taskofThread(lock):

   for _ in range(50000):
      lock.acquire()
      increment_global()
      lock.release()

def main():
   global x
   x = 0

   lock = threading.Lock()
   t1 = threading.Thread(target = taskofThread, args = (lock,))
   t2 = threading.Thread(target = taskofThread, args = (lock,))

   t1.start()
   t2.start()

   t1.join()
   t2.join()

if __name__ == "__main__":
   for i in range(5):
      main()
      print("x = {1} after Iteration {0}".format(i,x))

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

เอาต์พุต

x = 100000 after Iteration 0
x = 100000 after Iteration 1
x = 100000 after Iteration 2
x = 100000 after Iteration 3
x = 100000 after Iteration 4

Deadlocks - ปัญหานักปรัชญาการรับประทานอาหาร

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

Edsger Dijkstra เดิมนำเสนอปัญหาปราชญ์ด้านการรับประทานอาหารซึ่งเป็นหนึ่งในภาพประกอบที่มีชื่อเสียงของปัญหาใหญ่ที่สุดของระบบที่เกิดขึ้นพร้อมกันที่เรียกว่าการหยุดชะงัก

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

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

การหยุดชะงักในระบบพร้อมกัน

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

วิธีแก้ปัญหาด้วยโปรแกรม Python

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

ตัวอย่าง

โปรแกรม Python ต่อไปนี้จะช่วยเราหาวิธีแก้ปัญหานักปรัชญาการรับประทานอาหาร -

import threading
import random
import time

class DiningPhilosopher(threading.Thread):

   running = True

   def __init__(self, xname, Leftfork, Rightfork):
   threading.Thread.__init__(self)
   self.name = xname
   self.Leftfork = Leftfork
   self.Rightfork = Rightfork

   def run(self):
   while(self.running):
      time.sleep( random.uniform(3,13))
      print ('%s is hungry.' % self.name)
      self.dine()

   def dine(self):
   fork1, fork2 = self.Leftfork, self.Rightfork

   while self.running:
      fork1.acquire(True)
      locked = fork2.acquire(False)
	  if locked: break
      fork1.release()
      print ('%s swaps forks' % self.name)
      fork1, fork2 = fork2, fork1
   else:
      return

   self.dining()
   fork2.release()
   fork1.release()

   def dining(self):
   print ('%s starts eating '% self.name)
   time.sleep(random.uniform(1,10))
   print ('%s finishes eating and now thinking.' % self.name)

def Dining_Philosophers():
   forks = [threading.Lock() for n in range(5)]
   philosopherNames = ('1st','2nd','3rd','4th', '5th')

   philosophers= [DiningPhilosopher(philosopherNames[i], forks[i%5], forks[(i+1)%5]) \
      for i in range(5)]

   random.seed()
   DiningPhilosopher.running = True
   for p in philosophers: p.start()
   time.sleep(30)
   DiningPhilosopher.running = False
   print (" It is finishing.")

Dining_Philosophers()

โปรแกรมข้างต้นใช้แนวคิดของนักปรัชญาที่ละโมบและใจกว้าง โปรแกรมนี้ยังใช้ไฟล์acquire() และ release() วิธีการของ Lock คลาสของ <threading>โมดูล. เราสามารถดูวิธีแก้ปัญหาได้ในผลลัพธ์ต่อไปนี้ -

เอาต์พุต

4th is hungry.
4th starts eating
1st is hungry.
1st starts eating
2nd is hungry.
5th is hungry.
3rd is hungry.
1st finishes eating and now thinking.3rd swaps forks
2nd starts eating
4th finishes eating and now thinking.
3rd swaps forks5th starts eating
5th finishes eating and now thinking.
4th is hungry.
4th starts eating
2nd finishes eating and now thinking.
3rd swaps forks
1st is hungry.
1st starts eating
4th finishes eating and now thinking.
3rd starts eating
5th is hungry.
5th swaps forks
1st finishes eating and now thinking.
5th starts eating
2nd is hungry.
2nd swaps forks
4th is hungry.
5th finishes eating and now thinking.
3rd finishes eating and now thinking.
2nd starts eating 4th starts eating
It is finishing.

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

พิจารณาประเด็นสำคัญต่อไปนี้ที่เกี่ยวข้องกับการสื่อสารระหว่างเธรด -

  • No performance gain - หากเราไม่สามารถสื่อสารอย่างเหมาะสมระหว่างเธรดและกระบวนการได้ประสิทธิภาพที่ได้รับจากการทำงานพร้อมกันและการขนานกันก็ไม่มีประโยชน์

  • Accomplish task properly - หากไม่มีกลไกการสื่อสารที่เหมาะสมระหว่างเธรดงานที่ได้รับมอบหมายจะไม่สามารถดำเนินการได้อย่างถูกต้อง

  • More efficient than inter-process communication - การสื่อสารระหว่างเธรดมีประสิทธิภาพและใช้งานง่ายกว่าการสื่อสารระหว่างกระบวนการเนื่องจากเธรดทั้งหมดภายในกระบวนการใช้พื้นที่แอดเดรสเดียวกันและไม่จำเป็นต้องใช้หน่วยความจำร่วม

โครงสร้างข้อมูล Python สำหรับการสื่อสารที่ปลอดภัยของเธรด

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

ชุด

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

ตัวอย่าง

นี่คือตัวอย่าง Python ของการขยายคลาส -

class extend_class(set):
   def __init__(self, *args, **kwargs):
      self._lock = Lock()
      super(extend_class, self).__init__(*args, **kwargs)

   def add(self, elem):
      self._lock.acquire()
	  try:
      super(extend_class, self).add(elem)
      finally:
      self._lock.release()
  
   def delete(self, elem):
      self._lock.acquire()
      try:
      super(extend_class, self).delete(elem)
      finally:
      self._lock.release()

ในตัวอย่างข้างต้นคลาสอ็อบเจ็กต์ชื่อ extend_class ได้รับการกำหนดซึ่งสืบทอดมาจาก Python set class. อ็อบเจ็กต์ล็อกถูกสร้างขึ้นภายในคอนสตรัคเตอร์ของคลาสนี้ ตอนนี้มีสองฟังก์ชั่น -add() และ delete(). ฟังก์ชันเหล่านี้ถูกกำหนดและปลอดภัยต่อเธรด ทั้งคู่พึ่งพาไฟล์super ฟังก์ชันคลาสที่มีข้อยกเว้นคีย์เดียว

มัณฑนากร

นี่เป็นอีกวิธีการสำคัญสำหรับการสื่อสารที่ปลอดภัยต่อเธรดคือการใช้มัณฑนากร

ตัวอย่าง

ลองพิจารณาตัวอย่าง Python ที่แสดงวิธีใช้มัณฑนากร & mminus;

def lock_decorator(method):

   def new_deco_method(self, *args, **kwargs):
      with self._lock:
         return method(self, *args, **kwargs)
return new_deco_method

class Decorator_class(set):
   def __init__(self, *args, **kwargs):
      self._lock = Lock()
      super(Decorator_class, self).__init__(*args, **kwargs)

   @lock_decorator
   def add(self, *args, **kwargs):
      return super(Decorator_class, self).add(elem)
   @lock_decorator
   def delete(self, *args, **kwargs):
      return super(Decorator_class, self).delete(elem)

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

รายการ

โครงสร้างข้อมูลรายการเป็นโครงสร้างที่ปลอดภัยรวดเร็วและง่ายสำหรับการจัดเก็บชั่วคราวในหน่วยความจำ ใน Cpython GIL ป้องกันการเข้าถึงพร้อมกัน อย่างที่เราทราบกันดีว่ารายการนั้นปลอดภัยสำหรับเธรด แต่ข้อมูลที่อยู่ในนั้นจะเป็นอย่างไร จริงๆแล้วข้อมูลของรายการไม่ได้รับการปกป้อง ตัวอย่างเช่น,L.append(x)ไม่รับประกันว่าจะส่งคืนผลลัพธ์ที่คาดหวังหากเธรดอื่นพยายามทำสิ่งเดียวกัน เนื่องจากแม้ว่าappend() เป็นการดำเนินการแบบอะตอมและเธรดที่ปลอดภัย แต่เธรดอื่นกำลังพยายามแก้ไขข้อมูลของรายการในแบบพร้อมกันดังนั้นเราจึงสามารถเห็นผลข้างเคียงของสภาวะการแข่งขันที่มีต่อผลลัพธ์

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

ปฏิบัติการปรมาณูอื่น ๆ ในรายการมีดังนี้ -

L.append(x)
L1.extend(L2)
x = L[i]
x = L.pop()
L1[i:j] = L2
L.sort()
x = y
x.field = y
D[x] = y
D1.update(D2)
D.keys()

ที่นี่ -

  • L, L1, L2 ทั้งหมดเป็นรายการ
  • D, D1, D2 เป็นคำสั่ง
  • x, y คือวัตถุ
  • ผม j เป็น ints

คิว

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

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

ประเภทของคิว

ในส่วนนี้เราจะได้รับเกี่ยวกับคิวประเภทต่างๆ Python มีคิวให้ใช้สามตัวเลือกจากไฟล์<queue> โมดูล -

  • คิวปกติ (FIFO ก่อนออกก่อน)
  • LIFO สุดท้ายก่อนออก
  • Priority

เราจะเรียนรู้เกี่ยวกับคิวต่างๆในส่วนต่อไป

คิวปกติ (FIFO ก่อนออกก่อน)

เป็นการนำเสนอคิวที่ใช้บ่อยที่สุดที่เสนอโดย Python ในกลไกการเข้าคิวนี้ใครจะมาก่อนจะได้รับบริการก่อน FIFO เรียกอีกอย่างว่าคิวปกติ คิว FIFO สามารถแสดงได้ดังนี้ -

การใช้งาน Python ของคิว FIFO

ใน python คิว FIFO สามารถใช้งานได้กับเธรดเดี่ยวและมัลติเธรด

คิว FIFO พร้อมเธรดเดียว

สำหรับการนำคิว FIFO ไปใช้กับเธรดเดียวไฟล์ Queueคลาสจะใช้คอนเทนเนอร์แบบเข้าก่อนออกก่อนขั้นพื้นฐาน องค์ประกอบจะถูกเพิ่มลงใน "ส่วนท้าย" ของลำดับโดยใช้put()และนำออกจากปลายอีกด้านหนึ่งโดยใช้ get().

ตัวอย่าง

ต่อไปนี้เป็นโปรแกรม Python สำหรับใช้คิว FIFO ด้วยเธรดเดียว -

import queue

q = queue.Queue()

for i in range(8):
   q.put("item-" + str(i))

while not q.empty():
   print (q.get(), end = " ")

เอาต์พุต

item-0 item-1 item-2 item-3 item-4 item-5 item-6 item-7

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

คิว FIFO ที่มีหลายเธรด

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

ตัวอย่าง

ต่อไปนี้เป็นโปรแกรม Python สำหรับใช้คิว FIFO ที่มีหลายเธรด

import threading
import queue
import random
import time
def myqueue(queue):
   while not queue.empty():
   item = queue.get()
   if item is None:
   break
   print("{} removed {} from the queue".format(threading.current_thread(), item))
   queue.task_done()
   time.sleep(2)
q = queue.Queue()
for i in range(5):
   q.put(i)
threads = []
for i in range(4):
   thread = threading.Thread(target=myqueue, args=(q,))
   thread.start()
   threads.append(thread)
for thread in threads:
   thread.join()

เอาต์พุต

<Thread(Thread-3654, started 5044)> removed 0 from the queue
<Thread(Thread-3655, started 3144)> removed 1 from the queue
<Thread(Thread-3656, started 6996)> removed 2 from the queue
<Thread(Thread-3657, started 2672)> removed 3 from the queue
<Thread(Thread-3654, started 5044)> removed 4 from the queue

LIFO สุดท้ายในคิวก่อนออก

คิวนี้ใช้การเปรียบเทียบที่ตรงกันข้ามกับคิว FIFO (First in First Out) ในกลไกการจัดคิวนี้ผู้ที่มาสุดท้ายจะได้รับบริการก่อน ซึ่งคล้ายกับการใช้โครงสร้างข้อมูลสแต็ก คิว LIFO พิสูจน์ได้ว่ามีประโยชน์ในขณะที่ใช้การค้นหาเชิงลึกก่อนเช่นอัลกอริทึมของปัญญาประดิษฐ์

การใช้งาน Python ของคิว LIFO

ใน python สามารถใช้คิว LIFO กับเธรดเดี่ยวและมัลติเธรดได้

LIFO คิวด้วยเธรดเดียว

สำหรับการนำคิว LIFO ไปใช้กับเธรดเดียวไฟล์ Queue คลาสจะใช้งานคอนเทนเนอร์ที่เข้าก่อนออกก่อนขั้นพื้นฐานโดยใช้โครงสร้าง Queue.LifoQueue. ตอนนี้โทรput()องค์ประกอบจะถูกเพิ่มเข้าไปในส่วนหัวของภาชนะและนำออกจากหัวด้วยเมื่อใช้ get().

ตัวอย่าง

ต่อไปนี้เป็นโปรแกรม Python สำหรับการใช้งานคิว LIFO ด้วยเธรดเดียว -

import queue

q = queue.LifoQueue()

for i in range(8):
   q.put("item-" + str(i))

while not q.empty():
   print (q.get(), end=" ")
Output:
item-7 item-6 item-5 item-4 item-3 item-2 item-1 item-0

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

LIFO คิวที่มีหลายเธรด

การนำไปใช้นั้นคล้ายกับที่เราได้ดำเนินการตามคิว FIFO ที่มีหลายเธรด ข้อแตกต่างเพียงอย่างเดียวคือเราต้องใช้ไฟล์Queue คลาสที่จะใช้งานคอนเทนเนอร์แบบ last-in, first-out พื้นฐานโดยใช้โครงสร้าง Queue.LifoQueue.

ตัวอย่าง

ต่อไปนี้เป็นโปรแกรม Python สำหรับการใช้งานคิว LIFO ที่มีหลายเธรด -

import threading
import queue
import random
import time
def myqueue(queue):
   while not queue.empty():
      item = queue.get()
      if item is None:
      break
	  print("{} removed {} from the queue".format(threading.current_thread(), item))
      queue.task_done()
      time.sleep(2)
q = queue.LifoQueue()
for i in range(5):
   q.put(i)
threads = []
for i in range(4):
   thread = threading.Thread(target=myqueue, args=(q,))
   thread.start()
   threads.append(thread)
for thread in threads:
   thread.join()

เอาต์พุต

<Thread(Thread-3882, started 4928)> removed 4 from the queue
<Thread(Thread-3883, started 4364)> removed 3 from the queue
<Thread(Thread-3884, started 6908)> removed 2 from the queue
<Thread(Thread-3885, started 3584)> removed 1 from the queue
<Thread(Thread-3882, started 4928)> removed 0 from the queue

ลำดับความสำคัญ

ในคิว FIFO และ LIFO ลำดับของรายการจะเกี่ยวข้องกับลำดับการแทรก อย่างไรก็ตามมีหลายกรณีที่ลำดับความสำคัญสำคัญกว่าลำดับของการแทรก ขอให้เราพิจารณาตัวอย่างของโลกแห่งความเป็นจริง สมมติว่าเจ้าหน้าที่รักษาความปลอดภัยที่สนามบินกำลังตรวจสอบบุคคลประเภทต่างๆ คนของ VVIP, เจ้าหน้าที่สายการบิน, เจ้าหน้าที่กำหนดเอง, หมวดหมู่อาจได้รับการตรวจสอบตามลำดับความสำคัญแทนที่จะถูกตรวจสอบบนพื้นฐานของการมาถึงเช่นเดียวกับสามัญชน

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

การใช้งาน Python ของคิวลำดับความสำคัญ

ใน python ลำดับความสำคัญสามารถใช้กับเธรดเดี่ยวและมัลติเธรดได้

ลำดับความสำคัญของคิวด้วยเธรดเดียว

สำหรับการนำลำดับความสำคัญไปใช้กับเธรดเดี่ยวไฟล์ Queue คลาสจะใช้งานบนคอนเทนเนอร์ลำดับความสำคัญโดยใช้โครงสร้าง Queue.PriorityQueue. ตอนนี้โทรput()องค์ประกอบจะถูกเพิ่มด้วยค่าโดยที่ค่าต่ำสุดจะมีลำดับความสำคัญสูงสุดและด้วยเหตุนี้จึงถูกดึงมาก่อนโดยใช้ get().

ตัวอย่าง

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

import queue as Q
p_queue = Q.PriorityQueue()

p_queue.put((2, 'Urgent'))
p_queue.put((1, 'Most Urgent'))
p_queue.put((10, 'Nothing important'))
prio_queue.put((5, 'Important'))

while not p_queue.empty():
   item = p_queue.get()
   print('%s - %s' % item)

เอาต์พุต

1 – Most Urgent
2 - Urgent
5 - Important
10 – Nothing important

ในผลลัพธ์ด้านบนเราจะเห็นว่าคิวได้จัดเก็บรายการตามลำดับความสำคัญ - ค่าที่น้อยกว่ามีลำดับความสำคัญสูง

ลำดับความสำคัญของคิวที่มีหลายเธรด

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

ตัวอย่าง

โปรแกรม Python ต่อไปนี้ช่วยในการดำเนินการตามลำดับความสำคัญกับหลายเธรด -

import threading
import queue
import random
import time
def myqueue(queue):
   while not queue.empty():
      item = queue.get()
      if item is None:
      break
      print("{} removed {} from the queue".format(threading.current_thread(), item))
      queue.task_done()
      time.sleep(1)
q = queue.PriorityQueue()
for i in range(5):
   q.put(i,1)

for i in range(5):
   q.put(i,1)

threads = []
for i in range(2):
   thread = threading.Thread(target=myqueue, args=(q,))
   thread.start()
   threads.append(thread)
for thread in threads:
   thread.join()

เอาต์พุต

<Thread(Thread-4939, started 2420)> removed 0 from the queue
<Thread(Thread-4940, started 3284)> removed 0 from the queue
<Thread(Thread-4939, started 2420)> removed 1 from the queue
<Thread(Thread-4940, started 3284)> removed 1 from the queue
<Thread(Thread-4939, started 2420)> removed 2 from the queue
<Thread(Thread-4940, started 3284)> removed 2 from the queue
<Thread(Thread-4939, started 2420)> removed 3 from the queue
<Thread(Thread-4940, started 3284)> removed 3 from the queue
<Thread(Thread-4939, started 2420)> removed 4 from the queue
<Thread(Thread-4940, started 3284)> removed 4 from the queue

ในบทนี้เราจะเรียนรู้เกี่ยวกับการทดสอบการใช้งานเธรด นอกจากนี้เรายังจะได้เรียนรู้ถึงความสำคัญของการทดสอบ

ทำไมต้องทดสอบ?

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

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

การปรับปรุงคุณภาพซอฟต์แวร์

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

ความพึงพอใจของลูกค้า

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

ลดผลกระทบของคุณสมบัติใหม่

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

ประสบการณ์ของผู้ใช้

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

ลดค่าใช้จ่าย

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

จะทดสอบอะไร

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

พิจารณาประเด็นสำคัญต่อไปนี้ที่เกี่ยวข้องกับสิ่งที่ต้องทดสอบ -

  • เราต้องให้ความสำคัญกับการทดสอบการทำงานของโค้ดมากกว่าความครอบคลุมของโค้ด

  • เราจำเป็นต้องทดสอบส่วนที่สำคัญที่สุดของโค้ดก่อนจากนั้นจึงย้ายไปยังส่วนที่สำคัญน้อยกว่าของโค้ด แน่นอนจะช่วยประหยัดเวลา

  • ผู้ทดสอบจะต้องมีการทดสอบที่แตกต่างกันมากมายซึ่งสามารถผลักดันซอฟต์แวร์ให้ถึงขีด จำกัด ได้

แนวทางในการทดสอบโปรแกรมซอฟต์แวร์พร้อมกัน

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

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

การสำรวจอย่างเป็นระบบ

แนวทางนี้มีจุดมุ่งหมายเพื่อสำรวจพื้นที่ของ interleavings ให้กว้างที่สุด แนวทางดังกล่าวสามารถใช้เทคนิคเดรัจฉานบังคับและอื่น ๆ ใช้เทคนิคการลดลำดับบางส่วนหรือเทคนิคฮิวริสติกเพื่อสำรวจช่องว่างของการแทรกสอด

ขับเคลื่อนด้วยทรัพย์สิน

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

กลยุทธ์การทดสอบ

กลยุทธ์การทดสอบเรียกอีกอย่างว่าแนวทางการทดสอบ กลยุทธ์กำหนดวิธีการทดสอบ แนวทางการทดสอบมีสองเทคนิค -

เชิงรุก

แนวทางที่เริ่มต้นกระบวนการออกแบบการทดสอบโดยเร็วที่สุดเพื่อค้นหาและแก้ไขข้อบกพร่องก่อนที่จะสร้างบิลด์

ปฏิกิริยา

วิธีการที่การทดสอบไม่เริ่มต้นจนกว่ากระบวนการพัฒนาจะเสร็จสิ้น

ก่อนที่จะใช้กลยุทธ์หรือแนวทางการทดสอบใด ๆ กับโปรแกรม python เราต้องมีความคิดพื้นฐานเกี่ยวกับข้อผิดพลาดที่โปรแกรมซอฟต์แวร์อาจมี ข้อผิดพลาดมีดังนี้ -

ข้อผิดพลาดทางเทคนิค

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

ข้อผิดพลาดเชิงความหมาย

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

การทดสอบหน่วย

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

ในส่วนต่อไปเราจะเรียนรู้เกี่ยวกับโมดูล Python ต่างๆสำหรับการทดสอบหน่วย

โมดูลที่ไม่เหมาะสมที่สุด

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

ต่อไปนี้เป็นแนวคิดสำคัญบางประการที่ได้รับการสนับสนุนโดยโมดูลที่ไม่เหมาะสมที่สุด

การติดตั้งข้อความ

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

กรณีทดสอบ

กรณีทดสอบจะตรวจสอบว่าการตอบกลับที่ต้องการนั้นมาจากชุดอินพุตเฉพาะหรือไม่ โมดูลที่ไม่เหมาะสมที่สุดประกอบด้วยคลาสฐานชื่อ TestCase ซึ่งสามารถใช้เพื่อสร้างกรณีทดสอบใหม่ ประกอบด้วยสองวิธีโดยค่าเริ่มต้น -

  • setUp()- วิธีขอเกี่ยวสำหรับติดตั้งอุปกรณ์ทดสอบก่อนออกกำลังกาย สิ่งนี้ถูกเรียกก่อนที่จะเรียกใช้วิธีการทดสอบที่ใช้งาน

  • tearDown( - วิธีการเกี่ยวกับการแยกโครงสร้างของชั้นเรียนหลังจากเรียกใช้การทดสอบทั้งหมดในชั้นเรียน

ชุดทดสอบ

เป็นการรวบรวมชุดทดสอบกรณีทดสอบหรือทั้งสองอย่าง

นักวิ่งทดสอบ

ควบคุมการทำงานของกรณีทดสอบหรือชุดทดสอบและให้ผลลัพธ์แก่ผู้ใช้ อาจใช้ GUI หรืออินเทอร์เฟซข้อความธรรมดาเพื่อให้ได้ผลลัพธ์

Example

โปรแกรม Python ต่อไปนี้ใช้โมดูลที่ไม่เหมาะสมที่สุดเพื่อทดสอบโมดูลที่ชื่อ Fibonacci. โปรแกรมช่วยในการคำนวณอนุกรม Fibonacci ของตัวเลข ในตัวอย่างนี้เราได้สร้างคลาสชื่อ Fibo_test เพื่อกำหนดกรณีทดสอบโดยใช้วิธีการต่างๆ วิธีการเหล่านี้สืบทอดมาจาก unittest.TestCase เราใช้สองวิธีโดยเริ่มต้น - setUp () และ tearDown () เรายังกำหนดวิธี testfibocal ชื่อของการทดสอบต้องเริ่มต้นด้วยการทดสอบตัวอักษร ในบล็อกสุดท้าย unittest.main () จัดเตรียมอินเตอร์เฟสบรรทัดคำสั่งให้กับสคริปต์ทดสอบ

import unittest
def fibonacci(n):
   a, b = 0, 1
   for i in range(n):
   a, b = b, a + b
   return a
class Fibo_Test(unittest.TestCase):
   def setUp(self):
   print("This is run before our tests would be executed")
   def tearDown(self):
   print("This is run after the completion of execution of our tests")

   def testfibocal(self):
   self.assertEqual(fib(0), 0)
   self.assertEqual(fib(1), 1)
   self.assertEqual(fib(5), 5)
   self.assertEqual(fib(10), 55)
   self.assertEqual(fib(20), 6765)

if __name__ == "__main__":
   unittest.main()

เมื่อเรียกใช้จากบรรทัดคำสั่งสคริปต์ด้านบนจะสร้างเอาต์พุตที่มีลักษณะดังนี้ -

เอาต์พุต

This runs before our tests would be executed.
This runs after the completion of execution of our tests.
.
----------------------------------------------------------------------
Ran 1 test in 0.006s
OK

ตอนนี้เพื่อให้ชัดเจนขึ้นเรากำลังเปลี่ยนรหัสของเราซึ่งช่วยในการกำหนดโมดูล Fibonacci

ลองพิจารณาโค้ดบล็อกต่อไปนี้เป็นตัวอย่าง -

def fibonacci(n):
   a, b = 0, 1
   for i in range(n):
   a, b = b, a + b
   return a

มีการเปลี่ยนแปลงเล็กน้อยในบล็อกโค้ดดังที่แสดงด้านล่าง -

def fibonacci(n):
   a, b = 1, 1
   for i in range(n):
   a, b = b, a + b
   return a

ตอนนี้หลังจากรันสคริปต์ด้วยรหัสที่เปลี่ยนแปลงเราจะได้ผลลัพธ์ต่อไปนี้ -

This runs before our tests would be executed.
This runs after the completion of execution of our tests.
F
======================================================================
FAIL: testCalculation (__main__.Fibo_Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "unitg.py", line 15, in testCalculation
self.assertEqual(fib(0), 0)
AssertionError: 1 != 0
----------------------------------------------------------------------
Ran 1 test in 0.007s

FAILED (failures = 1)

เอาต์พุตด้านบนแสดงว่าโมดูลไม่สามารถให้เอาต์พุตที่ต้องการได้

โมดูล Docktest

โมดูล docktest ยังช่วยในการทดสอบหน่วย นอกจากนี้ยังมาพร้อมกับ python ใช้งานได้ง่ายกว่าโมดูลที่ไม่เหมาะสมที่สุด โมดูลที่ไม่เหมาะสมที่สุดเหมาะสำหรับการทดสอบที่ซับซ้อนมากกว่า สำหรับการใช้โมดูลหลักเราจำเป็นต้องนำเข้า docstring ของฟังก์ชันที่เกี่ยวข้องต้องมีเซสชัน python แบบโต้ตอบพร้อมกับเอาต์พุต

หากทุกอย่างเรียบร้อยดีในโค้ดของเราจะไม่มีเอาต์พุตจากโมดูล docktest มิฉะนั้นจะให้ผลลัพธ์

ตัวอย่าง

ตัวอย่าง Python ต่อไปนี้ใช้โมดูล docktest เพื่อทดสอบโมดูลชื่อ Fibonacci ซึ่งช่วยในการคำนวณอนุกรม Fibonacci ของตัวเลข

import doctest
def fibonacci(n):
   """
   Calculates the Fibonacci number

   >>> fibonacci(0)
   0
   >>> fibonacci(1)
   1
   >>> fibonacci(10)
   55
   >>> fibonacci(20)
   6765
   >>>

   """
   a, b = 1, 1
   for i in range(n):
   a, b = b, a + b
   return a
      if __name__ == "__main__":
   doctest.testmod()

เราจะเห็นว่า docstring ของฟังก์ชันที่ชื่อ fib มีเซสชัน python แบบโต้ตอบพร้อมกับเอาต์พุต ถ้ารหัสของเราดีก็จะไม่มีผลลัพธ์จากโมดูลหลัก แต่เพื่อดูว่ามันทำงานอย่างไรเราสามารถเรียกใช้ด้วยตัวเลือก –v

(base) D:\ProgramData>python dock_test.py -v
Trying:
   fibonacci(0)
Expecting:
   0
ok
Trying:
   fibonacci(1)
Expecting:
   1
ok
Trying:
   fibonacci(10)
Expecting:
   55
ok
Trying:
   fibonacci(20)
Expecting:
   6765
ok
1 items had no tests:
   __main__
1 items passed all tests:
4 tests in __main__.fibonacci
4 tests in 2 items.
4 passed and 0 failed.
Test passed.

ตอนนี้เราจะเปลี่ยนรหัสที่ช่วยในการกำหนดโมดูล Fibonacci

ลองพิจารณาโค้ดบล็อกต่อไปนี้เป็นตัวอย่าง -

def fibonacci(n):
   a, b = 0, 1
   for i in range(n):
   a, b = b, a + b
   return a

บล็อกรหัสต่อไปนี้ช่วยในการเปลี่ยนแปลง -

def fibonacci(n):
   a, b = 1, 1
   for i in range(n):
   a, b = b, a + b
   return a

หลังจากรันสคริปต์แม้ว่าจะไม่มีตัวเลือก –v ด้วยรหัสที่เปลี่ยนแปลงเราจะได้ผลลัพธ์ดังที่แสดงด้านล่าง

เอาต์พุต

(base) D:\ProgramData>python dock_test.py
**********************************************************************
File "unitg.py", line 6, in __main__.fibonacci
Failed example:
   fibonacci(0)
Expected:
   0
Got:
   1
**********************************************************************
File "unitg.py", line 10, in __main__.fibonacci
Failed example:
   fibonacci(10)
Expected:
   55
Got:
   89
**********************************************************************
File "unitg.py", line 12, in __main__.fibonacci
Failed example:
   fibonacci(20)
Expected:
   6765
Got:
   10946
**********************************************************************
1 items had failures:
   3 of 4 in __main__.fibonacci
***Test Failed*** 3 failures.

เราจะเห็นในผลลัพธ์ข้างต้นว่าการทดสอบสามครั้งล้มเหลว

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

Debugging คืออะไร?

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

Python Debugger

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

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

เราสามารถทำงานกับ pdb ได้สองวิธีดังต่อไปนี้ -

  • ผ่านบรรทัดคำสั่ง; สิ่งนี้เรียกอีกอย่างว่าการแก้จุดบกพร่องหลังการตาย
  • โดยรันแบบโต้ตอบ pdb

ทำงานกับ pdb

สำหรับการทำงานกับดีบักเกอร์ Python เราจำเป็นต้องใช้รหัสต่อไปนี้ในตำแหน่งที่เราต้องการเจาะเข้าไปในตัวดีบัก -

import pdb;
pdb.set_trace()

พิจารณาคำสั่งต่อไปนี้เพื่อทำงานกับ pdb ผ่านบรรทัดคำสั่ง

  • h(help)
  • d(down)
  • u(up)
  • b(break)
  • cl(clear)
  • l(list))
  • n(next))
  • c(continue)
  • s(step)
  • r(return))
  • b(break)

ต่อไปนี้เป็นตัวอย่างคำสั่ง h (help) ของตัวดีบักเกอร์ Python -

import pdb

pdb.set_trace()
--Call--
>d:\programdata\lib\site-packages\ipython\core\displayhook.py(247)__call__()
-> def __call__(self, result = None):
(Pdb) h

Documented commands (type help <topic>):
========================================
EOF   c         d       h        list     q       rv      undisplay
a     cl        debug   help     ll       quit    s       unt
alias clear     disable ignore   longlist r       source  until
args  commands  display interact n        restart step    up
b     condition down    j        next     return  tbreak  w
break cont      enable  jump     p        retval  u       whatis
bt    continue  exit    l        pp       run     unalias where

Miscellaneous help topics:
==========================
exec pdb

ตัวอย่าง

ในขณะที่ทำงานกับดีบักเกอร์ Python เราสามารถตั้งค่าเบรกพอยต์ที่ใดก็ได้ในสคริปต์โดยใช้บรรทัดต่อไปนี้ -

import pdb;
pdb.set_trace()

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

import pdb;
a = "aaa"
pdb.set_trace()
b = "bbb"
c = "ccc"
final = a + b + c
print (final)

เมื่อเรียกใช้สคริปต์ข้างต้นมันจะดำเนินการโปรแกรมจนถึง a =“ aaa” เราสามารถตรวจสอบสิ่งนี้ได้ในผลลัพธ์ต่อไปนี้

เอาต์พุต

--Return--
> <ipython-input-7-8a7d1b5cc854>(3)<module>()->None
-> pdb.set_trace()
(Pdb) p a
'aaa'
(Pdb) p b
*** NameError: name 'b' is not defined
(Pdb) p c
*** NameError: name 'c' is not defined

หลังจากใช้คำสั่ง 'p (print)' ใน pdb สคริปต์นี้จะพิมพ์เฉพาะ 'aaa' เท่านั้น ตามมาด้วยข้อผิดพลาดเนื่องจากเราได้ตั้งค่าจุดพักจนถึง a = "aaa"

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

import pdb
a = "aaa"
b = "bbb"
c = "ccc"
pdb.set_trace()
final = a + b + c
print (final)

เอาต์พุต

--Return--
> <ipython-input-9-a59ef5caf723>(5)<module>()->None
-> pdb.set_trace()
(Pdb) p a
'aaa'
(Pdb) p b
'bbb'
(Pdb) p c
'ccc'
(Pdb) p final
*** NameError: name 'final' is not defined
(Pdb) exit

ในสคริปต์ต่อไปนี้เรากำลังตั้งค่าเบรกพอยต์ในบรรทัดสุดท้ายของโปรแกรม -

import pdb
a = "aaa"
b = "bbb"
c = "ccc"
final = a + b + c
pdb.set_trace()
print (final)

ผลลัพธ์มีดังนี้ -

--Return--
> <ipython-input-11-8019b029997d>(6)<module>()->None
-> pdb.set_trace()
(Pdb) p a
'aaa'
(Pdb) p b
'bbb'
(Pdb) p c
'ccc'
(Pdb) p final
'aaabbbccc'
(Pdb)

ในบทนี้เราจะเรียนรู้ว่าการเปรียบเทียบและการทำโปรไฟล์ช่วยในการแก้ไขปัญหาด้านประสิทธิภาพได้อย่างไร

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

Benchmarking คืออะไร?

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

การเปรียบเทียบทำงานอย่างไร

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

โมดูล Python สำหรับการเปรียบเทียบ

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

ตัวอย่าง

ในสคริปต์ Python ต่อไปนี้เรากำลังนำเข้าไฟล์ timeit โมดูลซึ่งจะวัดเวลาที่ใช้ในการเรียกใช้ฟังก์ชันสองฟังก์ชันเพิ่มเติม - functionA และ functionB -

import timeit
import time
def functionA():
   print("Function A starts the execution:")
   print("Function A completes the execution:")
def functionB():
   print("Function B starts the execution")
   print("Function B completes the execution")
start_time = timeit.default_timer()
functionA()
print(timeit.default_timer() - start_time)
start_time = timeit.default_timer()
functionB()
print(timeit.default_timer() - start_time)

หลังจากเรียกใช้สคริปต์ข้างต้นเราจะได้รับเวลาดำเนินการของทั้งสองฟังก์ชันดังที่แสดงด้านล่าง

เอาต์พุต

Function A starts the execution:
Function A completes the execution:
0.0014599495514175942
Function B starts the execution
Function B completes the execution
0.0017024724827479076

การเขียนตัวจับเวลาของเราเองโดยใช้ฟังก์ชันมัณฑนากร

ใน Python เราสามารถสร้างตัวจับเวลาของเราเองได้ซึ่งจะทำหน้าที่เหมือนกับไฟล์ timeitโมดูล. สามารถทำได้ด้วยความช่วยเหลือของไฟล์decoratorฟังก์ชัน ต่อไปนี้เป็นตัวอย่างของตัวจับเวลาที่กำหนดเอง -

import random
import time

def timer_func(func):

   def function_timer(*args, **kwargs):
   start = time.time()
   value = func(*args, **kwargs)
   end = time.time()
   runtime = end - start
   msg = "{func} took {time} seconds to complete its execution."
      print(msg.format(func = func.__name__,time = runtime))
   return value
   return function_timer

@timer_func
def Myfunction():
   for x in range(5):
   sleep_time = random.choice(range(1,3))
   time.sleep(sleep_time)

if __name__ == '__main__':
   Myfunction()

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

เอาต์พุต

Myfunction took 8.000457763671875 seconds to complete its execution.

การทำโปรไฟล์คืออะไร?

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

ในส่วนต่อไปเราจะเรียนรู้เกี่ยวกับ Python Modules สำหรับการทำโปรไฟล์

cProfile - โมดูล inbuilt

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

ตัวอย่าง

def increment_global():

   global x
   x += 1

def taskofThread(lock):

   for _ in range(50000):
   lock.acquire()
   increment_global()
   lock.release()

def main():
   global x
   x = 0

   lock = threading.Lock()

   t1 = threading.Thread(target=taskofThread, args=(lock,))
   t2 = threading.Thread(target= taskofThread, args=(lock,))

   t1.start()
   t2.start()

   t1.join()
   t2.join()

if __name__ == "__main__":
   for i in range(5):
      main()
   print("x = {1} after Iteration {0}".format(i,x))

รหัสด้านบนจะถูกบันทึกไว้ในไฟล์ thread_increment.pyไฟล์. ตอนนี้รันโค้ดด้วย cProfile บนบรรทัดคำสั่งดังนี้ -

(base) D:\ProgramData>python -m cProfile thread_increment.py
x = 100000 after Iteration 0
x = 100000 after Iteration 1
x = 100000 after Iteration 2
x = 100000 after Iteration 3
x = 100000 after Iteration 4
      3577 function calls (3522 primitive calls) in 1.688 seconds

   Ordered by: standard name

   ncalls tottime percall cumtime percall filename:lineno(function)

   5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:103(release)
   5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:143(__init__)
   5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:147(__enter__)
   … … … …

จากผลลัพธ์ข้างต้นเป็นที่ชัดเจนว่า cProfile พิมพ์ฟังก์ชัน 3577 ทั้งหมดที่เรียกว่าโดยใช้เวลาที่ใช้ในแต่ละครั้งและจำนวนครั้งที่เรียกใช้ ต่อไปนี้เป็นคอลัมน์ที่เราได้รับในผลลัพธ์ -

  • ncalls - เป็นจำนวนการโทร

  • tottime - เป็นเวลาทั้งหมดที่ใช้ในฟังก์ชันที่กำหนด

  • percall - หมายถึงผลหารของ tottime หารด้วย ncalls

  • cumtime- เป็นเวลาสะสมที่ใช้ในฟังก์ชันนี้และฟังก์ชันย่อยทั้งหมด มีความแม่นยำแม้กระทั่งสำหรับฟังก์ชันเรียกซ้ำ

  • percall - มันคือผลหารของ cumtime หารด้วยการเรียกแบบดั้งเดิม

  • filename:lineno(function) - โดยทั่วไปจะให้ข้อมูลตามลำดับของแต่ละฟังก์ชัน

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

  • หากเธรดในเธรดพูลเสร็จสิ้นการดำเนินการเธรดนั้นสามารถนำมาใช้ซ้ำได้

  • หากเธรดถูกยกเลิกเธรดอื่นจะถูกสร้างขึ้นเพื่อแทนที่เธรดนั้น

โมดูล Python - Concurrent.futures

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

ในส่วนต่อไปเราจะเรียนรู้เกี่ยวกับคลาสต่างๆของโมดูล concurrent.futures

ระดับผู้บริหาร

Executorเป็นคลาสนามธรรมของ concurrent.futuresโมดูล Python ไม่สามารถใช้ได้โดยตรงและเราจำเป็นต้องใช้หนึ่งในคลาสย่อยคอนกรีตต่อไปนี้ -

  • ThreadPoolExecutor
  • ProcessPoolExecutor

ThreadPoolExecutor - คลาสย่อยคอนกรีต

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

จะสร้าง ThreadPoolExecutor ได้อย่างไร?

ด้วยความช่วยเหลือของ concurrent.futures โมดูลและคลาสย่อยคอนกรีต Executorเราสามารถสร้างกลุ่มของเธรดได้อย่างง่ายดาย สำหรับสิ่งนี้เราต้องสร้างไฟล์ThreadPoolExecutorด้วยจำนวนเธรดที่เราต้องการในพูล ตามค่าเริ่มต้นจำนวนคือ 5 จากนั้นเราสามารถส่งงานไปยังเธรดพูล เมื่อเราsubmit() งานเราจะกลับมา Future. วัตถุในอนาคตมีวิธีการที่เรียกว่าdone()ซึ่งจะบอกว่าอนาคตคลี่คลายหรือไม่ ด้วยเหตุนี้จึงมีการกำหนดค่าสำหรับวัตถุในอนาคตนั้น ๆ เมื่องานเสร็จสิ้นตัวดำเนินการเธรดพูลจะตั้งค่าเป็นอ็อบเจ็กต์ในอนาคต

ตัวอย่าง

from concurrent.futures import ThreadPoolExecutor
from time import sleep
def task(message):
   sleep(2)
   return message

def main():
   executor = ThreadPoolExecutor(5)
   future = executor.submit(task, ("Completed"))
   print(future.done())
   sleep(2)
   print(future.done())
   print(future.result())
if __name__ == '__main__':
main()

เอาต์พุต

False
True
Completed

ในตัวอย่างข้างต้น a ThreadPoolExecutorถูกสร้างขึ้นด้วย 5 เธรด จากนั้นงานซึ่งจะรอ 2 วินาทีก่อนที่จะให้ข้อความจะถูกส่งไปยังตัวดำเนินการเธรดพูล ดังที่เห็นจากผลลัพธ์งานจะไม่เสร็จสมบูรณ์จนกว่าจะถึง 2 วินาทีดังนั้นการโทรครั้งแรกไปที่done()จะส่งคืน False หลังจากผ่านไป 2 วินาทีงานจะเสร็จสิ้นและเราจะได้รับผลลัพธ์ของอนาคตโดยเรียกไฟล์result() วิธีการกับมัน

การสร้างอินสแตนซ์ ThreadPoolExecutor - ตัวจัดการบริบท

อีกวิธีหนึ่งในการสร้างอินสแตนซ์ ThreadPoolExecutorด้วยความช่วยเหลือของผู้จัดการบริบท ทำงานคล้ายกับวิธีการที่ใช้ในตัวอย่างข้างต้น ข้อได้เปรียบหลักของการใช้ตัวจัดการบริบทคือมันดูดีในเชิงไวยากรณ์ การสร้างอินสแตนซ์สามารถทำได้ด้วยความช่วยเหลือของรหัสต่อไปนี้ -

with ThreadPoolExecutor(max_workers = 5) as executor

ตัวอย่าง

ตัวอย่างต่อไปนี้ยืมมาจาก Python docs ในตัวอย่างนี้ก่อนอื่นconcurrent.futuresต้องนำเข้าโมดูล จากนั้นฟังก์ชั่นที่ชื่อว่าload_url()ถูกสร้างขึ้นซึ่งจะโหลด url ที่ร้องขอ จากนั้นฟังก์ชันจะสร้างThreadPoolExecutorด้วย 5 เธรดในพูล ThreadPoolExecutorถูกใช้เป็นตัวจัดการบริบท เราสามารถรับผลลัพธ์ของอนาคตได้โดยเรียกไฟล์result() วิธีการกับมัน

import concurrent.futures
import urllib.request

URLS = ['http://www.foxnews.com/',
   'http://www.cnn.com/',
   'http://europe.wsj.com/',
   'http://www.bbc.co.uk/',
   'http://some-made-up-domain.com/']

def load_url(url, timeout):
   with urllib.request.urlopen(url, timeout = timeout) as conn:
   return conn.read()

with concurrent.futures.ThreadPoolExecutor(max_workers = 5) as executor:

   future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
   for future in concurrent.futures.as_completed(future_to_url):
   url = future_to_url[future]
   try:
      data = future.result()
   except Exception as exc:
      print('%r generated an exception: %s' % (url, exc))
   else:
      print('%r page is %d bytes' % (url, len(data)))

เอาต์พุต

ต่อไปนี้จะเป็นผลลัพธ์ของสคริปต์ Python ด้านบน -

'http://some-made-up-domain.com/' generated an exception: <urlopen error [Errno 11004] getaddrinfo failed>
'http://www.foxnews.com/' page is 229313 bytes
'http://www.cnn.com/' page is 168933 bytes
'http://www.bbc.co.uk/' page is 283893 bytes
'http://europe.wsj.com/' page is 938109 bytes

การใช้ฟังก์ชัน Executor.map ()

Python map()ฟังก์ชันนี้ใช้กันอย่างแพร่หลายในหลาย ๆ งาน ภารกิจอย่างหนึ่งคือการใช้ฟังก์ชันบางอย่างกับทุกองค์ประกอบภายในการวนซ้ำ ในทำนองเดียวกันเราสามารถแมปองค์ประกอบทั้งหมดของตัววนซ้ำกับฟังก์ชันและส่งงานเหล่านี้ให้เป็นงานอิสระThreadPoolExecutor. พิจารณาตัวอย่างต่อไปนี้ของสคริปต์ Python เพื่อทำความเข้าใจว่าฟังก์ชันทำงานอย่างไร

ตัวอย่าง

ในตัวอย่างด้านล่างนี้ฟังก์ชั่นแผนที่ถูกใช้เพื่อใช้ square() ฟังก์ชันกับทุกค่าในอาร์เรย์ค่า

from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import as_completed
values = [2,3,4,5]
def square(n):
   return n * n
def main():
   with ThreadPoolExecutor(max_workers = 3) as executor:
      results = executor.map(square, values)
for result in results:
      print(result)
if __name__ == '__main__':
   main()

เอาต์พุต

สคริปต์ Python ข้างต้นสร้างผลลัพธ์ต่อไปนี้ -

4
9
16
25

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

โมดูล Python - Concurrent.futures

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

ในส่วนต่อไปเราจะดูคลาสย่อยที่แตกต่างกันของโมดูล concurrent.futures

ระดับผู้บริหาร

Executor เป็นคลาสนามธรรมของ concurrent.futuresโมดูล Python ไม่สามารถใช้ได้โดยตรงและเราจำเป็นต้องใช้หนึ่งในคลาสย่อยคอนกรีตต่อไปนี้ -

  • ThreadPoolExecutor
  • ProcessPoolExecutor

ProcessPoolExecutor - คลาสย่อยที่เป็นรูปธรรม

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

จะสร้าง ProcessPoolExecutor ได้อย่างไร?

ด้วยความช่วยเหลือของ concurrent.futures โมดูลและคลาสย่อยคอนกรีต Executorเราสามารถสร้างกลุ่มของกระบวนการได้อย่างง่ายดาย สำหรับสิ่งนี้เราต้องสร้างไฟล์ProcessPoolExecutorด้วยจำนวนกระบวนการที่เราต้องการในพูล ตามค่าเริ่มต้นตัวเลขคือ 5 ตามด้วยการส่งงานไปยังพูลกระบวนการ

ตัวอย่าง

ตอนนี้เราจะพิจารณาตัวอย่างเดียวกับที่เราใช้ในการสร้างเธรดพูลข้อแตกต่างเพียงอย่างเดียวคือตอนนี้เราจะใช้ ProcessPoolExecutor แทน ThreadPoolExecutor .

from concurrent.futures import ProcessPoolExecutor
from time import sleep
def task(message):
   sleep(2)
   return message

def main():
   executor = ProcessPoolExecutor(5)
   future = executor.submit(task, ("Completed"))
   print(future.done())
   sleep(2)
   print(future.done())
   print(future.result())
if __name__ == '__main__':
main()

เอาต์พุต

False
False
Completed

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

การสร้างอินสแตนซ์ ProcessPoolExecutor - ตัวจัดการบริบท

อีกวิธีหนึ่งในการสร้างอินสแตนซ์ ProcessPoolExecutor คือด้วยความช่วยเหลือของผู้จัดการบริบท ทำงานคล้ายกับวิธีการที่ใช้ในตัวอย่างข้างต้น ข้อได้เปรียบหลักของการใช้ตัวจัดการบริบทคือมันดูดีในเชิงไวยากรณ์ การสร้างอินสแตนซ์สามารถทำได้ด้วยความช่วยเหลือของรหัสต่อไปนี้ -

with ProcessPoolExecutor(max_workers = 5) as executor

ตัวอย่าง

เพื่อความเข้าใจที่ดีขึ้นเราใช้ตัวอย่างเดียวกับที่ใช้ในการสร้างเธรดพูล ในตัวอย่างนี้เราต้องเริ่มต้นด้วยการนำเข้าไฟล์concurrent.futuresโมดูล. จากนั้นฟังก์ชั่นที่ชื่อว่าload_url()ถูกสร้างขึ้นซึ่งจะโหลด url ที่ร้องขอ ProcessPoolExecutorจากนั้นจะสร้างด้วยจำนวน 5 เธรดในพูล กระบวนการPoolExecutorถูกใช้เป็นตัวจัดการบริบท เราสามารถรับผลลัพธ์ของอนาคตได้โดยเรียกไฟล์result() วิธีการกับมัน

import concurrent.futures
from concurrent.futures import ProcessPoolExecutor
import urllib.request

URLS = ['http://www.foxnews.com/',
   'http://www.cnn.com/',
   'http://europe.wsj.com/',
   'http://www.bbc.co.uk/',
   'http://some-made-up-domain.com/']

def load_url(url, timeout):
   with urllib.request.urlopen(url, timeout = timeout) as conn:
      return conn.read()

def main():
   with concurrent.futures.ProcessPoolExecutor(max_workers=5) as executor:
      future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
      for future in concurrent.futures.as_completed(future_to_url):
      url = future_to_url[future]
      try:
         data = future.result()
      except Exception as exc:
         print('%r generated an exception: %s' % (url, exc))
      else:
         print('%r page is %d bytes' % (url, len(data)))

if __name__ == '__main__':
   main()

เอาต์พุต

สคริปต์ Python ด้านบนจะสร้างผลลัพธ์ต่อไปนี้ -

'http://some-made-up-domain.com/' generated an exception: <urlopen error [Errno 11004] getaddrinfo failed>
'http://www.foxnews.com/' page is 229476 bytes
'http://www.cnn.com/' page is 165323 bytes
'http://www.bbc.co.uk/' page is 284981 bytes
'http://europe.wsj.com/' page is 967575 bytes

การใช้ฟังก์ชัน Executor.map ()

Python map()ฟังก์ชันนี้ใช้กันอย่างแพร่หลายในการทำงานหลายอย่าง ภารกิจอย่างหนึ่งคือการใช้ฟังก์ชันบางอย่างกับทุกองค์ประกอบภายในการวนซ้ำ ในทำนองเดียวกันเราสามารถแมปองค์ประกอบทั้งหมดของตัววนซ้ำกับฟังก์ชันและส่งงานเหล่านี้เป็นงานอิสระไปยังไฟล์ProcessPoolExecutor. พิจารณาตัวอย่างต่อไปนี้ของสคริปต์ Python เพื่อทำความเข้าใจสิ่งนี้

ตัวอย่าง

เราจะพิจารณาตัวอย่างเดียวกับที่เราใช้ในขณะสร้างเธรดพูลโดยใช้ Executor.map()ฟังก์ชัน ในตัวอย่างที่ระบุไว้ด้านล่างฟังก์ชันแผนที่ถูกใช้เพื่อนำไปใช้square() ฟังก์ชันกับทุกค่าในอาร์เรย์ค่า

from concurrent.futures import ProcessPoolExecutor
from concurrent.futures import as_completed
values = [2,3,4,5]
def square(n):
   return n * n
def main():
   with ProcessPoolExecutor(max_workers = 3) as executor:
      results = executor.map(square, values)
   for result in results:
      print(result)
if __name__ == '__main__':
   main()

เอาต์พุต

สคริปต์ Python ด้านบนจะสร้างผลลัพธ์ต่อไปนี้

4
9
16
25

ควรใช้ ProcessPoolExecutor และ ThreadPoolExecutor เมื่อใด

ตอนนี้เราได้ศึกษาเกี่ยวกับคลาส Executor ทั้ง ThreadPoolExecutor และ ProcessPoolExecutor แล้วเราจำเป็นต้องรู้ว่าเมื่อใดควรใช้ตัวดำเนินการใด เราจำเป็นต้องเลือก ProcessPoolExecutor ในกรณีของเวิร์กโหลดที่ผูกกับ CPU และ ThreadPoolExecutor ในกรณีของปริมาณงานที่ผูกกับ I / O

ถ้าเราใช้ ProcessPoolExecutorดังนั้นเราไม่จำเป็นต้องกังวลเกี่ยวกับ GIL เพราะมันใช้การประมวลผลหลายขั้นตอน ยิ่งไปกว่านั้นเวลาดำเนินการจะน้อยลงเมื่อเทียบกับThreadPoolExecution. พิจารณาตัวอย่างสคริปต์ Python ต่อไปนี้เพื่อทำความเข้าใจสิ่งนี้

ตัวอย่าง

import time
import concurrent.futures

value = [8000000, 7000000]

def counting(n):
   start = time.time()
   while n > 0:
      n -= 1
   return time.time() - start

def main():
   start = time.time()
   with concurrent.futures.ProcessPoolExecutor() as executor:
      for number, time_taken in zip(value, executor.map(counting, value)):
         print('Start: {} Time taken: {}'.format(number, time_taken))
   print('Total time taken: {}'.format(time.time() - start))

if __name__ == '__main__':
main()

เอาต์พุต

Start: 8000000 Time taken: 1.5509998798370361
Start: 7000000 Time taken: 1.3259999752044678
Total time taken: 2.0840001106262207

Example- Python script with ThreadPoolExecutor:
import time
import concurrent.futures

value = [8000000, 7000000]

def counting(n):
   start = time.time()
   while n > 0:
      n -= 1
   return time.time() - start

def main():
   start = time.time()
   with concurrent.futures.ThreadPoolExecutor() as executor:
      for number, time_taken in zip(value, executor.map(counting, value)):
         print('Start: {} Time taken: {}'.format(number, time_taken))
      print('Total time taken: {}'.format(time.time() - start))

if __name__ == '__main__':
main()

เอาต์พุต

Start: 8000000 Time taken: 3.8420000076293945
Start: 7000000 Time taken: 3.6010000705718994
Total time taken: 3.8480000495910645

จากผลลัพธ์ของทั้งสองโปรแกรมข้างต้นเราจะเห็นความแตกต่างของเวลาดำเนินการขณะใช้งาน ProcessPoolExecutor และ ThreadPoolExecutor.

ในบทนี้เราจะมุ่งเน้นไปที่การเปรียบเทียบระหว่างการประมวลผลหลายกระบวนการและมัลติเธรดมากขึ้น

การประมวลผลหลายขั้นตอน

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

มัลติเธรด

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

ตารางต่อไปนี้แสดงความแตกต่างที่สำคัญบางประการระหว่างกัน -

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

การขจัดผลกระทบของการล็อคล่ามทั่วโลก (GIL)

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

ด้วยการใช้การประมวลผลหลายขั้นตอนเราสามารถข้ามข้อ จำกัด ที่เกิดจาก GIL ได้อย่างมีประสิทธิภาพ -

  • ด้วยการใช้การประมวลผลหลายขั้นตอนเรากำลังใช้ความสามารถของกระบวนการต่างๆและด้วยเหตุนี้เราจึงใช้หลายอินสแตนซ์ของ GIL

  • ด้วยเหตุนี้จึงไม่มีข้อ จำกัด ในการเรียกใช้ bytecode ของเธรดเดียวภายในโปรแกรมของเราในคราวเดียว

กระบวนการเริ่มต้นใน Python

สามารถใช้สามวิธีต่อไปนี้เพื่อเริ่มกระบวนการใน Python ภายในโมดูลการประมวลผลหลายตัว -

  • Fork
  • Spawn
  • Forkserver

การสร้างกระบวนการด้วย Fork

คำสั่ง Fork เป็นคำสั่งมาตรฐานที่พบใน UNIX ใช้เพื่อสร้างกระบวนการใหม่ที่เรียกว่ากระบวนการลูก กระบวนการลูกนี้ทำงานพร้อมกันกับกระบวนการที่เรียกว่ากระบวนการหลัก กระบวนการลูกเหล่านี้ยังเหมือนกับกระบวนการพาเรนต์และสืบทอดทรัพยากรทั้งหมดที่มีให้กับพาเรนต์ การเรียกระบบต่อไปนี้ใช้ในขณะสร้างกระบวนการด้วย Fork -

  • fork()- เป็นการเรียกระบบโดยทั่วไปใช้ในเคอร์เนล ใช้เพื่อสร้างสำเนาของกระบวนการ p>

  • getpid() - การเรียกระบบนี้ส่งคืน ID กระบวนการ (PID) ของกระบวนการโทร

ตัวอย่าง

ตัวอย่างสคริปต์ Python ต่อไปนี้จะช่วยให้คุณเข้าใจวิธีการสร้างกระบวนการย่อยใหม่และรับ PID ของกระบวนการย่อยและพาเรนต์ -

import os

def child():
   n = os.fork()
   
   if n > 0:
      print("PID of Parent process is : ", os.getpid())

   else:
      print("PID of Child process is : ", os.getpid())
child()

เอาต์พุต

PID of Parent process is : 25989
PID of Child process is : 25990

การสร้างกระบวนการด้วย Spawn

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

  • การนำเข้าโมดูลหลายกระบวนการ

  • การสร้างกระบวนการวัตถุ

  • เริ่มกิจกรรมกระบวนการโดยโทร start() วิธี.

  • รอจนกว่ากระบวนการจะเสร็จสิ้นและออกโดยการโทร join() วิธี.

ตัวอย่าง

ตัวอย่างต่อไปนี้ของสคริปต์ Python ช่วยในการวางไข่สามกระบวนการ

import multiprocessing

def spawn_process(i):
   print ('This is process: %s' %i)
   return

if __name__ == '__main__':
   Process_jobs = []
   for i in range(3):
   p = multiprocessing.Process(target = spawn_process, args = (i,))
      Process_jobs.append(p)
   p.start()
   p.join()

เอาต์พุต

This is process: 0
This is process: 1
This is process: 2

การสร้างกระบวนการด้วย Forkserver

กลไก Forkserver มีให้เฉพาะบนแพลตฟอร์ม UNIX ที่เลือกซึ่งรองรับการส่งผ่านตัวอธิบายไฟล์ผ่าน Unix Pipes พิจารณาประเด็นต่อไปนี้เพื่อทำความเข้าใจการทำงานของกลไก Forkserver -

  • เซิร์ฟเวอร์ถูกสร้างอินสแตนซ์โดยใช้กลไก Forkserver สำหรับการเริ่มกระบวนการใหม่

  • จากนั้นเซิร์ฟเวอร์รับคำสั่งและจัดการคำขอทั้งหมดสำหรับการสร้างกระบวนการใหม่

  • สำหรับการสร้างกระบวนการใหม่โปรแกรม python ของเราจะส่งคำขอไปยัง Forkserver และมันจะสร้างกระบวนการให้เรา

  • ในที่สุดเราสามารถใช้กระบวนการที่สร้างขึ้นใหม่นี้ในโปรแกรมของเรา

Daemon ประมวลผลใน Python

Python multiprocessingโมดูลช่วยให้เรามีกระบวนการ daemon ผ่านอ็อพชัน daemonic กระบวนการ Daemon หรือกระบวนการที่กำลังทำงานอยู่เบื้องหลังเป็นไปตามแนวคิดที่คล้ายกันกับเธรด daemon ในการดำเนินการกระบวนการในเบื้องหลังเราจำเป็นต้องตั้งค่าแฟล็ก daemonic เป็น true กระบวนการ daemon จะทำงานต่อไปตราบเท่าที่กระบวนการหลักกำลังดำเนินการอยู่และกระบวนการดังกล่าวจะสิ้นสุดลงหลังจากเสร็จสิ้นการดำเนินการหรือเมื่อโปรแกรมหลักถูกฆ่า

ตัวอย่าง

ที่นี่เรากำลังใช้ตัวอย่างเดียวกับที่ใช้ในเธรด daemon ความแตกต่างเพียงอย่างเดียวคือการเปลี่ยนโมดูลจากmultithreading ถึง multiprocessingและตั้งค่าแฟล็ก daemonic เป็น true อย่างไรก็ตามจะมีการเปลี่ยนแปลงผลลัพธ์ดังที่แสดงด้านล่าง -

import multiprocessing
import time

def nondaemonProcess():
   print("starting my Process")
   time.sleep(8)
   print("ending my Process")
def daemonProcess():
   while True:
   print("Hello")
   time.sleep(2)
if __name__ == '__main__':
   nondaemonProcess = multiprocessing.Process(target = nondaemonProcess)
   daemonProcess = multiprocessing.Process(target = daemonProcess)
   daemonProcess.daemon = True
   nondaemonProcess.daemon = False
   daemonProcess.start()
   nondaemonProcess.start()

เอาต์พุต

starting my Process
ending my Process

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

การยุติกระบวนการใน Python

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

ตัวอย่าง

import multiprocessing
import time
def Child_process():
   print ('Starting function')
   time.sleep(5)
   print ('Finished function')
P = multiprocessing.Process(target = Child_process)
P.start()
print("My Process has terminated, terminating main thread")
print("Terminating Child Process")
P.terminate()
print("Child Process successfully terminated")

เอาต์พุต

My Process has terminated, terminating main thread
Terminating Child Process
Child Process successfully terminated

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

การระบุกระบวนการปัจจุบันใน Python

ทุกกระบวนการในระบบปฏิบัติการมีเอกลักษณ์ของกระบวนการที่เรียกว่า PID ใน Python เราสามารถค้นหา PID ของกระบวนการปัจจุบันด้วยความช่วยเหลือของคำสั่งต่อไปนี้ -

import multiprocessing
print(multiprocessing.current_process().pid)

ตัวอย่าง

ตัวอย่างต่อไปนี้ของสคริปต์ Python ช่วยค้นหา PID ของกระบวนการหลักรวมทั้ง PID ของกระบวนการลูก -

import multiprocessing
import time
def Child_process():
   print("PID of Child Process is: {}".format(multiprocessing.current_process().pid))
print("PID of Main process is: {}".format(multiprocessing.current_process().pid))
P = multiprocessing.Process(target=Child_process)
P.start()
P.join()

เอาต์พุต

PID of Main process is: 9401
PID of Child Process is: 9402

การใช้กระบวนการในคลาสย่อย

เราสามารถสร้างเธรดโดยการแบ่งประเภทย่อยของไฟล์ threading.Threadชั้นเรียน. นอกจากนี้เรายังสามารถสร้างกระบวนการโดยการแบ่งประเภทย่อยของไฟล์multiprocessing.Processชั้นเรียน. สำหรับการใช้กระบวนการในคลาสย่อยเราต้องพิจารณาประเด็นต่อไปนี้ -

  • เราจำเป็นต้องกำหนดคลาสย่อยใหม่ของ Process ชั้นเรียน.

  • เราจำเป็นต้องลบล้างไฟล์ _init_(self [,args] ) ชั้นเรียน.

  • เราจำเป็นต้องลบล้างไฟล์ run(self [,args] ) วิธีการดำเนินการอะไร Process

  • เราจำเป็นต้องเริ่มกระบวนการโดยเรียกใช้ไฟล์start() วิธี.

ตัวอย่าง

import multiprocessing
class MyProcess(multiprocessing.Process):
   def run(self):
   print ('called run method in process: %s' %self.name)
   return
if __name__ == '__main__':
   jobs = []
   for i in range(5):
   P = MyProcess()
   jobs.append(P)
   P.start()
   P.join()

เอาต์พุต

called run method in process: MyProcess-1
called run method in process: MyProcess-2
called run method in process: MyProcess-3
called run method in process: MyProcess-4
called run method in process: MyProcess-5

Python Multiprocessing Module - คลาสพูล

ถ้าเราพูดถึงเส้นขนานง่ายๆ processingงานในแอปพลิเคชัน Python ของเราจากนั้นโมดูลการประมวลผลหลายขั้นตอนจะมอบคลาส Pool ให้กับเรา วิธีการดังต่อไปนี้ของPool คลาสสามารถใช้เพื่อหมุนจำนวนโปรเซสลูกภายในโปรแกรมหลักของเรา

ใช้ () วิธีการ

วิธีนี้คล้ายกับไฟล์.submit()วิธีการของ .ThreadPoolExecutor.บล็อกจนกว่าผลลัพธ์จะพร้อม

apply_async () วิธีการ

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

map () วิธีการ

เช่นเดียวกับ apply()นอกจากนี้ยังบล็อกจนกว่าผลลัพธ์จะพร้อม เทียบเท่ากับในตัวmap() ฟังก์ชันที่แยกข้อมูลที่ทำซ้ำได้ในหลาย ๆ ส่วนและส่งไปยังกลุ่มกระบวนการเป็นงานแยกกัน

map_async () วิธีการ

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

ตัวอย่าง

ตัวอย่างต่อไปนี้จะช่วยให้คุณใช้กลุ่มกระบวนการสำหรับการดำเนินการแบบขนาน การคำนวณกำลังสองอย่างง่ายได้ดำเนินการโดยใช้square() ผ่านฟังก์ชัน multiprocessing.Poolวิธี. แล้วpool.map() ถูกใช้ในการส่ง 5 เนื่องจากอินพุตเป็นรายการจำนวนเต็มตั้งแต่ 0 ถึง 4 ผลลัพธ์จะถูกเก็บไว้ใน p_outputs และจะพิมพ์

def square(n):
   result = n*n
   return result
if __name__ == '__main__':
   inputs = list(range(5))
   p = multiprocessing.Pool(processes = 4)
   p_outputs = pool.map(function_square, inputs)
   p.close()
   p.join()
   print ('Pool :', p_outputs)

เอาต์พุต

Pool : [0, 1, 4, 9, 16]

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

กลไกการสื่อสารต่างๆ

ในส่วนนี้เราจะเรียนรู้เกี่ยวกับกลไกการสื่อสารต่างๆ กลไกต่างๆอธิบายไว้ด้านล่าง -

คิว

คิวสามารถใช้ได้กับโปรแกรมแบบหลายกระบวนการ คลาสคิวของmultiprocessing โมดูลคล้ายกับไฟล์ Queue.Queueชั้นเรียน. ดังนั้นสามารถใช้ API เดียวกันได้Multiprocessing.Queue จัดเตรียมเธรดและประมวลผลกลไก FIFO (เข้าก่อนออกก่อน) ที่ปลอดภัยในการสื่อสารระหว่างกระบวนการ

ตัวอย่าง

ต่อไปนี้เป็นตัวอย่างง่ายๆที่นำมาจากเอกสารอย่างเป็นทางการของ python เกี่ยวกับการประมวลผลหลายกระบวนการเพื่อทำความเข้าใจแนวคิดของคลาส Queue ของการประมวลผลหลายขั้นตอน

from multiprocessing import Process, Queue
import queue
import random
def f(q):
   q.put([42, None, 'hello'])
def main():
   q = Queue()
   p = Process(target = f, args = (q,))
   p.start()
   print (q.get())
if __name__ == '__main__':
   main()

เอาต์พุต

[42, None, 'hello']

ท่อ

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

  • ส่งคืนคู่ของวัตถุการเชื่อมต่อที่แสดงถึงปลายทั้งสองของท่อ

  • ทุกวัตถุมีสองวิธี - send() และ recv()เพื่อสื่อสารระหว่างกระบวนการ

ตัวอย่าง

ต่อไปนี้เป็นตัวอย่างง่ายๆที่นำมาจากเอกสารอย่างเป็นทางการของ python เกี่ยวกับการประมวลผลหลายขั้นตอนเพื่อทำความเข้าใจแนวคิดของ Pipe() หน้าที่ของการประมวลผลหลายขั้นตอน

from multiprocessing import Process, Pipe

def f(conn):
   conn.send([42, None, 'hello'])
   conn.close()

if __name__ == '__main__':
   parent_conn, child_conn = Pipe()
   p = Process(target = f, args = (child_conn,))
   p.start()
   print (parent_conn.recv())
   p.join()

เอาต์พุต

[42, None, 'hello']

ผู้จัดการ

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

  • คุณสมบัติหลักของผู้จัดการคือการควบคุมกระบวนการเซิร์ฟเวอร์ซึ่งจัดการอ็อบเจ็กต์ที่แบ่งใช้

  • คุณสมบัติที่สำคัญอีกประการหนึ่งคือการอัพเดตอ็อบเจ็กต์ที่แบ่งใช้ทั้งหมดเมื่อกระบวนการใด ๆ แก้ไข

ตัวอย่าง

ต่อไปนี้เป็นตัวอย่างที่ใช้อ็อบเจ็กต์ตัวจัดการสำหรับสร้างเรกคอร์ดรายการในกระบวนการเซิร์ฟเวอร์จากนั้นเพิ่มเร็กคอร์ดใหม่ในรายการนั้น

import multiprocessing

def print_records(records):
   for record in records:
      print("Name: {0}\nScore: {1}\n".format(record[0], record[1]))

def insert_record(record, records):
   records.append(record)
      print("A New record is added\n")

if __name__ == '__main__':
   with multiprocessing.Manager() as manager:

      records = manager.list([('Computers', 1), ('Histoty', 5), ('Hindi',9)])
      new_record = ('English', 3)

      p1 = multiprocessing.Process(target = insert_record, args = (new_record, records))
      p2 = multiprocessing.Process(target = print_records, args = (records,))
	  p1.start()
      p1.join()
      p2.start()
      p2.join()

เอาต์พุต

A New record is added

Name: Computers
Score: 1

Name: Histoty
Score: 5

Name: Hindi
Score: 9

Name: English
Score: 3

แนวคิดของเนมสเปซในตัวจัดการ

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

ตัวอย่าง

ตัวอย่างสคริปต์ Python ต่อไปนี้ช่วยให้เราใช้เนมสเปซสำหรับการแชร์ข้อมูลระหว่างกระบวนการหลักและกระบวนการย่อย -

import multiprocessing

def Mng_NaSp(using_ns):

   using_ns.x +=5
   using_ns.y *= 10

if __name__ == '__main__':
   manager = multiprocessing.Manager()
   using_ns = manager.Namespace()
   using_ns.x = 1
   using_ns.y = 1

   print ('before', using_ns)
   p = multiprocessing.Process(target = Mng_NaSp, args = (using_ns,))
   p.start()
   p.join()
   print ('after', using_ns)

เอาต์พุต

before Namespace(x = 1, y = 1)
after Namespace(x = 6, y = 10)

Ctypes-Array และ Value

Multiprocessing module จัดเตรียมอ็อบเจ็กต์ Array และ Value สำหรับจัดเก็บข้อมูลในแผนที่หน่วยความจำแบบแบ่งใช้ Array เป็นอาร์เรย์ ctypes ที่จัดสรรจากหน่วยความจำแบบแบ่งใช้และ Value เป็นวัตถุ ctypes ที่จัดสรรจากหน่วยความจำแบบแบ่งใช้

ในการใช้งานให้นำเข้ากระบวนการค่าอาร์เรย์จากการประมวลผลหลายขั้นตอน

ตัวอย่าง

สคริปต์ Python ต่อไปนี้เป็นตัวอย่างที่นำมาจาก python docs เพื่อใช้ประโยชน์จาก Ctypes Array และ Value สำหรับการแบ่งปันข้อมูลระหว่างกระบวนการ

def f(n, a):
   n.value = 3.1415927
   for i in range(len(a)):
   a[i] = -a[i]

if __name__ == '__main__':
   num = Value('d', 0.0)
   arr = Array('i', range(10))

   p = Process(target = f, args = (num, arr))
   p.start()
   p.join()
   print (num.value)
   print (arr[:])

เอาต์พุต

3.1415927
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]

การสื่อสารกระบวนการตามลำดับ (CSP)

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

ไลบรารี Python - PyCSP

สำหรับการใช้งาน Core Primitives ที่พบใน CSP Python มีไลบรารีชื่อ PyCSP ทำให้การนำไปใช้งานสั้นและอ่านง่ายเพื่อให้สามารถเข้าใจได้ง่ายมาก ต่อไปนี้เป็นเครือข่ายกระบวนการพื้นฐานของ PyCSP -

ในเครือข่ายกระบวนการ PyCSP ข้างต้นมีสองกระบวนการ - กระบวนการ 1 และกระบวนการ 2 กระบวนการเหล่านี้สื่อสารโดยส่งข้อความผ่านสองช่องทาง - ช่องทาง 1 และช่อง 2

การติดตั้ง PyCSP

ด้วยความช่วยเหลือของคำสั่งต่อไปนี้เราสามารถติดตั้ง Python library PyCSP -

pip install PyCSP

ตัวอย่าง

การทำตามสคริปต์ Python เป็นตัวอย่างง่ายๆสำหรับการรันสองกระบวนการควบคู่กัน ทำได้ด้วยความช่วยเหลือของ PyCSP python libabary -

from pycsp.parallel import *
import time
@process
def P1():
   time.sleep(1)
   print('P1 exiting')
@process
def P2():
   time.sleep(1)
   print('P2 exiting')
def main():
   Parallel(P1(), P2())
   print('Terminating')
if __name__ == '__main__':
   main()

ในสคริปต์ข้างต้นมีสองฟังก์ชันคือ P1 และ P2 ถูกสร้างขึ้นแล้วตกแต่งด้วย @process สำหรับการแปลงเป็นกระบวนการ

เอาต์พุต

P2 exiting
P1 exiting
Terminating

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

โมดูล Python - Asyncio

โมดูล Asyncio ถูกเพิ่มใน Python 3.4 และมีโครงสร้างพื้นฐานสำหรับการเขียนโค้ด single-threaded พร้อมกันโดยใช้ co-Routines ต่อไปนี้เป็นแนวคิดต่างๆที่ใช้โดยโมดูล Asyncio -

วนซ้ำเหตุการณ์

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

  • loop = get_event_loop() - วิธีนี้จะจัดเตรียมห่วงเหตุการณ์สำหรับบริบทปัจจุบัน

  • loop.call_later(time_delay,callback,argument) - วิธีนี้จัดเตรียมการโทรกลับที่จะถูกเรียกหลังจากวินาที time_delay ที่กำหนด

  • loop.call_soon(callback,argument)- วิธีนี้จัดเตรียมการโทรกลับที่จะโทรกลับโดยเร็วที่สุด การเรียกกลับถูกเรียกหลังจาก call_soon () ส่งคืนและเมื่อตัวควบคุมกลับไปที่ลูปเหตุการณ์

  • loop.time() - วิธีนี้ใช้เพื่อคืนเวลาปัจจุบันตามนาฬิกาภายในของลูปเหตุการณ์

  • asyncio.set_event_loop() - วิธีนี้จะตั้งค่าการวนซ้ำเหตุการณ์สำหรับบริบทปัจจุบันเป็นลูป

  • asyncio.new_event_loop() - วิธีนี้จะสร้างและส่งคืนอ็อบเจกต์วนซ้ำเหตุการณ์ใหม่

  • loop.run_forever() - เมธอดนี้จะทำงานจนกว่าจะมีการเรียกเมธอด stop ()

ตัวอย่าง

ตัวอย่างเหตุการณ์ต่อไปนี้ช่วยในการพิมพ์ hello worldโดยใช้เมธอด get_event_loop () ตัวอย่างนี้นำมาจากเอกสารทางการของ Python

import asyncio

def hello_world(loop):
   print('Hello World')
   loop.stop()

loop = asyncio.get_event_loop()

loop.call_soon(hello_world, loop)

loop.run_forever()
loop.close()

เอาต์พุต

Hello World

ฟิวเจอร์ส

สิ่งนี้เข้ากันได้กับคลาส concurrent.futures.Future ที่แสดงถึงการคำนวณที่ยังไม่สำเร็จ มีความแตกต่างดังต่อไปนี้ระหว่าง asyncio.futures.Future และ concurrent.futures.Future -

  • วิธีการ result () และข้อยกเว้น () ไม่ใช้อาร์กิวเมนต์การหมดเวลาและเพิ่มข้อยกเว้นเมื่อยังไม่เสร็จสิ้นในอนาคต

  • การโทรกลับที่ลงทะเบียนด้วย add_done_callback () จะถูกเรียกผ่าน call_soon () ของลูปเหตุการณ์เสมอ

  • asyncio.futures คลาสในอนาคตเข้ากันไม่ได้กับฟังก์ชัน wait () และ as_completed () ในแพ็คเกจ concurrent.futures

ตัวอย่าง

ต่อไปนี้เป็นตัวอย่างที่จะช่วยให้คุณเข้าใจวิธีใช้คลาส asyncio.futures.future

import asyncio

async def Myoperation(future):
   await asyncio.sleep(2)
   future.set_result('Future Completed')

loop = asyncio.get_event_loop()
future = asyncio.Future()
asyncio.ensure_future(Myoperation(future))
try:
   loop.run_until_complete(future)
   print(future.result())
finally:
   loop.close()

เอาต์พุต

Future Completed

โครูทีน

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

ฟังก์ชัน async def ()

นี่เป็นวิธีการใช้โครูทีนภายใต้โมดูล Asyncio ต่อไปนี้เป็นสคริปต์ Python สำหรับสิ่งเดียวกัน -

import asyncio

async def Myoperation():
   print("First Coroutine")

loop = asyncio.get_event_loop()
try:
   loop.run_until_complete(Myoperation())

finally:
   loop.close()

เอาต์พุต

First Coroutine

@ asyncio.coroutine มัณฑนากร

อีกวิธีในการใช้โครูทีนคือการใช้เครื่องกำเนิดไฟฟ้ากับมัณฑนากร @ asyncio.coroutine ต่อไปนี้เป็นสคริปต์ Python สำหรับสิ่งเดียวกัน -

import asyncio

@asyncio.coroutine
def Myoperation():
   print("First Coroutine")

loop = asyncio.get_event_loop()
try:
   loop.run_until_complete(Myoperation())

finally:
   loop.close()

เอาต์พุต

First Coroutine

งาน

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

import asyncio
import time
async def Task_ex(n):
   time.sleep(1)
   print("Processing {}".format(n))
async def Generator_task():
   for i in range(10):
      asyncio.ensure_future(Task_ex(i))
   int("Tasks Completed")
   asyncio.sleep(2)

loop = asyncio.get_event_loop()
loop.run_until_complete(Generator_task())
loop.close()

เอาต์พุต

Tasks Completed
Processing 0
Processing 1
Processing 2
Processing 3
Processing 4
Processing 5
Processing 6
Processing 7
Processing 8
Processing 9

ขนส่ง

โมดูล Asyncio มีคลาสการขนส่งสำหรับการใช้งานการสื่อสารประเภทต่างๆ คลาสเหล่านี้ไม่ใช่เธรดที่ปลอดภัยและจะจับคู่กับอินสแตนซ์โปรโตคอลเสมอหลังจากสร้างช่องทางการสื่อสาร

ต่อไปนี้เป็นประเภทที่แตกต่างกันของการขนส่งที่สืบทอดมาจาก BaseTransport -

  • ReadTransport - นี่คืออินเทอร์เฟซสำหรับการขนส่งแบบอ่านอย่างเดียว

  • WriteTransport - นี่คืออินเทอร์เฟซสำหรับการขนส่งแบบเขียนอย่างเดียว

  • DatagramTransport - นี่คืออินเทอร์เฟซสำหรับการส่งข้อมูล

  • BaseSubprocessTransport - คล้ายกับคลาส BaseTransport

ต่อไปนี้เป็นวิธีการที่แตกต่างกันห้าวิธีของคลาส BaseTransport ซึ่งจะเกิดขึ้นชั่วคราวในเวลาต่อมาในประเภทการขนส่งทั้งสี่ประเภท -

  • close() - ปิดการขนส่ง

  • is_closing() - วิธีนี้จะคืนค่าเป็นจริงหากการขนส่งปิดหรือปิดแล้วการขนส่ง

  • get_extra_info(name, default = none) - ข้อมูลนี้จะให้ข้อมูลเพิ่มเติมเกี่ยวกับการขนส่ง

  • get_protocol() - วิธีนี้จะส่งคืนโปรโตคอลปัจจุบัน

โปรโตคอล

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

  • Protocol - นี่คือคลาสพื้นฐานสำหรับการนำโปรโตคอลการสตรีมไปใช้กับการขนส่ง TCP และ SSL

  • DatagramProtocol - นี่คือคลาสพื้นฐานสำหรับการนำโปรโตคอลดาตาแกรมไปใช้กับการขนส่ง UDP ..

  • SubprocessProtocol - นี่คือคลาสพื้นฐานสำหรับการใช้โปรโตคอลที่สื่อสารกับโปรเซสลูกผ่านชุดของท่อทิศทางเดียว

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

ReactiveX หรือ RX สำหรับการเขียนโปรแกรมแบบปฏิกิริยา

ReactiveX หรือ Raective Extension เป็นการนำโปรแกรมปฏิกิริยาที่มีชื่อเสียงที่สุดมาใช้ การทำงานของ ReactiveX ขึ้นอยู่กับสองคลาสต่อไปนี้ -

ชั้นสังเกตได้

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

ชั้นสังเกตการณ์

คลาสนี้ใช้สตรีมข้อมูลที่ปล่อยออกมาโดย observable. อาจมีผู้สังเกตการณ์หลายคนที่สามารถสังเกตได้และผู้สังเกตการณ์แต่ละคนจะได้รับข้อมูลแต่ละรายการที่ปล่อยออกมา ผู้สังเกตการณ์สามารถรับเหตุการณ์สามประเภทโดยสมัครรับข้อมูลที่สังเกตได้ -

  • on_next() event - หมายความว่ามีองค์ประกอบในสตรีมข้อมูล

  • on_completed() event - หมายถึงการสิ้นสุดของการปล่อยและไม่มีรายการอื่น ๆ มาอีก

  • on_error() event - นอกจากนี้ยังหมายถึงการสิ้นสุดของการปล่อย แต่ในกรณีที่ข้อผิดพลาดเกิดขึ้น observable.

RxPY - โมดูล Python สำหรับการเขียนโปรแกรมแบบตอบสนอง

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

pip install RxPY

ตัวอย่าง

ต่อไปนี้เป็นสคริปต์ Python ซึ่งใช้ไฟล์ RxPY โมดูลและคลาส Observable และ Observe forการเขียนโปรแกรมปฏิกิริยา โดยทั่วไปมีสองคลาส -

  • get_strings() - สำหรับการรับสายจากผู้สังเกตการณ์

  • PrintObserver()- สำหรับการพิมพ์สตริงจากผู้สังเกตการณ์ ใช้ทั้งสามเหตุการณ์ของคลาสผู้สังเกตการณ์ นอกจากนี้ยังใช้คลาส subscribe ()

from rx import Observable, Observer
def get_strings(observer):
   observer.on_next("Ram")
   observer.on_next("Mohan")
   observer.on_next("Shyam")
      observer.on_completed()
class PrintObserver(Observer):
   def on_next(self, value):
      print("Received {0}".format(value))
   def on_completed(self):
   print("Finished")
   def on_error(self, error):
      print("Error: {0}".format(error))
source = Observable.create(get_strings)
source.subscribe(PrintObserver())

เอาต์พุต

Received Ram
Received Mohan
Received Shyam
Finished

ไลบรารี PyFunctional สำหรับการเขียนโปรแกรมปฏิกิริยา

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

ความแตกต่างระหว่าง RxPY และ PyFunctional

ไลบรารีทั้งสองใช้สำหรับการเขียนโปรแกรมแบบรีแอคทีฟและจัดการสตรีมในลักษณะที่คล้ายคลึงกัน แต่ความแตกต่างหลักระหว่างไลบรารีทั้งสองขึ้นอยู่กับการจัดการข้อมูล RxPY จัดการข้อมูลและเหตุการณ์ในระบบในขณะที่ PyFunctional มุ่งเน้นไปที่การแปลงข้อมูลโดยใช้กระบวนทัศน์การเขียนโปรแกรมเชิงฟังก์ชัน

การติดตั้งโมดูล PyFunctional

เราจำเป็นต้องติดตั้งโมดูลนี้ก่อนใช้งาน สามารถติดตั้งได้ด้วยความช่วยเหลือของคำสั่ง pip ดังนี้ -

pip install pyfunctional

ตัวอย่าง

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

from functional import seq

result = seq(1,2,3).map(lambda x: x*2).filter(lambda x: x > 4).reduce(lambda x, y: x + y)

print ("Result: {}".format(result))

เอาต์พุต

Result: 6