Có tương đương không nguyên tử của std :: shared_ptr không? Và tại sao không có cái nào trong <memory>?


88

Đây là một câu hỏi nhỏ gồm hai phần, tất cả về tính nguyên tử của std::shared_ptr:

1. Theo như tôi có thể nói, std::shared_ptrlà con trỏ thông minh duy nhất trong <memory>đó là nguyên tử. Tôi đang tự hỏi liệu có phiên bản phi nguyên tử std::shared_ptrnào có sẵn không (tôi không thể nhìn thấy bất kỳ thứ gì trong đó <memory>, vì vậy tôi cũng sẵn sàng đón nhận các đề xuất bên ngoài tiêu chuẩn, như trong Boost). Tôi biết boost::shared_ptrcũng là nguyên tử (nếu BOOST_SP_DISABLE_THREADSkhông được xác định), nhưng có thể có một thay thế khác? Tôi đang tìm thứ gì đó có cùng ngữ nghĩa std::shared_ptrnhưng không có tính nguyên tử.

2. Tôi hiểu tại sao std::shared_ptrlà nguyên tử; thật tuyệt. Tuy nhiên, nó không phải là tốt cho mọi tình huống và C ++ đã có câu thần chú "chỉ trả tiền cho những gì bạn sử dụng". Nếu tôi không sử dụng nhiều luồng hoặc nếu tôi đang sử dụng nhiều luồng nhưng không chia sẻ quyền sở hữu con trỏ trên các luồng, thì con trỏ thông minh nguyên tử là quá mức cần thiết. Câu hỏi thứ hai của tôi là tại sao một phiên bản phi nguyên tử không std::shared_ptrđược cung cấp trong C ++ 11 ? (giả sử có lý do tại sao ) (nếu câu trả lời chỉ đơn giản là "một phiên bản phi nguyên tử đơn giản là chưa bao giờ được xem xét" hoặc "không ai từng yêu cầu một phiên bản phi nguyên tử" thì tốt!).

Với câu hỏi số 2, tôi tự hỏi liệu ai đó đã bao giờ đề xuất một phiên bản phi nguyên tử của shared_ptr(cho Boost hoặc ủy ban tiêu chuẩn) (không phải để thay thế phiên bản nguyên tử shared_ptr, nhưng để cùng tồn tại với nó) và nó đã bị bắn hạ vì Lý do cụ thể.


4
"Chi phí" chính xác mà bạn đang quan tâm ở đây là gì? Chi phí của việc tăng nguyên tử một số nguyên? Đó có thực sự là chi phí mà bạn quan tâm cho bất kỳ ứng dụng thực tế nào không? Hay bạn chỉ đang tối ưu hóa quá sớm?
Nicol Bolas

9
@NicolBolas: Đó là sự tò mò hơn bất cứ điều gì khác; Tôi (hiện tại) không có bất kỳ mã / dự án nào mà tôi thực sự muốn sử dụng con trỏ chia sẻ phi nguyên tử. Tuy nhiên, tôi đã từng có các dự án (trước đây) mà Boost của shared_ptrđã bị chậm lại đáng kể do tính nguyên tử của nó và việc xác định BOOST_DISABLE_THREADSđã tạo ra một sự khác biệt đáng chú ý (tôi không biết liệu std::shared_ptrcó phải có cùng chi phí hay không boost::shared_ptr).
Cornstalks

13
@ Đóng cử tri: phần nào của câu hỏi không mang tính xây dựng? Nếu không có lý do cụ thể cho câu hỏi thứ hai, điều đó cũng tốt (một câu đơn giản "nó chỉ đơn giản là không được coi là" sẽ là một câu trả lời đủ hợp lệ). Tôi tò mò nếu có một lý do / lý do cụ thể tồn tại. Và câu hỏi đầu tiên chắc chắn là một câu hỏi hợp lệ, tôi muốn nói. Nếu tôi cần làm rõ câu hỏi hoặc điều chỉnh nhỏ, vui lòng cho tôi biết. Nhưng tôi không thấy nó không mang tính xây dựng như thế nào.
Cornstalks

