เธรดพูลถูกใช้เมื่อใด


104

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

คำถามของฉันคือถ้าฉันตั้งเซิร์ฟเวอร์ HTTP ใน Node.js และเรียกใช้โหมดสลีปบนหนึ่งในเหตุการณ์เส้นทางที่กำหนดเส้นทางของฉัน (เช่น "/ test / sleep") ระบบทั้งหมดจะหยุดทำงาน แม้แต่กระทู้ผู้ฟังคนเดียว แต่ความเข้าใจของฉันคือรหัสนี้เกิดขึ้นในกลุ่มผู้ปฏิบัติงาน

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

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


@ โทบิ - ฉันเห็นแล้ว มันยังไม่ตอบคำถามของฉัน หากงานอยู่ในเธรดอื่นการนอนหลับจะส่งผลต่อเธรดนั้นเท่านั้นและไม่ใช่ผู้ฟังด้วย
Haney

8
คำถามที่แท้จริงที่คุณพยายามทำความเข้าใจบางสิ่งด้วยตัวเองและเมื่อคุณไม่พบทางออกไปยังเขาวงกตคุณก็ขอความช่วยเหลือ
Rafael Eyng

คำตอบ:


242

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

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

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

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

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


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

  • โมดูลภายนอกใด ๆ ที่คุณรวมไว้ในโปรเจ็กต์ของคุณที่ใช้ C ++ ดั้งเดิมและ libuv มีแนวโน้มที่จะใช้เธรดพูล (คิดว่า: การเข้าถึงฐานข้อมูล)
  • libuv มีเธรดพูลดีฟอลต์ขนาด 4 และใช้คิวเพื่อจัดการการเข้าถึงเธรดพูล - ผลลัพธ์คือถ้าคุณมีคิวรี DB ที่รันเป็นเวลานาน 5 คิวรีทั้งหมดพร้อมกันหนึ่งในนั้น (และอะซิงโครนัสอื่น ๆ การดำเนินการที่อาศัยเธรดพูล) จะรอให้แบบสอบถามเหล่านั้นเสร็จสิ้นก่อนที่จะเริ่มต้นด้วยซ้ำ
  • คุณสามารถลดสิ่งนี้ได้โดยการเพิ่มขนาดของเธรดพูลผ่านUV_THREADPOOL_SIZEตัวแปรสภาพแวดล้อมตราบใดที่คุณทำก่อนที่เธรดพูลจะจำเป็นและสร้างขึ้น:process.env.UV_THREADPOOL_SIZE = 10;

หากคุณต้องการหลายการประมวลผลแบบดั้งเดิมหรือมัลติเธรดในโหนดคุณสามารถรับได้จากclusterโมดูลในตัวหรือโมดูลอื่น ๆ เช่นที่กล่าวมาข้างต้นwebworker-threadsหรือคุณสามารถปลอมได้โดยใช้วิธีการแยกชิ้นส่วนงานของคุณและด้วยตนเองโดยใช้setTimeoutหรือsetImmediateหรือprocess.nextTickหยุดงานของคุณชั่วคราวและดำเนินการต่อในลูปในภายหลังเพื่อให้กระบวนการอื่น ๆ เสร็จสมบูรณ์ (แต่ไม่แนะนำ)

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


1
อึศักดิ์สิทธิ์สิ่งนี้ทำให้ฉันกระจ่างขึ้นอย่างสมบูรณ์ ขอบคุณมาก @Jason!
Haney

5
ไม่มีปัญหา :) ฉันพบว่าตัวเองอยู่ที่ไหนเมื่อไม่นานมานี้และเป็นการยากที่จะหาคำตอบที่กำหนดไว้อย่างดีเพราะในด้านหนึ่งคุณมีนักพัฒนา C / C ++ ซึ่งคำตอบนั้นชัดเจนและอีกด้านหนึ่งที่คุณมีโดยทั่วไป นักพัฒนาเว็บที่ไม่เคยเจาะลึกคำถามประเภทนี้มาก่อน ฉันไม่แน่ใจด้วยซ้ำว่าคำตอบของฉันถูกต้องทางเทคนิค 100% เมื่อคุณลงไปที่ระดับ C แต่มันถูกต้องในจังหวะกว้าง ๆ
Jason

3
การใช้เธรดพูลสำหรับคำขอเครือข่ายจะเป็นการสิ้นเปลืองทรัพยากรอย่างมาก จากคำถามนี้ "มันทำ I / O ของเครือข่าย async ตามอินเทอร์เฟซ async I / O ในแพลตฟอร์มต่างๆเช่น epoll, kqueue และ IOCP โดยไม่มีเธรดพูล" ซึ่งก็สมเหตุสมผลดี
Denis Dollfus

1
... ที่กล่าวว่าหากคุณทำอย่างหนักในเธรดจาวาสคริปต์หลักโดยตรงหรือคุณมีทรัพยากรไม่เพียงพอหรือไม่ได้จัดการอย่างเหมาะสมเพื่อให้ headroom เพียงพอกับเธรดพูลคุณสามารถแนะนำความล่าช้าในการทำงานพร้อมกันที่ลดลง threshold - ผลลัพธ์คือสำหรับทรัพยากรระบบเดียวกันโดยทั่วไปคุณจะพบ thruput ที่สูงขึ้นด้วย node.js มากกว่าตัวเลือกอื่น ๆ (แม้ว่าจะมีระบบที่อิงเหตุการณ์อื่น ๆ ในภาษาอื่น ๆ ที่มีเป้าหมายที่จะท้าทายสิ่งนั้น - ฉันไม่ได้ เห็นการเปรียบเทียบล่าสุด) - เป็นที่ชัดเจนว่าโมเดลตามเหตุการณ์มีประสิทธิภาพดีกว่าโมเดลเธรด
Jason

