2013-05-20 35 views
17

Cho phép nói rằng tôi có một con trỏ đến một số nguyên.Con trỏ dereferencing có phải là nguyên tử không?

volatile int* commonPointer = new int(); 

Và tôi có nhiều chủ đề mà dereference con trỏ đó.

int blah = *commonPointer; 

Nhưng, một thread cần phải thay đổi địa chỉ của con trỏ rằng:

int* temp = new int(); 
int* old = commonPointer; 
InterlockedExchange(&commonPointer,temp); 
delete old; 

Bây giờ, cho phép bỏ qua một thực tế rằng một số chủ đề chưa đọc giá trị "cũ", và một số có thể được đọc " mới "giá trị, đó không phải là một vấn đề trong trường hợp của tôi.

Có thể có trường hợp trong đó một chuỗi bắt đầu bỏ qua con trỏ, giống như địa chỉ bị xóa và sau đó bị ngoại lệ?
Hoặc là cuộc hội thảo dicferencing đủ để không xảy ra?

+5

"nguyên tử * đủ *"? Việc đọc một số nguyên 32 bit không được đảm bảo là nguyên tử theo tiêu chuẩn, được xác định bởi nền tảng của bạn. Trong thực tế nó thường sẽ được, nhưng bạn luôn có thể kiểm tra rằng chính mình. –

+0

Kiến trúc hệ điều hành và CPU là gì? –

+0

@EdS. Đọc một số nguyên 32 bit là nguyên tử. Nhưng dereferencing con trỏ ... Tôi không chắc chắn như vậy. Nó nhiều hơn một lần đọc .. nó đọc địa chỉ 32-bit (hoặc 64 in x64) của con trỏ, và sau đó cần thực hiện một thao tác đọc khác từ địa chỉ đó. –

Trả lời

22

Không có gì trong tiêu chuẩn C++ đảm bảo nguyên tử trong trường hợp này.

Bạn phải bảo vệ các khu vực mã có liên quan bằng mutex: thậm chí std::atomic là không đủ vì nó chỉ cung cấp truy cập nguyên tử cho con trỏ nhưng điều đó sẽ không bao gồm thao tác dereference.

+0

std :: stomic sẽ đảm bảo giá trị của biến là nguyên tử. nhưng nó không đảm bảo dereferencing giá trị đó. –

+4

@YochaiTimmer: không phải là chính xác những gì tôi đã nói? ;) – syam

+1

Có, không biết tại sao tôi không chú ý rằng :) –

1

Chỉnh sửa2: Rất tiếc, không, nó sẽ không hữu ích. Bạn cần mutexes xung quanh truy cập - có thể (rất có thể) trình biên dịch tạo mã tải con trỏ vào thanh ghi [hoặc lưu trữ khác, chẳng hạn như ngăn xếp, nếu đó là bộ xử lý không có thanh ghi], sau đó truy cập bộ nhớ con trỏ tại, và cùng lúc, con trỏ đang được cập nhật bởi một chuỗi khác. Cách duy nhất để đảm bảo rằng con trỏ là chính xác là sử dụng mutex hoặc các cấu trúc tương tự để đóng toàn bộ khối truy cập. Bất cứ điều gì khác đều có thể thất bại.

Khi syam nói, tiêu chuẩn không đảm bảo rằng ngay cả khi đọc giá trị 32 bit mà điểm trỏ hte là nguyên tử - nó phụ thuộc vào việc triển khai hệ thống. Tuy nhiên, nếu bạn đang yêu cầu "tôi sẽ nhận được một giá trị là giá trị cũ hoặc mới", thì ít nhất x86 và x86-64 sẽ đảm bảo điều đó. Kiến trúc máy khác có thể không (thực thi 32 bit int trên một bộ xử lý SMP 68000 sẽ không đảm bảo nó, vì ghi là 16-bit tại một thời điểm, và bộ vi xử lý thứ hai có thể đã viết một nửa, nhưng không phải là khác - không phải Tôi biết về một hệ thống SMP với 68000 bộ vi xử lý từng được xây dựng).

