“Cuộc đua dữ liệu” và “cuộc đua điều kiện” có thực sự giống nhau trong bối cảnh lập trình đồng thời không


Câu trả lời:


135

Không, chúng không giống nhau. Chúng không phải là một tập hợp con của nhau. Chúng cũng không phải là điều kiện cần, cũng không phải là điều kiện đủ cho nhau.

Định nghĩa về một cuộc đua dữ liệu khá rõ ràng và do đó, việc khám phá nó có thể được tự động hóa. Một cuộc chạy đua dữ liệu xảy ra khi 2 lệnh từ các luồng khác nhau truy cập vào cùng một vị trí bộ nhớ, ít nhất một trong các truy cập này là ghi và không có sự đồng bộ hóa bắt buộc bất kỳ thứ tự cụ thể nào giữa các truy cập này.

Điều kiện chủng tộc là một lỗi ngữ nghĩa. Đó là một sai sót xảy ra trong thời gian hoặc thứ tự của các sự kiện dẫn đến hành vi chương trình sai. Nhiều tình trạng cuộc đua có thể do cuộc đua dữ liệu gây ra, nhưng điều này không cần thiết.

Hãy xem xét ví dụ đơn giản sau trong đó x là một biến được chia sẻ:

Thread 1    Thread 2

 lock(l)     lock(l)
 x=1         x=2
 unlock(l)   unlock(l)

Trong ví dụ này, các lần ghi vào x từ luồng 1 và 2 được bảo vệ bằng các khóa, do đó chúng luôn xảy ra theo một số thứ tự được thực thi bởi thứ tự mà các khóa có được trong thời gian chạy. Đó là, tính nguyên tử được viết 'không thể bị phá vỡ; luôn luôn có một điều gì đó xảy ra trước khi mối quan hệ giữa hai viết trong bất kỳ quá trình thực thi nào. Chúng ta chỉ không thể biết viết nào xảy ra trước tiên nghiệm khác.

Không có thứ tự cố định giữa các lần ghi, vì khóa không thể cung cấp điều này. Nếu tính đúng đắn của chương trình bị xâm phạm, giả sử khi ghi vào x bởi luồng 2 được theo sau bởi ghi vào x trong luồng 1, chúng ta nói rằng có một điều kiện chạy đua, mặc dù về mặt kỹ thuật không có chủng tộc dữ liệu.

Nó hữu ích hơn nhiều để phát hiện điều kiện cuộc đua so với cuộc đua dữ liệu; tuy nhiên điều này cũng rất khó đạt được.

Việc xây dựng ví dụ ngược lại cũng rất tầm thường. Bài đăng trên blog này cũng giải thích sự khác biệt rất tốt, với một ví dụ giao dịch ngân hàng đơn giản.


"cuộc đua dữ liệu (...) không đồng bộ hóa bắt buộc bất kỳ thứ tự cụ thể nào giữa các truy cập này." Tôi la một chut Nhâm lân. Trong ví dụ của bạn, các hoạt động có thể xảy ra trong cả hai thứ tự (= 1 rồi = 2 hoặc ngược lại). Tại sao nó không phải là một cuộc đua dữ liệu?
josinalvo

5
@josinalvo: nó là một tạo tác của định nghĩa kỹ thuật về cuộc đua dữ liệu. Điểm mấu chốt là giữa hai lần truy cập, sẽ có một lần phát hành khóa và một khóa có được (cho một trong hai lệnh có thể). Theo định nghĩa, một bản phát hành khóa và một khóa thu được thiết lập một trật tự giữa hai truy cập và do đó không có cuộc đua dữ liệu.
Baris Kasikci

Đồng bộ hóa không bao giờ bắt buộc bất kỳ thứ tự cụ thể nào giữa các hoạt động, vì vậy đây không phải là một cách rất may mắn để thể hiện nó. Mặt khác, JMM chỉ định rằng đối với mỗi thao tác đọc phải có một op ghi xác định mà nó quan sát, ngay cả trong một cuộc đua dữ liệu. Thật khó để tránh đề cập đến thứ tự xảy ra trước và đồng bộ một cách rõ ràng , tuy nhiên ngay cả định nghĩa JLS cũng sai khi đề cập chỉ xảy ra trước đó : theo định nghĩa của nó, hai lần ghi biến động đồng thời tạo thành một cuộc đua dữ liệu.
Marko Topolnik

@BarisKasikci "thiết lập một đơn hàng" không có bất kỳ ý nghĩa thực sự nào, theo như tôi nghĩ. Chúng chỉ là lời nói của chồn. Tôi thành thật không tin "cuộc đua dữ liệu" là một khái niệm hữu ích từ xa, vì theo nghĩa đen, mọi vị trí bộ nhớ được truy cập bởi nhiều luồng có thể được coi là trong một "cuộc chạy đua dữ liệu"
Noldorin

