2012-02-06 42 views
7

Bất cứ ai có thể giải thích, tại sao chương trình này trả lại giá trị chính xác cho sqrt_min?parallel.foreach hoạt động, nhưng tại sao?

int n = 1000000; 

double[] myArr = new double[n]; 
for(int i = n-1 ; i>= 0; i--){ myArr[i] = (double)i;} 

// sqrt_min contains minimal sqrt-value 
double sqrt_min = double.MaxValue; 

Parallel.ForEach(myArr, num => 
{ 
double sqrt = Math.Sqrt(num); // some time consuming calculation that should be parallized 
if(sqrt < sqrt_min){ sqrt_min = sqrt;} 
}); 
Console.WriteLine("minimum: "+sqrt_min); 
+1

xem thêm http://stackoverflow.com/questions/3679209/why-doesnt-this-code-demonstrate-the-non-atomicity-of-reads-writes nó có thể không may mắn. Nó có thể là do cpu của bạn cung cấp các hoạt động gấp đôi nguyên tử mặc dù C# không đảm bảo nó. – hatchet

+1

Có khả năng sửa lỗi này sẽ giới thiệu đủ tranh chấp khóa rằng nó sẽ chậm hơn so với chỉ tính căn bậc hai trên một sợi. Một giải pháp tốt hơn là để có mỗi CPU tính toán nhiều căn bậc hai, theo dõi mức tối thiểu của chúng và tìm ra mức tối thiểu toàn cầu khi mọi CPU được thực hiện. Bạn vẫn sẽ có tranh chấp, nhưng tranh chấp sẽ chỉ áp dụng trong khi so sánh các mức tối thiểu địa phương cụ thể theo chủ đề, thay vì cho mỗi một căn bậc hai. – Brian

Trả lời

13

Nó hoạt động bằng sự may mắn tuyệt đối. Đôi khi, khi bạn chạy nó, bạn là may mắn rằng các lần đọc và ghi không phải nguyên tử để tăng gấp đôi không dẫn đến các giá trị "bị rách". Đôi khi bạn là may mắn rằng các thử nghiệm và bộ phi nguyên tử chỉ xảy ra khi đặt giá trị chính xác khi cuộc đua đó xảy ra. Không có gì đảm bảo rằng chương trình này tạo ra bất kỳ kết quả cụ thể nào.

+0

vì vậy cách tốt nhất để giải quyết vấn đề này là gì? – user1193134

+1

@ user1193134: Nói chung, khóa hoặc (cực kỳ cẩn thận) hoạt động nguyên tử. Trong trường hợp cụ thể của bạn, PLINQ 'Aggregate()' hoặc chỉ 'Min()'. – SLaks

+0

bạn có thể giúp tôi giải quyết vấn đề này không? – user1193134

5

Mã của bạn không an toàn; nó chỉ hoạt động bằng sự trùng hợp ngẫu nhiên.

Nếu hai luồng chạy if đồng thời, một trong những tối thiểu sẽ được ghi đè:

  • sqrt_min = 6
  • Chủ đề A: sqrt = 5
  • Thread B: sqrt = 4
  • Chủ đề A tham gia vào if
  • Chủ đề B đi vào if
  • Thread B gán sqrt_min = 4
  • Chủ đề Một gán sqrt_min = 5

Trên các hệ thống 32-bit, bạn cũng dễ bị tổn thương để đọc/ghi rách.

Có thể làm điều này an toàn khi sử dụng Interlocked.CompareExchange trong một vòng lặp.

+0

đó là những gì tôi nghĩ! Nhưng tôi chưa bao giờ đến điểm này! Ngay cả khi cố gắng rất nhiều và khá nhiều lần> 10^9 – user1193134

+0

@ user1193134: Kích thước của mảng không thực sự quan trọng (trừ khi bạn có hàng trăm lõi). Sử dụng sửa đổi của dasblinkenlight, tôi nhận được những thất bại nhất quán. – SLaks

+0

bạn dường như rất quen thuộc với điều Interlocked.CompareExchange. Bạn có thể cho tôi một bàn tay với vấn đề này không? – user1193134

3

Mã của bạn không thực sự làm việc: Tôi chạy nó trong một vòng lặp 100.000 lần, và nó đã thất bại một lần trên máy tính 8 lõi của tôi, tạo ra sản lượng này:

minimum: 1 

tôi rút ngắn chạy để làm cho lỗi xuất hiện nhanh hơn.

Dưới đây là những thay đổi của tôi:

static void Run() { 
    int n = 10; 

    double[] myArr = new double[n]; 
    for (int i = n - 1; i >= 0; i--) { myArr[i] = (double)i*i; } 

    // sqrt_min contains minimal sqrt-value 
    double sqrt_min = double.MaxValue; 

    Parallel.ForEach(myArr, num => { 
     double sqrt = Math.Sqrt(num); // some time consuming calculation that should be parallized 
     if (sqrt < sqrt_min) { sqrt_min = sqrt; } 
    }); 
    if (sqrt_min > 0) { 
     Console.WriteLine("minimum: " + sqrt_min); 
    } 
} 


static void Main() { 
    for (int i = 0; i != 100000; i++) { 
     Run(); 
    } 
} 

Đây không phải là một trùng hợp ngẫu nhiên, xem xét việc thiếu đồng bộ xung quanh việc đọc và viết của một biến chia sẻ.

+0

Tôi cũng bị lỗi. – SLaks

2

Như những người khác đã nói, điều này chỉ hoạt động dựa trên may mắn cắt. Cả hai OP và các áp phích khác đã gặp rắc rối thực sự tạo ra các điều kiện chủng tộc mặc dù. Điều đó khá dễ giải thích. Mã này tạo ra rất nhiều điều kiện chủng tộc, nhưng phần lớn trong số đó (chính xác là 99.9999%) là không liên quan. Tất cả những gì quan trọng vào cuối ngày là thực tế là 0 phải là kết quả tối thiểu. Nếu mã của bạn cho rằng root 5 lớn hơn root 6, hoặc root đó lớn hơn 235 gốc thì nó vẫn không bị phá vỡ. Có cần phải là một điều kiện chủng tộc đặc biệt với việc tạo lặp lại 0. Tỷ lệ cược rằng một trong các lần lặp lại có điều kiện cuộc đua với một điều kiện khác là rất, rất cao. Các tỷ lệ cược rằng việc xử lý lặp lại mục cuối cùng có một điều kiện chủng tộc thực sự là khá thấp.

4

Vì sao mã ban đầu của bạn bị hỏng kiểm tra các câu trả lời khác, tôi sẽ không lặp lại điều đó.

Đa luồng dễ dàng nhất khi không có quyền ghi vào trạng thái được chia sẻ. May mắn là mã của bạn có thể được viết theo cách đó. Song song LINQ có thể được tốt đẹp trong tình huống như vậy, nhưng đôi khi các chi phí quá lớn.

Bạn có thể viết lại mã của bạn để:

double sqrt_min = myArr.AsParallel().Select(x=>Math.Sqrt(x)).Min(); 

Trong bài toán cụ thể của bạn nó nhanh hơn để trao đổi xung quanh và các hoạt động MinSqrt, mà có thể vì Sqrt đang gia tăng đơn điệu.

double sqrt_min = Math.Sqrt(myArr.AsParallel().Min()) 
Các vấn đề liên quan