InterlockedExchange (không phải là chức năng "chuẩn") sẽ đảm bảo rằng bộ xử lý của chuỗi này có quyền truy cập EXCLUSIVE vào chính con trỏ, vì vậy sẽ không an toàn - không có bộ xử lý nào khác có thể truy cập con trỏ tại điểm đó . Đây là toàn bộ điểm của các lệnh "khóa" trong kiến ​​trúc x86 - chúng là "an toàn" (và khá chậm, nhưng giả sử bạn không làm điều này mỗi lần ...).

Chỉnh sửa: Lưu ý rằng bạn phải cẩn thận với chính bản thân commonPointer, vì trình biên dịch có thể không nhận ra rằng bạn đang sử dụng một chuỗi khác để cập nhật nó. Vì vậy, bạn vẫn có thể đọc từ giá trị con trỏ OLD.

Cuộc gọi đến một chức năng [không bị gạch chân không] hoặc khai báo con trỏ volatile int * volatile commonPointer; sẽ thực hiện thủ thuật. [cue người downvoting câu trả lời của tôi cho việc sử dụng volatile, vì "không có vấn đề mà các giải pháp là volatile như một người nào đó được đăng trước đó].

[Xem edit2 trên]

+0

Ngay cả hai 'biến động' sẽ không đủ, ngay cả với ý nghĩa mở rộng của Microsoft là 'volatile' (không được thực hiện trong tất cả các trình biên dịch , cũng không phải với tất cả các tùy chọn trình biên dịch). 'volatile' hoàn toàn không liên quan ở đây, nói chung. –

3

Thứ nhất, volatile trong tuyên bố của bạn không có bất kỳ tác dụng thực. Và thứ hai, ngay sau khi bạn sửa đổi một giá trị trong một chuỗi và truy cập nó trong nhiều hơn một chuỗi, tất cả các quyền truy cập phải được bảo vệ. Nếu không, bạn có hành vi không xác định. Tôi không biết những gì đảm bảo InterlockedExchange cho, nhưng tôi chắc chắn rằng nó không có tác động trên bất kỳ chủ đề nào không gọi nó.

+0

InterlockedExchange đảm bảo rằng giá trị trong con trỏ được cập nhật một cách nguyên tử trên tất cả các CPU. Nhưng như tôi đã thảo luận trong câu trả lời của tôi, nó không giúp đỡ, bởi vì chúng tôi vẫn có một cuộc đua giữa giá trị con trỏ và giá trị dereferenced được đọc. Vui lòng xem Edit2 ở đầu bài đăng của tôi. –

4

Có lẽ, C++ 11

atomic<shared_ptr<int> > 

phù hợp với nhu cầu của bạn. Nó ngăn cản giá trị cũ biến mất cho đến khi ít nhất một tham chiếu đến giá trị là hợp lệ.

atomic<shared_ptr<int> > commonPointer; 

// producer: 
{ 
    shared_ptr<int> temp(new int); 
    shared_ptr<int> old= atomic_exchange(&commonPointer, temp); 
    //... 
};// destructor of "old" decrements reference counter for the old value 


// reader: 
{ 
    shared_ptr<int> current= atomic_load(&commonPointer); 

    // the referent pointed by current will not be deleted 
    // until current is alive (not destructed); 
} 

Tuy nhiên, khóa thi miễn phí của ptr chia sẻ nguyên tử rất phức tạp đủ để lẽ ổ khóa hoặc khóa quay sẽ được sử dụng trong việc thực hiện thư viện (ngay cả khi thực hiện có sẵn trên nền tảng của bạn).

+0

Giải pháp tuyệt vời bằng cách sử dụng các công cụ chuẩn. Nhưng sẽ có ít nhất 2 khóa ở đó, một cho nguyên tử và một cho shared_ptr. Tốt hơn hết là tạo một khóa duy nhất của riêng tôi. –

+0

Các con trỏ chia sẻ nguyên tử có thể được thực hiện bằng cách sử dụng các con trỏ nguy hiểm. Trong khi nó phức tạp, nó có thể được thực hiện mà không cần khóa, –

Các vấn đề liên quan