Các cặp phát hành-mua luôn thiết lập một trật tự. Một lời giải thích chung thì dài dòng, nhưng một ví dụ nhỏ là cặp tín hiệu chờ. @Noldorin "Thiết lập một trật tự" đề cập đến một trật tự xảy ra trước đó, là một khái niệm chính của lý thuyết đồng thời (xem bài báo của Lamport về mối quan hệ xảy ra trước đó) và các hệ thống phân tán. Các chủng tộc dữ liệu là một khái niệm hữu ích, trong đó sự hiện diện của chúng đặt ra nhiều vấn đề (ví dụ: ngữ nghĩa không xác định theo mô hình bộ nhớ C ++, ngữ nghĩa rất phức tạp trong Java, v.v.). Việc phát hiện và loại bỏ chúng tạo thành một tài liệu rộng lớn trong nghiên cứu và thực hành.
Baris Kasikci

20

Theo Wikipedia, thuật ngữ "điều kiện chủng tộc" đã được sử dụng kể từ ngày của những cổng logic điện tử đầu tiên. Trong ngữ cảnh của Java, một điều kiện chủng tộc có thể liên quan đến bất kỳ tài nguyên nào, chẳng hạn như tệp, kết nối mạng, luồng từ nhóm luồng, v.v.

Thuật ngữ "cuộc đua dữ liệu" được dành riêng cho ý nghĩa cụ thể của nó được định nghĩa bởi JLS .

Trường hợp thú vị nhất là một điều kiện chủng tộc rất giống với chủng tộc dữ liệu, nhưng vẫn không phải là một, như trong ví dụ đơn giản này:

class Race {
  static volatile int i;
  static int uniqueInt() { return i++; }
}

ilà không ổn định, không có cuộc chạy đua dữ liệu; tuy nhiên, từ quan điểm độ đúng của chương trình có một điều kiện chạy đua do tính phi nguyên tử của hai hoạt động: đọc i, ghi i+1. Nhiều chủ đề có thể nhận cùng một giá trị từ uniqueInt.


1
bạn có thể thả một dòng trong câu trả lời của mình mô tả ý data racenghĩa thực sự trong JLS không?
Geek

@geek Từ "JLS" là một siêu liên kết đến phần liên quan của JLS.
Marko Topolnik

@MarkoTopolnik Tôi hơi bối rối trước ví dụ này. Bạn có thể vui lòng giải thích: "Vì tôi hay thay đổi, không có cuộc đua dữ liệu"? Voltility chỉ đảm bảo rằng nó có thể nhìn thấy nhưng vẫn còn: 1) nó không được đồng bộ hóa và nhiều luồng có thể đọc / ghi cùng một lúc và 2) Nó được chia sẻ trường không phải cuối cùng Vì vậy, theo Java Concurrency in Practice (trích dẫn bên dưới) , đó là cuộc đua dữ liệu chứ không phải cuộc đua điều kiện, phải không?
aniliitb10

@ aniliitb10 Thay vì dựa vào các trích dẫn đã qua sử dụng bị xé ra khỏi ngữ cảnh của chúng, bạn nên xem lại phần JLS 17.4 mà tôi đã liên kết trong câu trả lời của mình. Quyền truy cập vào một biến dễ thay đổi là hành động đồng bộ hóa như được định nghĩa trong §17.4.2.
Marko Topolnik

@ aniliitb10 Votaltiles không gây ra cuộc đua dữ liệu, vì quyền truy cập của chúng có thể được đặt hàng. Đó là, bạn có thể suy luận thứ tự của họ theo cách này hoặc cách kia, dẫn đến kết quả khác nhau. Với cuộc chạy đua dữ liệu, bạn không có cách nào để lý giải thứ tự. Ví dụ: hoạt động i ++ của mỗi luồng có thể chỉ xảy ra dựa trên giá trị i được lưu trữ cục bộ tương ứng của chúng. Trên toàn cầu, bạn không có cách nào để sắp xếp các thao tác đó (theo quan điểm của lập trình viên) - trừ khi bạn có sẵn một mô hình bộ nhớ ngôn ngữ nhất định.
Xiao-Feng Li

3

Không, chúng khác nhau và cả hai đều không phải là tập hợp con của một hoặc ngược lại.