10
@Cornstalks Chà, có lẽ chỉ là mọi người không phản ứng tốt với những câu hỏi mà họ có thể dễ dàng loại bỏ là "tối ưu hóa quá sớm" , bất kể câu hỏi đó hợp lệ, được đặt ra hay có liên quan đến mức nào, tôi đoán vậy. Đối với bản thân tôi, tôi không thấy bất kỳ lý do gì để kết thúc điều này là không mang tính xây dựng.
Christian Rau

13
(không thể viết câu trả lời bây giờ nó đã đóng, vì vậy hãy bình luận) Với GCC khi chương trình của bạn không sử dụng nhiều luồng shared_ptrkhông sử dụng các hoạt động nguyên tử cho tài khoản lại. Xem (2) tại gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html để biết bản vá cho GCC cho phép sử dụng triển khai phi nguyên tử ngay cả trong các ứng dụng đa luồng, cho shared_ptrcác đối tượng không được chia sẻ giữa chủ đề. Tôi đã ngồi trên bản vá đó trong nhiều năm nhưng tôi đang xem xét cuối cùng cam kết nó cho GCC 4.9
Jonathan Wakely

Câu trả lời:


104

1. Tôi đang tự hỏi liệu có phiên bản phi nguyên tử của std :: shared_ptr không

Không được cung cấp bởi tiêu chuẩn. Cũng có thể có một cái được cung cấp bởi thư viện "bên thứ 3". Thật vậy, trước C ++ 11 và trước Boost, có vẻ như mọi người đều viết con trỏ thông minh được tính tham chiếu của riêng họ (bao gồm cả tôi).

2. Câu hỏi thứ hai của tôi là tại sao một phiên bản phi nguyên tử của std :: shared_ptr không được cung cấp trong C ++ 11?

Câu hỏi này đã được thảo luận tại cuộc họp Rapperswil năm 2010. Chủ đề này được giới thiệu bởi một National Body Comment # 20 của Thụy Sĩ. Có những lập luận mạnh mẽ ở cả hai bên của cuộc tranh luận, bao gồm cả những lập luận mà bạn cung cấp trong câu hỏi của mình. Tuy nhiên, vào cuối cuộc thảo luận, phiếu bầu đã áp đảo (nhưng không nhất trí) chống lại việc thêm một phiên bản không đồng bộ (phi nguyên tử) của shared_ptr.

Lập luận chống lại bao gồm:

  • Mã được viết bằng shared_ptr không được đồng bộ hóa có thể cuối cùng sẽ được sử dụng trong mã luồng, cuối cùng gây ra sự cố khó gỡ lỗi mà không có cảnh báo.

  • Việc có một shared_ptr "phổ quát" là "một cách" để lưu lượng truy cập trong việc đếm tham chiếu có lợi: Từ đề xuất ban đầu :

    Có cùng một loại đối tượng bất kể các tính năng được sử dụng, tạo điều kiện thuận lợi đáng kể cho khả năng tương tác giữa các thư viện, bao gồm cả thư viện của bên thứ ba.

  • Chi phí của nguyên tử, mặc dù không bằng 0, nhưng không quá cao. Chi phí được giảm thiểu bằng cách sử dụng xây dựng di chuyển và phân công di chuyển mà không cần sử dụng các hoạt động nguyên tử. Các thao tác này thường được sử dụng trong vector<shared_ptr<T>>xóa và chèn.

  • Không có gì cấm mọi người viết con trỏ thông minh được tính tham chiếu phi nguyên tử của riêng họ nếu đó thực sự là điều họ muốn làm.

Lời cuối cùng từ LWG trong Rapperswil ngày hôm đó là:

