Parallel.ForEach có giới hạn số lượng chủ đề đang hoạt động không?


107

Đưa ra mã này:

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

Tất cả 1000 chủ đề sẽ sinh ra gần như đồng thời?

Câu trả lời:


149

Không, nó sẽ không bắt đầu 1000 chủ đề - vâng, nó sẽ giới hạn số lượng chủ đề được sử dụng. Tiện ích mở rộng song song sử dụng số lượng lõi thích hợp, dựa trên số lượng lõi bạn có bao nhiêu lõi đã bận. Nó phân bổ công việc cho mỗi lõi và sau đó sử dụng một kỹ thuật được gọi là đánh cắp công việc để cho phép mỗi luồng xử lý hàng đợi của chính nó một cách hiệu quả và chỉ cần thực hiện bất kỳ truy cập xuyên luồng đắt tiền nào khi nó thực sự cần.

Hãy xem Blog của Nhóm PFX để biết số thông tin về cách nó phân bổ công việc và tất cả các loại chủ đề khác.

Lưu ý rằng trong một số trường hợp, bạn cũng có thể chỉ định mức độ song song mà bạn muốn.


2
Tôi đang sử dụng Parallel.ForEach (FilePathArray, path => ... để đọc khoảng 24.000 tệp tối nay, tạo một tệp mới cho mỗi tệp mà tôi đọc. Mã rất đơn giản. Có vẻ như ngay cả 6 luồng cũng đủ để áp đảo đĩa 7200 RPM Tôi đang đọc với mức sử dụng 100%. Trong khoảng thời gian vài giờ, tôi đã xem thư viện Parallel xử lý hơn 8.000 chủ đề. Tôi đã thử nghiệm bằng cách sử dụng MaxDegreeOfParallelism và chắc chắn rằng 8000+ chủ đề đã biến mất. Tôi đã thử nghiệm nó nhiều lần với cùng một kết quả.
Jake Drew

có thể bắt đầu 1000 luồng cho một số 'DoSomething' đang bị thoái hóa. (Như trong trường hợp tôi hiện đang xử lý sự cố trong mã sản xuất không đặt được giới hạn và tạo ra hơn 200 luồng do đó xuất hiện nhóm kết nối SQL .. Tôi khuyên bạn nên đặt Max DOP cho bất kỳ công việc nào không thể lý giải được về như là một cách rõ ràng CPU bị ràng buộc).
user2864740


28

Trên một máy lõi đơn ... Parallel.Mỗi phân vùng (phần) của bộ sưu tập mà nó đang hoạt động giữa một số luồng, nhưng con số đó được tính toán dựa trên một thuật toán có tính đến và dường như liên tục theo dõi công việc được thực hiện bởi luồng nó đang phân bổ cho ForEach. Vì vậy, nếu phần nội dung của ForEach gọi ra các hàm IO-ràng buộc / chặn đang chạy dài sẽ khiến chuỗi chờ xung quanh, thuật toán sẽ tạo ra nhiều chuỗi hơn và phân vùng lại bộ sưu tập giữa chúng . Nếu các chuỗi hoàn thành nhanh chóng và không chặn trên các chuỗi IO, chẳng hạn như chỉ cần tính toán một số số,thuật toán sẽ tăng (hoặc thực sự giảm) số luồng đến một điểm mà thuật toán coi là tối ưu cho thông lượng (thời gian hoàn thành trung bình của mỗi lần lặp) .

Về cơ bản nhóm luồng đằng sau tất cả các chức năng thư viện Song song khác nhau, sẽ tính ra số lượng luồng tối ưu để sử dụng. Số lượng lõi xử lý vật lý chỉ là một phần của phương trình. KHÔNG có mối quan hệ đơn giản 1-1 giữa số lõi và số luồng được tạo ra.

Tôi không thấy tài liệu về việc hủy và xử lý đồng bộ hóa các luồng rất hữu ích. Hy vọng rằng MS có thể cung cấp các ví dụ tốt hơn trong MSDN.

Đừng quên, mã phần thân phải được viết để chạy trên nhiều luồng, cùng với tất cả các cân nhắc về an toàn luồng thông thường, khung công tác chưa tóm tắt yếu tố đó ...


1
"..nếu phần thân của ForEach gọi đến các hàm chặn đang chạy lâu sẽ khiến chuỗi chờ xung quanh, thuật toán sẽ sinh ra nhiều chủ đề hơn .." - Trong các trường hợp thoái hóa, điều này có nghĩa là có thể có nhiều luồng được tạo ra cho phép mỗi ThreadPool.
user2864740

2
Bạn là đúng, cho IO nó có thể phân bổ 100 chủ đề như tôi sửa lỗi bản thân mình
FindOutIslamNow

5

Nó tính toán số luồng tối ưu dựa trên số lượng bộ xử lý / lõi. Chúng sẽ không sinh sản cùng một lúc.



4

Câu hỏi tuyệt vời. Trong ví dụ của bạn, mức độ song song là khá thấp ngay cả trên bộ vi xử lý lõi tứ, nhưng với một số chờ đợi, mức độ song song có thể khá cao.

// 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());
}

Bây giờ hãy xem điều gì sẽ xảy ra khi một thao tác chờ được thêm vào để mô phỏng một yêu cầu 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());
}

Tôi chưa thực hiện bất kỳ thay đổi nào và mức độ đồng thời / song song đã tăng một cách kịch tính. Đồng tiền có thể có giới hạn của nó tăng lên với 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());
}

Tôi khuyên bạn nên thiết lập ParallelOptions.MaxDegreeOfParallelism. Nó không nhất thiết phải tăng số lượng chủ đề được sử dụng, nhưng nó sẽ đảm bảo bạn chỉ bắt đầu một số lượng chủ đề lành mạnh, điều này dường như là mối quan tâm của bạn.

Cuối cùng để trả lời câu hỏi của bạn, không, bạn sẽ không nhận được tất cả các chủ đề để bắt đầu cùng một lúc. Sử dụng Parallel.Invoke nếu bạn đang muốn gọi song song một cách hoàn hảo, ví dụ: thử nghiệm các điều kiện cuộc đua.

// 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)));
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.