Thuật ngữ điều kiện chủng tộc thường bị nhầm lẫn với thuật ngữ chủng tộc dữ liệu có liên quan, phát sinh khi đồng bộ hóa không được sử dụng để điều phối tất cả quyền truy cập vào trường không chính thức được chia sẻ. Bạn có nguy cơ chạy đua dữ liệu bất cứ khi nào một luồng ghi một biến mà có thể tiếp theo sẽ được đọc bởi một luồng khác hoặc đọc một biến có thể đã được ghi lần cuối bởi một luồng khác nếu cả hai luồng không sử dụng đồng bộ hóa; mã với các chủng tộc dữ liệu không có ngữ nghĩa xác định hữu ích theo Mô hình Bộ nhớ Java. Không phải tất cả các điều kiện cuộc đua đều là cuộc đua dữ liệu và không phải tất cả cuộc đua dữ liệu đều là điều kiện cuộc đua, nhưng cả hai đều có thể khiến các chương trình đồng thời bị lỗi theo những cách không thể đoán trước.

Trích từ cuốn sách xuất sắc - Java Concurrency in Practice của Joshua Bloch & Co.


Lưu ý rằng câu hỏi có thẻ ngôn ngữ bất khả tri.
martinkunev

1

TL; DR: Sự phân biệt giữa chủng tộc dữ liệu và điều kiện chủng tộc phụ thuộc vào bản chất của việc xây dựng vấn đề và nơi vẽ ranh giới giữa hành vi không xác định và hành vi được xác định rõ nhưng không xác định. Sự khác biệt hiện tại là thông thường và phản ánh tốt nhất giao diện giữa kiến ​​trúc bộ xử lý và ngôn ngữ lập trình.

1. Ngữ nghĩa

Cuộc đua dữ liệu đề cập cụ thể đến các "truy cập bộ nhớ" (hoặc các hành động hoặc hoạt động) xung đột không được đồng bộ hóa vào cùng một vị trí bộ nhớ. Nếu không có xung đột trong truy cập bộ nhớ, trong khi vẫn có hành vi không xác định được gây ra bởi thứ tự hoạt động, thì đó là điều kiện chủng tộc.

Lưu ý "truy cập bộ nhớ" ở đây có nghĩa cụ thể. Chúng đề cập đến hành động tải hoặc lưu trữ bộ nhớ "thuần túy" mà không áp dụng bất kỳ ngữ nghĩa bổ sung nào. Ví dụ, một kho lưu trữ bộ nhớ từ một luồng không (nhất thiết) biết mất bao lâu để dữ liệu được ghi vào bộ nhớ và cuối cùng được truyền sang một luồng khác. Ví dụ khác, một bộ nhớ lưu trữ đến một vị trí trước khi lưu trữ đến một vị trí khác theo cùng một chuỗi không (nhất thiết) đảm bảo dữ liệu đầu tiên được ghi trong bộ nhớ trước dữ liệu thứ hai. Do đó, thứ tự của các truy cập bộ nhớ thuần túy đó không (nhất thiết) có thể được "lý luận" , và bất cứ điều gì có thể xảy ra, trừ khi được xác định rõ ràng.

Khi các "truy cập bộ nhớ" được xác định rõ ràng về thứ tự thông qua đồng bộ hóa, ngữ nghĩa bổ sung có thể đảm bảo rằng, ngay cả khi thời gian của các truy cập bộ nhớ là không xác định, thứ tự của chúng có thể được "lý giải" thông qua đồng bộ hóa. Lưu ý, mặc dù thứ tự giữa các truy cập bộ nhớ có thể được giải thích, chúng không nhất thiết phải xác định, do đó điều kiện chủng tộc.

2. Tại sao sự khác biệt?

Nhưng nếu thứ tự vẫn không xác định trong điều kiện chủng tộc, tại sao phải phân biệt nó với chủng tộc dữ liệu? Lý do là trong thực tế hơn là lý thuyết. Đó là bởi vì sự khác biệt tồn tại trong giao diện giữa ngôn ngữ lập trình và kiến ​​trúc bộ xử lý.

Lệnh tải / lưu trữ bộ nhớ trong kiến ​​trúc hiện đại thường được thực hiện dưới dạng truy cập bộ nhớ "thuần túy", do tính chất của đường ống không theo thứ tự, đầu cơ, đa cấp bộ nhớ cache, kết nối cpu-ram, đặc biệt là đa lõi, v.v. Có rất nhiều yếu tố dẫn đến thời gian và thứ tự không xác định. Để thực thi thứ tự cho mọi lệnh bộ nhớ sẽ bị phạt rất lớn, đặc biệt là trong thiết kế bộ xử lý hỗ trợ đa lõi. Vì vậy, ngữ nghĩa thứ tự được cung cấp với các hướng dẫn bổ sung như các hàng rào (hoặc hàng rào) khác nhau.