Từ chối CH 20. Không có sự đồng thuận để thực hiện một thay đổi tại thời điểm này.


7
Wow, hoàn hảo, cảm ơn vì thông tin! Đó chính xác là loại thông tin mà tôi hy vọng sẽ tìm thấy.
Cornstalks

> Has the same object type regardless of features used, greatly facilitating interoperability between libraries, including third-party libraries. đó là một suy luận cực kỳ kỳ lạ. Thư viện của bên thứ ba sẽ cung cấp các loại riêng của họ, vậy tại sao sẽ có vấn đề nếu họ cung cấp nó dưới dạng std :: shared_ptr <CustomType>, std :: non_atomic_shared_ptr <CustomType>, v.v.? bạn sẽ luôn luôn phải thích ứng với mã của bạn để những gì trở về thư viện anyways
Jean-Michaël Celerier

Điều đó đúng đối với các loại thư viện cụ thể, nhưng ý tưởng là cũng có rất nhiều nơi mà các loại tiêu chuẩn hiển thị trong các API của bên thứ ba. Ví dụ, thư viện của tôi có thể lấy một std::shared_ptr<std::string>nơi nào đó. Nếu thư viện của người khác cũng sử dụng loại đó, người gọi có thể chuyển cùng một chuỗi cho cả hai chúng tôi mà không gặp bất tiện hoặc tốn kém chi phí chuyển đổi giữa các đại diện khác nhau và đó là một chiến thắng nhỏ cho mọi người.
Jack O'Connor

52

Howard đã trả lời tốt câu hỏi và Nicol đã đưa ra một số điểm tốt về lợi ích của việc có một loại con trỏ dùng chung tiêu chuẩn, thay vì nhiều loại con trỏ không tương thích.

Mặc dù tôi hoàn toàn đồng ý với quyết định của ủy ban, nhưng tôi nghĩ rằng có một số lợi ích khi sử dụng kiểu không đồng bộ shared_ptrgiống như trong các trường hợp đặc biệt , vì vậy tôi đã điều tra chủ đề này một vài lần.

Nếu tôi không sử dụng nhiều luồng hoặc nếu tôi đang sử dụng nhiều luồng nhưng không chia sẻ quyền sở hữu con trỏ trên các luồng, thì con trỏ thông minh nguyên tử là quá mức cần thiết.

Với GCC khi chương trình của bạn không sử dụng nhiều luồng shared_ptr không sử dụng các hoạt động nguyên tử cho số tiền tái cấp lại. Điều này được thực hiện bằng cách cập nhật số lượng tham chiếu thông qua các hàm wrapper để phát hiện xem chương trình có đa luồng hay không (trên GNU / Linux, điều này được thực hiện đơn giản bằng cách phát hiện xem chương trình có liên kết đến libpthread.sohay không) và gửi đến các hoạt động nguyên tử hoặc phi nguyên tử tương ứng.

Nhiều năm trước, tôi đã nhận ra rằng vì GCC's shared_ptr<T>được triển khai dưới dạng __shared_ptr<T, _LockPolicy>lớp cơ sở , nên có thể sử dụng lớp cơ sở với chính sách khóa đơn luồng ngay cả trong mã đa luồng, bằng cách sử dụng rõ ràng __shared_ptr<T, __gnu_cxx::_S_single>. Thật không may vì đó không phải là trường hợp sử dụng dự kiến ​​nên nó không hoạt động tối ưu trước GCC 4.9 và một số hoạt động vẫn sử dụng các chức năng của trình bao bọc và do đó được gửi đến các hoạt động nguyên tử mặc dù bạn đã yêu cầu _S_singlechính sách một cách rõ ràng . Xem điểm (2) tại http://gcc.gnu.org/ml/libstdc++/2007-10/msg00180.htmlđể biết thêm chi tiết và bản vá cho GCC để cho phép triển khai phi nguyên tử được sử dụng ngay cả trong các ứng dụng đa luồng. Tôi đã ngồi trên bản vá đó trong nhiều năm nhưng cuối cùng tôi đã cam kết nó cho GCC 4.9, cho phép bạn sử dụng mẫu bí danh như thế này để xác định loại con trỏ dùng chung không an toàn cho chuỗi, nhưng nhanh hơn một chút:

template<typename T>
  using shared_ptr_unsynchronized = std::__shared_ptr<T, __gnu_cxx::_S_single>;

Loại này sẽ không thể tương tác với nhau std::shared_ptr<T>và sẽ chỉ an toàn khi sử dụng khi được đảm bảo rằng các shared_ptr_unsynchronizedđối tượng sẽ không bao giờ được chia sẻ giữa các luồng mà không có đồng bộ hóa bổ sung do người dùng cung cấp.

Điều này tất nhiên là hoàn toàn không di động, nhưng đôi khi điều đó vẫn ổn. Với các bản hack tiền xử lý phù hợp, mã của bạn sẽ vẫn hoạt động tốt với các triển khai khác nếu shared_ptr_unsynchronized<T>là bí danh cho shared_ptr<T>, nó sẽ nhanh hơn một chút với GCC.


Nếu bạn đang sử dụng GCC trước 4.9, bạn có thể sử dụng nó bằng cách thêm các _Sp_counted_base<_S_single>chuyên môn rõ ràng vào mã của riêng bạn (và đảm bảo không ai từng khởi tạo __shared_ptr<T, _S_single>mà không bao gồm các chuyên môn, để tránh vi phạm ODR.) Việc thêm các loại chuyên môn như vậy về stdmặt kỹ thuật là không xác định, nhưng sẽ hoạt động trong thực tế, bởi vì trong trường hợp này không có gì khác biệt giữa việc tôi thêm các chuyên môn vào GCC hoặc bạn thêm chúng vào mã của riêng bạn.


2
Chỉ tự hỏi, có lỗi đánh máy nào trong ví dụ của bạn về bí danh mẫu không? Tức là tôi nghĩ nó nên đọc shared_ptr_unsynchronized = std :: __ shared_ptr <. Thật ngẫu nhiên, tôi đã thử nghiệm điều này hôm nay, kết hợp với std :: __ enable_shared_from_this và std :: __ thin_ptr, và nó có vẻ hoạt động tốt (gcc 4.9 và gcc 5.2). Tôi sẽ sơ lược / tháo rời nó trong thời gian ngắn để xem liệu các hoạt động nguyên tử có thực sự bị bỏ qua hay không.
Carl Cook

Chi tiết tuyệt vời! Gần đây tôi đã phải đối mặt với một vấn đề, như mô tả trong câu hỏi này , mà cuối cùng khiến tôi phải nhìn vào mã nguồn std::shared_ptr, std::__shared_ptr, __default_lock_policyvà như vậy. Câu trả lời này xác nhận những gì tôi hiểu từ mã.
Nawaz

21

Câu hỏi thứ hai của tôi là tại sao phiên bản phi nguyên tử của std :: shared_ptr không được cung cấp trong C ++ 11? (giả sử có lý do tại sao).

Người ta có thể dễ dàng hỏi tại sao không có con trỏ xâm nhập, hoặc bất kỳ biến thể nào khác của con trỏ dùng chung mà người ta có thể có.

Thiết kế shared_ptr, được truyền lại từ Boost, đã tạo ra một thuật ngữ tiêu chuẩn tối thiểu cho các con trỏ thông minh. Nói chung, bạn có thể kéo cái này xuống khỏi tường và sử dụng nó. Đó là thứ sẽ được sử dụng chung, trên nhiều ứng dụng. Bạn có thể đặt nó trong một giao diện và tỷ lệ cược là những người tốt sẽ sẵn lòng sử dụng nó.