1
@Aabid เธรดตัวฟังไม่ได้ดำเนินการสืบค้นฐานข้อมูลดังนั้นจะใช้เวลาประมาณ 6 วินาทีในการดำเนินการค้นหาทั้งหมด 10 รายการ (โดยค่าเริ่มต้นเธรดพูลขนาด 4) หากคุณจำเป็นต้องทำงานใด ๆ ใน javascript ที่ไม่ต้องการให้ผลลัพธ์ของการสืบค้นฐานข้อมูลนั้นเสร็จสมบูรณ์เช่นมีการร้องขอเพิ่มเติมเข้ามาซึ่งไม่จำเป็นต้องทำงานแบบอะซิงโครนัสใด ๆ ให้เสร็จสมบูรณ์โดยเธรดพูลก็จะยังคงทำงานในส่วนหลัก วนเหตุการณ์
Jason

20

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

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

คำถามของฉันคือถ้าฉันตั้งเซิร์ฟเวอร์ HTTP ใน Node.js และเรียกใช้โหมดสลีปบนหนึ่งในเหตุการณ์เส้นทางที่กำหนดเส้นทางของฉัน (เช่น "/ test / sleep") ระบบทั้งหมดจะหยุดทำงาน แม้แต่กระทู้ผู้ฟังคนเดียว แต่ความเข้าใจของฉันคือรหัสนี้เกิดขึ้นในกลุ่มผู้ปฏิบัติงาน

ไม่มีกลไกการนอนหลับใน JavaScript เราสามารถพูดคุยเรื่องนี้อย่างเป็นรูปธรรมมากขึ้นหากคุณโพสต์ข้อมูลโค้ดที่คุณคิดว่า "การนอนหลับ" หมายถึง ไม่มีฟังก์ชันดังกล่าวในการเรียกใช้เพื่อจำลองสิ่งต่างๆเช่นtime.sleep(30)ใน python เป็นต้น มีsetTimeoutแต่โดยพื้นฐานแล้วไม่ใช่การนอนหลับ setTimeoutและปล่อยsetIntervalอย่างชัดเจนไม่ใช่บล็อกลูปเหตุการณ์เพื่อให้บิตของโค้ดอื่น ๆ สามารถรันบนเธรดการดำเนินการหลักได้ สิ่งเดียวที่คุณสามารถทำได้คือไม่ว่างวนรอบ CPU ด้วยการคำนวณในหน่วยความจำซึ่งจะทำให้เธรดการดำเนินการหลักอดอาหารและทำให้โปรแกรมของคุณไม่ตอบสนอง

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

Network IO เป็นแบบอะซิงโครนัสเสมอ ตอนจบของเรื่อง. Disk IO มีทั้ง API แบบซิงโครนัสและอะซิงโครนัสดังนั้นจึงไม่มี "การตัดสินใจ" node.js จะทำงานตามฟังก์ชันหลักของ API ที่คุณเรียกว่า sync เทียบกับ async ปกติ ตัวอย่างเช่น: VSfs.readFile fs.readFileSyncสำหรับกระบวนการย่อยยังมีแยกต่างหากchild_process.execและchild_process.execSyncAPI

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


1
API แบบอะซิงโครนัสเหล่านี้มาจากไหน ฉันเข้าใจว่าคุณกำลังพูดอะไร แต่ใครก็ตามที่เขียน API นี้เลือกใช้ IOCP / async พวกเขาเลือกทำสิ่งนี้ได้อย่างไร?
Haney

3
คำถามของเขาคือเขาจะเขียนโค้ดเร่งรัดเวลาของตัวเองอย่างไรและไม่ปิดกั้น
Jason

1
ใช่. โหนดมีเครือข่าย UDP, TCP และ HTTP พื้นฐาน มีเฉพาะ API แบบอะซิงโครนัส "ตามพูล" เท่านั้น โค้ด node.js ทั้งหมดในโลกโดยไม่มีข้อยกเว้นใช้ API แบบอะซิงโครนัสแบบพูลตามที่มีทั้งหมดที่มีอยู่ ระบบไฟล์และกระบวนการย่อยเป็นคนละเรื่องกัน แต่ระบบเครือข่ายเป็นแบบอะซิงโครนัสอย่างสม่ำเสมอ
Peter Lyons

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

2
โหนดไม่ใช้เธรดพูลสำหรับ IO มันใช้ IO แบบเนทีฟที่ไม่ปิดกั้นข้อยกเว้นเพียงอย่างเดียวคือfsเท่าที่ฉันรู้
vkurchatkin

2

เธรดพูลว่าเมื่อไรและใครใช้:

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

ป้อนคำอธิบายภาพที่นี่

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

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

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

ป้อนคำอธิบายภาพที่นี่

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

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

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

ป้อนคำอธิบายภาพที่นี่

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

ดังนั้นเธรดทั้งหมดเหล่านี้จึงรวมกันเป็นเธรดพูล จากนั้นลูปเหตุการณ์จะสามารถปลดภาระงานหนักไปยังเธรดพูลโดยอัตโนมัติ

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

มีงานมากมายที่ไปที่พูลเธรดเช่น

-> All operations dealing with files
->Everyting is related to cryptography, like caching passwords.
->All compression stuff
->DNS lookups

0

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

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

... ดังนั้นอย่าปิดกั้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.