Chạy đua dữ liệu là tình huống thực thi lệnh của bộ xử lý mà không có hàng rào bổ sung để giúp suy luận thứ tự của các truy cập bộ nhớ xung đột. Kết quả không chỉ là không xác định, mà còn có thể rất kỳ lạ, ví dụ: hai lần ghi vào cùng một vị trí từ bởi các chuỗi khác nhau có thể dẫn đến mỗi lần viết một nửa từ hoặc có thể chỉ hoạt động dựa trên các giá trị được lưu trong bộ nhớ cache cục bộ của chúng. - Đây là những hành vi không xác định, theo quan điểm của lập trình viên. Nhưng chúng (thường) được xác định rõ ràng theo quan điểm của kiến ​​trúc sư bộ xử lý.

Các lập trình viên phải có một cách để lý giải việc thực thi mã của họ. Cuộc đua dữ liệu là thứ mà chúng không thể có ý nghĩa, do đó luôn phải tránh (bình thường). Đó là lý do tại sao các đặc tả ngôn ngữ đủ mức thấp thường xác định chủng tộc dữ liệu là hành vi không xác định, khác với hành vi bộ nhớ được xác định rõ ràng về điều kiện chủng tộc.

3. Các mô hình bộ nhớ ngôn ngữ

Các bộ xử lý khác nhau có thể có hành vi truy cập bộ nhớ khác nhau, tức là, mô hình bộ nhớ bộ xử lý. Thật khó cho các lập trình viên khi nghiên cứu mô hình bộ nhớ của mọi bộ xử lý hiện đại và sau đó phát triển các chương trình có thể thu được lợi ích từ chúng. Nó là mong muốn nếu ngôn ngữ có thể xác định một mô hình bộ nhớ để các chương trình của ngôn ngữ đó luôn hoạt động như mong đợi như mô hình bộ nhớ xác định. Đó là lý do tại sao Java và C ++ có các mô hình bộ nhớ của chúng được định nghĩa. Đó là gánh nặng của các nhà phát triển trình biên dịch / thời gian chạy để đảm bảo các mô hình bộ nhớ ngôn ngữ được thực thi trên các kiến ​​trúc bộ xử lý khác nhau.

Điều đó nói rằng, nếu một ngôn ngữ không muốn bộc lộ hành vi cấp thấp của bộ xử lý (và sẵn sàng hy sinh một số lợi ích hiệu suất nhất định của các kiến ​​trúc hiện đại), họ có thể chọn xác định một mô hình bộ nhớ hoàn toàn ẩn các chi tiết của "thuần túy" truy cập bộ nhớ, nhưng áp dụng ngữ nghĩa sắp xếp cho tất cả các hoạt động bộ nhớ của chúng. Sau đó, các nhà phát triển trình biên dịch / thời gian chạy có thể chọn coi mọi biến bộ nhớ là dễ bay hơi trong tất cả các kiến ​​trúc bộ xử lý. Đối với những ngôn ngữ này (hỗ trợ bộ nhớ chia sẻ trên các luồng), không có chủng tộc dữ liệu, nhưng vẫn có thể là điều kiện chủng tộc, ngay cả với ngôn ngữ có tính nhất quán tuần tự hoàn toàn.

Mặt khác, mô hình bộ nhớ của bộ xử lý có thể chặt chẽ hơn (hoặc ít thoải mái hơn, hoặc ở mức cao hơn), ví dụ, thực hiện nhất quán tuần tự như bộ xử lý ngày đầu đã làm. Sau đó, tất cả các hoạt động của bộ nhớ được sắp xếp theo thứ tự và không có cuộc đua dữ liệu nào tồn tại cho bất kỳ ngôn ngữ nào đang chạy trong bộ xử lý.

4. Kết luận

Quay lại câu hỏi ban đầu, IMHO có thể định nghĩa cuộc đua dữ liệu là một trường hợp đặc biệt của điều kiện chủng tộc, và điều kiện chủng tộc ở một cấp có thể trở thành chủng tộc dữ liệu ở cấp cao hơn. Nó phụ thuộc vào bản chất của việc xây dựng vấn đề và nơi vẽ ranh giới giữa hành vi không xác định và hành vi được xác định rõ nhưng không xác định. Chỉ quy ước hiện tại xác định ranh giới tại giao diện ngôn ngữ-bộ xử lý, không nhất thiết có nghĩa là luôn và phải như vậy; nhưng quy ước hiện tại có lẽ phản ánh tốt nhất giao diện hiện đại (và sự khôn ngoan) giữa kiến ​​trúc sư bộ xử lý và ngôn ngữ lập trình.

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.