Phân luồng chỉ sẽ trở nên phổ biến hơn trong tương lai. Thật vậy, khi thời gian trôi qua, luồng thường sẽ là một trong những phương tiện chính để đạt được hiệu suất. Việc yêu cầu con trỏ thông minh cơ bản thực hiện mức tối thiểu cần thiết để hỗ trợ phân luồng tạo điều kiện thuận lợi cho thực tế này.

Đưa một nửa tá con trỏ thông minh với những thay đổi nhỏ giữa chúng vào tiêu chuẩn, hoặc thậm chí tệ hơn là con trỏ thông minh dựa trên chính sách, sẽ là điều khủng khiếp. Mọi người sẽ chọn con trỏ mà họ thích nhất và bỏ mặc tất cả những người khác. Không ai có thể giao tiếp với bất kỳ ai khác. Nó sẽ giống như các tình huống hiện tại với chuỗi C ++, nơi mọi người đều có kiểu riêng của họ. Chỉ tệ hơn nhiều, bởi vì tương tác với các chuỗi dễ dàng hơn nhiều so với tương tác giữa các lớp con trỏ thông minh.

Boost, và bằng cách mở rộng ủy ban, đã chọn một con trỏ thông minh cụ thể để sử dụng. Nó cung cấp sự cân bằng tốt giữa các tính năng và được sử dụng rộng rãi và phổ biến trong thực tế.

std::vectorcó một số điểm không hiệu quả so với mảng trần trong một số trường hợp góc. Nó có một số hạn chế; một số mục đích sử dụng thực sự muốn có giới hạn cứng về kích thước của a vectormà không cần sử dụng bộ phân bổ ném. Tuy nhiên, ủy ban không thiết kế vectorđể trở thành mọi thứ cho tất cả mọi người. Nó được thiết kế để trở thành một mặc định tốt cho hầu hết các ứng dụng. Những người mà nó không thể hoạt động có thể chỉ cần viết một giải pháp thay thế phù hợp với nhu cầu của họ.

Cũng như bạn có thể làm cho một con trỏ thông minh nếu shared_ptrtính nguyên tử của nó là một gánh nặng. Sau đó, một lần nữa, người ta cũng có thể cân nhắc không sao chép chúng quá nhiều.


7
+1 cho "một người cũng có thể cân nhắc không sao chép chúng quá nhiều."
Ali

Nếu bạn đã từng kết nối một hồ sơ, bạn là người đặc biệt và bạn chỉ có thể điều chỉnh các đối số như trên. Nếu bạn không có yêu cầu hoạt động khó đáp ứng, bạn không nên sử dụng C ++. Lập luận như bạn làm là một cách hay để làm cho C ++ được mọi người quan tâm đến hiệu suất cao hoặc độ trễ thấp phản đối.
Hans Malherbe

Để rõ ràng, tôi nghĩ câu trích dẫn ở đầu câu trả lời của bạn nên đọc "tại sao phiên bản phi nguyên tử của std :: shared_ptr không được cung cấp trong C ++ 11?"
Charles Savoie

4

Tôi đang chuẩn bị một bài nói chuyện trên shared_ptr tại nơi làm việc. Tôi đã sử dụng một boost_ptr được sửa đổi để tránh malloc riêng biệt (như những gì make_shared có thể làm) và một tham số mẫu cho chính sách khóa như shared_ptr_unsynchronized đã đề cập ở trên. Tôi đang sử dụng chương trình từ

http://flyingfrogblog.blogspot.hk/2011/01/boosts-sharedptr-up-to-10-slower-than.html

như một bài kiểm tra, sau khi dọn dẹp các bản sao shared_ptr không cần thiết. Chương trình chỉ sử dụng luồng chính và đối số kiểm tra được hiển thị. Env thử nghiệm là một máy tính xách tay chạy linuxmint 14. Đây là thời gian tính bằng giây:

