ParallelForEach จำกัด จำนวนเธรดที่ใช้งานอยู่หรือไม่


107

ให้รหัสนี้:

var arrayStrings = new string[1000];
Parallel.ForEach<string>(arrayStrings, someString =>
{
    DoSomething(someString);
});

เธรดทั้งหมด 1,000 เธรดจะเกิดเกือบพร้อมกันหรือไม่?

คำตอบ:


149

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

มีลักษณะที่เป็นบล็อก PFX ทีมสำหรับการโหลดของข้อมูลเกี่ยวกับวิธีการที่จะจัดสรรงานและทุกชนิดของหัวข้ออื่น ๆ

โปรดทราบว่าในบางกรณีคุณสามารถระบุระดับความขนานที่ต้องการได้เช่นกัน


2
ฉันใช้ ParallelForEach (FilePathArray, path => ... เพื่ออ่านประมาณ 24,000 ไฟล์ในคืนนี้โดยสร้างไฟล์ใหม่หนึ่งไฟล์สำหรับแต่ละไฟล์ที่ฉันอ่านรหัสง่ายมากดูเหมือนว่ามี 6 เธรดก็เพียงพอที่จะทำให้ดิสก์ 7200 RPM ล้น ฉันกำลังอ่านจากการใช้งาน 100% ในช่วงสองสามชั่วโมงฉันดูไลบรารี Parallel แยกออกมากกว่า 8,000 เธรดฉันทดสอบโดยใช้ MaxDegreeOfParallelism และแน่นอนว่าเธรด 8000+ หายไปฉันได้ทดสอบหลายครั้งแล้วด้วยสิ่งเดียวกัน ผล
Jake Drew

มันสามารถเริ่ม 1,000 เธรดสำหรับ 'DoSomething' ที่เสื่อมสภาพ (เช่นในกรณีที่ฉันกำลังจัดการกับปัญหาเกี่ยวกับรหัสการผลิตที่ไม่สามารถตั้งค่าขีด จำกัด และสร้างเธรดมากกว่า 200 รายการขึ้นมาจึงทำให้พูลการเชื่อมต่อ SQL ปรากฏขึ้น .. ฉันขอแนะนำให้ตั้งค่า DOP สูงสุดสำหรับงานใด ๆ ที่ไม่สามารถให้เหตุผลได้เล็กน้อย เกี่ยวกับการถูกผูกมัดของ CPU อย่างชัดเจน)
user2864740


28

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

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

ฉันไม่พบเอกสารเกี่ยวกับการยกเลิกและการจัดการเธรดการซิงโครไนซ์ที่มีประโยชน์มาก หวังว่า MS สามารถจัดหาตัวอย่างที่ดีกว่าใน MSDN ได้

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


1
".. หากส่วนเนื้อหาของ ForEach เรียกใช้ฟังก์ชันการบล็อกที่ทำงานเป็นเวลานานซึ่งจะปล่อยให้เธรดรอรอบอัลกอริทึมจะสร้างเธรดมากขึ้น ต่อ ThreadPool
user2864740

2
คุณพูดถูกสำหรับ IO มันอาจจัดสรรเธรด +100 ในขณะที่ฉันดีบักตัวเอง
FindOutIslamNow


5

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


4

คำถามที่ดี ในตัวอย่างของคุณระดับการขนานนั้นค่อนข้างต่ำแม้ในโปรเซสเซอร์ Quad Core แต่ด้วยการรอระดับการขนานจะค่อนข้างสูง

// Max concurrency: 5
[Test]
public void Memory_Operations()
{
    ConcurrentBag<int> monitor = new ConcurrentBag<int>();
    ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
    var arrayStrings = new string[1000];
    Parallel.ForEach<string>(arrayStrings, someString =>
    {
        monitor.Add(monitor.Count);
        monitor.TryTake(out int result);
        monitorOut.Add(result);
    });

    Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}

ตอนนี้ดูว่าจะเกิดอะไรขึ้นเมื่อมีการเพิ่มการดำเนินการรอเพื่อจำลองคำขอ HTTP

// Max concurrency: 34
[Test]
public void Waiting_Operations()
{
    ConcurrentBag<int> monitor = new ConcurrentBag<int>();
    ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
    var arrayStrings = new string[1000];
    Parallel.ForEach<string>(arrayStrings, someString =>
    {
        monitor.Add(monitor.Count);

        System.Threading.Thread.Sleep(1000);

        monitor.TryTake(out int result);
        monitorOut.Add(result);
    });

    Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}

ฉันยังไม่ได้ทำการเปลี่ยนแปลงใด ๆ และระดับของการทำงานพร้อมกัน / การขนานกันได้เพิ่มขึ้นอย่างมาก เห็นพ้องสามารถมีขีด จำกัด ParallelOptions.MaxDegreeOfParallelismของเพิ่มขึ้นด้วย

// Max concurrency: 43
[Test]
public void Test()
{
    ConcurrentBag<int> monitor = new ConcurrentBag<int>();
    ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
    var arrayStrings = new string[1000];
    var options = new ParallelOptions {MaxDegreeOfParallelism = int.MaxValue};
    Parallel.ForEach<string>(arrayStrings, options, someString =>
    {
        monitor.Add(monitor.Count);

        System.Threading.Thread.Sleep(1000);

        monitor.TryTake(out int result);
        monitorOut.Add(result);
    });

    Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}

// Max concurrency: 391
[Test]
public void Test()
{
    ConcurrentBag<int> monitor = new ConcurrentBag<int>();
    ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
    var arrayStrings = new string[1000];
    var options = new ParallelOptions {MaxDegreeOfParallelism = int.MaxValue};
    Parallel.ForEach<string>(arrayStrings, options, someString =>
    {
        monitor.Add(monitor.Count);

        System.Threading.Thread.Sleep(100000);

        monitor.TryTake(out int result);
        monitorOut.Add(result);
    });

    Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}

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

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

// 636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623368346
// 636462943623368346
// 636462943623373351
// 636462943623393364
// 636462943623393364
[Test]
public void Test()
{
    ConcurrentBag<string> monitor = new ConcurrentBag<string>();
    ConcurrentBag<string> monitorOut = new ConcurrentBag<string>();
    var arrayStrings = new string[1000];
    var options = new ParallelOptions {MaxDegreeOfParallelism = int.MaxValue};
    Parallel.ForEach<string>(arrayStrings, options, someString =>
    {
        monitor.Add(DateTime.UtcNow.Ticks.ToString());
        monitor.TryTake(out string result);
        monitorOut.Add(result);
    });

    var startTimes = monitorOut.OrderBy(x => x.ToString()).ToList();
    Console.WriteLine(string.Join(Environment.NewLine, startTimes.Take(10)));
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.