Xin lỗi vì đã trả lời trễ, nhưng tôi vừa tìm thấy câu hỏi (câu hỏi, thực sự). Tôi cũng đang nghiên cứu đồng thời và tôi sẽ cố gắng chia sẻ một số ý tưởng với bạn.
Đầu tiên, hãy bắt đầu với tính nhất quán tuần tự. Một mô hình có thuộc tính này nếu các hoạt động dường như có hiệu lực theo thứ tự chương trình. Nói cách khác, thứ tự các dòng mã được thực thi là thứ tự được chỉ định trong tệp nguồn. Điều kiện tiên quyết này là đặc thù của luồng: các hoạt động liên quan đến các luồng khác nhau không liên quan đến thứ tự chương trình. Vì vậy, thuộc tính cấp các điều sau: các hoạt động được phát hành bởi cùng một luồng được thực hiện theo cùng một thứ tự được chỉ định bởi mã nguồn luồng. Hoạt động được phát hành bởi các chủ đề khác nhau có thể xảy ra theo thứ tự bất kỳ. Điều kiện tiên quyết này có vẻ rõ ràng, nhưng không phải lúc nào cũng được (tối ưu hóa trình biên dịch có thể thay đổi thứ tự các hoạt động được ban hành, do đó làm cho thứ tự chương trình khác với nguồn).
Ví dụ của bạn là đúng, nhưng hãy để tôi cung cấp cho bạn một số người khác. Hãy xem xét chương trình này P1 (Tôi sẽ gắn thẻ từng dòng để tham khảo dễ dàng):
int x = 1;
void ThreadA()
{
A1: x = x * 2;
A3: int a = x;
}
void ThreadB()
{
B2: x = 20;
}
Có thực hiện tuần tự trong đó, ở cuối, a = 40 không? Có: B2, A1, A3.
B2 và A1 có thể được thực thi theo bất kỳ thứ tự nào (chúng thuộc các luồng khác nhau). A1 được thực thi trước A3 (thứ tự chương trình = thứ tự mã nguồn).
Bây giờ hãy xem xét chương trình này P2:
int y = 1;
void ThreadA()
{
A2: y = 40;
}
void ThreadB()
{
B1: y = y / 2;
B3: int b = y;
}
Có thực hiện tuần tự trong đó, ở cuối, b = 20? Có: A2, B1, B3.
Thành phần thì sao? Hãy soạn P1 và P2. Chúng tôi nhận được P1∘P2:
int x = 1;
int y = 1;
void ThreadA()
{
A1: x = x * 2;
A2: y = 40;
A3: int a = x;
}
void ThreadB()
{
B1: y = y / 2;
B2: x = 20;
B3: int b = y;
}
Nếu tính nhất quán tuần tự là thành phần, thì nên có một thực thi trong đó, ở cuối, a = 40 và b = 20. Đây có phải là trường hợp không? Không. Hãy đưa ra một bằng chứng chính thức oh tại sao nó không thể là trường hợp. Tôi sẽ viết "o1 → o2" để nói rằng hoạt động o1 phải được thực hiện trước khi hoạt động o2.
Trong P1∘P2, do tính nhất quán tuần tự, các phần sau phải giữ: A1 -> A2 và B1 -> B2 (thứ tự chương trình trên mỗi luồng). Để có được a = 40 còn B2 -> A1 phải giữ. Để có được b = 20 cũng phải có A2 -> B1. Bạn có thể thấy chuỗi ưu tiên? A1 -> A2 -> B1 -> B2 -> A1 -> ...
P1 và P2 là nhất quán liên tục, nhưng thành phần của chúng là P1∘P2 thì không. Tính nhất quán tuần tự không phải là thành phần. Ví dụ bạn cung cấp không đủ khó để thể hiện sự thật này.
Bây giờ, hãy xem xét tính nhất quán tĩnh. Thật khó cho tôi để giải thích tính chất này mà không sử dụng mô hình hướng đối tượng. Trong thực tế, tính nhất quán tĩnh có thể được hiểu một cách dễ dàng theo các lời gọi phương thức đang chờ xử lý trên các đối tượng. Tuy nhiên, tôi sẽ cố gắng tuân theo mô hình bắt buộc (các lệnh gọi phương thức đang chờ xử lý trở thành các hàm bắt đầu nhưng chưa hoàn thành, các đối tượng trở thành các biến liên quan đến các hàm).
Một cuộc gọi chức năng được sáng tác bởi một lời gọi và một phản hồi. Một hàm đang chờ xử lý nếu nó được gọi bởi một luồng nhưng nó chưa trả về phản hồi nào cho luồng đó. Khoảng thời gian không hoạt động cho một biến là một khoảng thời gian trong đó không có lệnh gọi hàm đang chờ xử lý (bởi bất kỳ luồng nào) hoạt động trên biến đó. Một mô hình có thuộc tính nhất quán tĩnh nếu các lệnh gọi hoạt động trên cùng một biến và được phân tách bằng một khoảng thời gian hoạt động dường như có hiệu lực theo thứ tự thời gian thực của chúng (một lệnh được chỉ định bởi mã nguồn). Từ quan điểm ngược lại, định nghĩa nói rằng các hoạt động trên cùng một biến được thực hiện bởi các hàm được gọi đồng thời bởi các luồng khác nhau (không được phân tách bằng cách ly) có thể xảy ra theo bất kỳ thứ tự nào.
Tôi đã cố gắng trong nhiều giờ để thiết kế một ví dụ có ý nghĩa chỉ sử dụng các thao tác đọc-ghi để cho bạn thấy sự khác biệt giữa tính nhất quán tĩnh và liên tục, nhưng tôi đã không thành công. Hãy để tôi sử dụng một ví dụ khác liên quan đến bộ. Tôi sẽ sử dụng một vectơ bit để theo dõi các số nguyên nào (từ 0 đến 4) trong tập hợp:
int set[5] = {0, 0, 0, 0, 0}; // 0 if i-th item is absent, 1 otherwise
void add(int number) {
L1: set[number] = 1;
L2: temp foo = set[0];
}
void remove(int number) {
set[number] = 0;
}
int contains(int number) {
return set[number] == 1;
}
void ThreadA()
{
A1: add(1);
}
void ThreadB()
{
B1: add(2);
B2: remove(2);
B3: int res = contains(2);
}
Có thực hiện tuần tự trong đó, ở cuối, res = 1 không? Không: do tính nhất quán tuần tự B1 -> B2 và B2 -> B3, vì vậy B1 -> B3. Vì vậy, remove (2) luôn được thực hiện sau add (2) và chứa (2) sẽ luôn trả về 0.
Có một thực thi tĩnh trong đó, ở cuối, res = 1 không? Đúng! Hãy xem xét việc thực hiện này:
Chủ đề A gọi thêm (1) tại A1.
Chủ đề A thực thi L1 (nhưng không phải L2). // vì có một cuộc gọi đang chờ xử lý trên tập hợp, nên bây giờ Thread B có thể thực hiện B2 trước B1 vì các cuộc gọi này cũng liên quan đến tập hợp
Chủ đề B gọi B2. // gọi + phản hồi
Chủ đề B gọi B1. // gọi + phản hồi
Chủ đề A thực thi L2 và thêm (1) trả lời. // bây giờ có sự im lặng trên tập hợp
Chủ đề B thực thi B3. // gọi + phản hồi
Bây giờ res = 1.
Thật không may, tôi vẫn không thể trả lời câu hỏi mới nhất về lý do tại sao tính nhất quán tĩnh lại là thành phần: Tôi đã tìm thấy câu hỏi của bạn trong khi tôi đang tìm kiếm câu trả lời chính xác này thực sự. Nếu tôi nghĩ ra thứ gì đó tôi sẽ cho bạn biết.
Để đọc tốt về chủ đề này, hãy xem chương 3.3 của cuốn sách "Nghệ thuật lập trình đa bộ xử lý" của Herlihy và Shavit.
EDIT: Tôi vừa tìm thấy một trang tuyệt vời giải thích lý do tại sao tính nhất quán không hoạt động là thành phần. Và đây là một cách đọc rất tốt!
EDIT2: Đã sửa lỗi nhỏ trong ví dụ về tính tổng hợp nhất quán liên tiếp.