tăng thiết lập chạy thử nghiệm (1.49) std với tăng cường sửa đổi make_shared
mt-không an toàn (11) 11,9 9 / 11,5 (-pthread on) 8,4  
nguyên tử (11) 13,6 12,4 13,0  
mt-không an toàn (12) 113,5 85,8 / 108,9 (-pthread on) 81,5  
nguyên tử (12) 126,0 109,1 123,6  

Chỉ phiên bản 'std' mới sử dụng -std = cxx11 và -pthread có khả năng chuyển lock_policy trong lớp g ++ __shared_ptr.

Từ những con số này, tôi thấy tác động của hướng dẫn nguyên tử đối với việc tối ưu hóa mã. Trường hợp thử nghiệm không sử dụng bất kỳ vùng chứa C ++ nào, nhưng vector<shared_ptr<some_small_POD>>có khả năng bị ảnh hưởng nếu đối tượng không cần bảo vệ luồng. Boost ít bị ảnh hưởng hơn có lẽ vì malloc bổ sung đang hạn chế số lượng nội tuyến và mã tối ưu hóa.

Tôi vẫn chưa tìm thấy một máy có đủ lõi để kiểm tra khả năng mở rộng của các lệnh nguyên tử, nhưng chỉ sử dụng std :: shared_ptr khi cần thiết có lẽ tốt hơn.


3

Boost cung cấp một shared_ptrphi nguyên tử. Nó được gọi là local_shared_ptrvà có thể được tìm thấy trong thư viện con trỏ thông minh của boost.


+1 cho một câu trả lời ngắn gọn chắc chắn với trích dẫn tốt, nhưng loại con trỏ này trông có vẻ tốn kém - về cả bộ nhớ và thời gian chạy, do thêm một mức chuyển hướng (cục bộ-> chia sẻ-> ptr so với chia sẻ-> ptr).
Red.Wave

@ Red.Wave Bạn có thể giải thích ý bạn với tính năng chuyển hướng và nó ảnh hưởng đến hiệu suất như thế nào không? Ý bạn là dù sao thì đó cũng là shared_ptrmột quầy có quầy, mặc dù nó là hàng địa phương? Hay bạn có nghĩa là có một vấn đề khác với nó? Các tài liệu nói rằng sự khác biệt duy nhất là đây không phải là nguyên tử.
Nhà vật lý lượng tử

Mỗi ptr địa phương giữ một số lượng và tham chiếu đến ptr được chia sẻ ban đầu. Do đó, bất kỳ quyền truy cập nào đến điểm cuối cùng cần có sự hủy bỏ phân biệt từ ptr cục bộ sang ptr được chia sẻ, sau đó sẽ bị hủy bỏ phân biệt đối với người nhận điểm. Do đó, có thêm một hướng dẫn được xếp chồng lên các hướng dẫn từ ptr được chia sẻ. Và điều đó làm tăng chi phí.
Red.Wave

@ Red.Wave Bạn lấy thông tin này từ đâu? Điều này: "Vì vậy, bất kỳ quyền truy cập nào đến điểm cuối cùng cần có sự khác biệt từ ptr cục bộ sang ptr được chia sẻ" cần một số trích dẫn. Tôi không thể tìm thấy điều đó trong tài liệu tăng cường. Một lần nữa, những gì tôi thấy trong tài liệu là nó nói như vậy local_shared_ptrshared_ptrgiống hệt nhau ngoại trừ nguyên tử. Tôi thực sự muốn tìm hiểu xem điều bạn đang nói có đúng không vì tôi sử dụng local_shared_ptrtrong các ứng dụng yêu cầu hiệu suất cao.
Nhà vật lý lượng tử

3
@ Red.Wave Nếu bạn nhìn vào mã nguồn thực tế github.com/boostorg/smart_ptr/blob/…, bạn sẽ thấy không có chuyển hướng kép. Đoạn này trong tài liệu chỉ là một mô hình tinh thần.
Ilya Popov
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.