Chính xác thì std :: nguyên tử là gì?

186
Noname 2015-08-13 16:00.

Tôi hiểu đó std::atomic<>là một vật thể nguyên tử. Nhưng nguyên tử ở mức độ nào? Theo hiểu biết của tôi, một hoạt động có thể là nguyên tử. Chính xác thì ý nghĩa của việc chế tạo một vật thể là nguyên tử? Ví dụ: nếu có hai luồng thực thi đồng thời đoạn mã sau:

a = a + 12;

Sau đó, toàn bộ hoạt động (nói add_twelve_to(int)) là nguyên tử? Hay những thay đổi được thực hiện đối với nguyên tử biến (như vậy operator=())?

3 answers

206
Mateusz Grzejek 2015-08-13 16:55.

Mỗi bản khởi tạo và chuyên môn hóa đầy đủ của std :: atom <> đại diện cho một kiểu mà các luồng khác nhau có thể đồng thời hoạt động trên (các phiên bản của chúng), mà không làm tăng hành vi không xác định:

Các đối tượng kiểu nguyên tử là đối tượng C ++ duy nhất không bị chạy đua dữ liệu; nghĩa là, nếu một luồng ghi vào một đối tượng nguyên tử trong khi một luồng khác đọc từ nó, thì hành vi được xác định rõ ràng.

Ngoài ra, các truy cập đến các đối tượng nguyên tử có thể thiết lập đồng bộ hóa liên luồng và sắp xếp các truy cập bộ nhớ phi nguyên tử như được chỉ định bởi std::memory_order.

std::atomic<>kết thúc các hoạt động, trong 11 lần trước C ++, phải được thực hiện bằng cách sử dụng (ví dụ) các hàm được lồng vào nhau với MSVC hoặc các bultins trong trường hợp GCC.

Ngoài ra, std::atomic<>cung cấp cho bạn nhiều quyền kiểm soát hơn bằng cách cho phép các thứ tự bộ nhớ khác nhau chỉ định các ràng buộc đồng bộ hóa và sắp xếp. Nếu bạn muốn đọc thêm về nguyên tử C ++ 11 và mô hình bộ nhớ, các liên kết sau có thể hữu ích:

Lưu ý rằng, đối với các trường hợp sử dụng điển hình, bạn có thể sẽ sử dụng các toán tử số học được nạp chồng hoặc một tập hợp khác của chúng :

std::atomic<long> value(0);
value++; //This is an atomic op
value += 5; //And so is this

Bởi vì cú pháp toán tử không cho phép bạn chỉ định thứ tự bộ nhớ, các phép toán này sẽ được thực hiện với std::memory_order_seq_cst, vì đây là thứ tự mặc định cho tất cả các phép toán nguyên tử trong C ++ 11. Nó đảm bảo tính nhất quán tuần tự (tổng thứ tự toàn cục) giữa tất cả các phép toán nguyên tử.

Tuy nhiên, trong một số trường hợp, điều này có thể không bắt buộc (và không có gì miễn phí), vì vậy bạn có thể muốn sử dụng biểu mẫu rõ ràng hơn:

std::atomic<long> value {0};
value.fetch_add(1, std::memory_order_relaxed); // Atomic, but there are no synchronization or ordering constraints
value.fetch_add(5, std::memory_order_release); // Atomic, performs 'release' operation

Bây giờ, ví dụ của bạn:

a = a + 12;

sẽ không đánh giá một op nguyên tử đơn lẻ: nó sẽ dẫn đến a.load()(chính là nguyên tử), sau đó cộng giữa giá trị này và 12a.store()(cũng là nguyên tử) của kết quả cuối cùng. Như tôi đã lưu ý trước đó, std::memory_order_seq_cstsẽ được sử dụng ở đây.

Tuy nhiên, nếu bạn viết a += 12, nó sẽ là một hoạt động nguyên tử (như tôi đã lưu ý trước đây) và gần tương đương với a.fetch_add(12, std::memory_order_seq_cst).

Đối với bình luận của bạn:

Một thường xuyên intcó tải nguyên tử và lưu trữ. Gói nó bằng atomic<>gì?

Tuyên bố của bạn chỉ đúng với các kiến ​​trúc cung cấp sự đảm bảo về tính nguyên tử cho các cửa hàng và / hoặc tải. Có những kiến ​​trúc không làm được điều này. Ngoài ra, thường yêu cầu các hoạt động phải được thực hiện trên địa chỉ được căn chỉnh theo từ / từ khóa để trở thành nguyên tử std::atomic<>là thứ được đảm bảo là nguyên tử trên mọi nền tảng, không có yêu cầu bổ sung. Hơn nữa, nó cho phép bạn viết mã như thế này:

void* sharedData = nullptr;
std::atomic<int> ready_flag = 0;

// Thread 1
void produce()
{
    sharedData = generateData();
    ready_flag.store(1, std::memory_order_release);
}

// Thread 2
void consume()
{
    while (ready_flag.load(std::memory_order_acquire) == 0)
    {
        std::this_thread::yield();
    }

    assert(sharedData != nullptr); // will never trigger
    processData(sharedData);
}

Lưu ý rằng điều kiện xác nhận sẽ luôn đúng (và do đó, sẽ không bao giờ kích hoạt), vì vậy bạn luôn có thể chắc chắn rằng dữ liệu đã sẵn sàng sau khi whilethoát khỏi vòng lặp. Đó là bởi vì:

  • store()cờ được thực hiện sau khi sharedDatađược thiết lập (chúng tôi giả định rằng generateData()luôn trả về thứ gì đó hữu ích, cụ thể là không bao giờ trả về NULL) và sử dụng std::memory_order_releasethứ tự:

memory_order_release

Hoạt động lưu trữ với thứ tự bộ nhớ này thực hiện thao tác phát hành: không thể đọc hoặc ghi trong luồng hiện tại có thể được sắp xếp lại thứ tự sau khi lưu trữ này. Tất cả các lần ghi trong luồng hiện tại có thể nhìn thấy trong các luồng khác có cùng một biến nguyên tử

  • sharedDatađược sử dụng sau khi whilethoát khỏi vòng lặp và do đó load()cờ after from sẽ trả về giá trị khác 0. load()sử dụng std::memory_order_acquirethứ tự:

std::memory_order_acquire

Thao tác tải với thứ tự bộ nhớ này thực hiện thao tác thu nhận trên vị trí bộ nhớ bị ảnh hưởng: không thể đọc hoặc ghi trong luồng hiện tại có thể được sắp xếp lại thứ tự trước khi tải này. Tất cả các lần ghi trong các luồng khác giải phóng cùng một biến nguyên tử có thể nhìn thấy trong luồng hiện tại .

Điều này cho phép bạn kiểm soát chính xác quá trình đồng bộ hóa và cho phép bạn chỉ định rõ ràng cách mã của bạn có thể / có thể không / sẽ / sẽ không hoạt động. Điều này sẽ không thể thực hiện được nếu chỉ đảm bảo là tính nguyên tử. Đặc biệt là khi nói đến các mô hình đồng bộ rất thú vị như thứ tự phát hành-tiêu thụ .

21
Tony Delroy 2015-08-13 16:42.

Tôi hiểu điều đó std::atomic<>làm cho một vật thể trở thành nguyên tử.

Đó là một vấn đề về quan điểm ... bạn không thể áp dụng nó cho các đối tượng tùy ý và để các phép toán của chúng trở thành nguyên tử, nhưng các chuyên môn được cung cấp cho (hầu hết) các loại tích phân và con trỏ có thể được sử dụng.

a = a + 12;

std::atomic<>không (sử dụng các biểu thức mẫu để) đơn giản hóa điều này thành một phép toán nguyên tử đơn lẻ, thay vào đó operator T() const volatile noexceptthành viên thực hiện một nguyên tử load()của a, sau đó mười hai được thêm vào và operator=(T t) noexceptthực hiện a store(t).

std::atomic tồn tại vì nhiều ISA có hỗ trợ phần cứng trực tiếp cho nó

Tiêu chuẩn C ++ nói gì std::atomicđã được phân tích trong các câu trả lời khác.

Vì vậy, bây giờ chúng ta hãy xem những gì std::atomicbiên dịch để có được một loại thông tin chi tiết khác.

Điểm rút ra chính từ thử nghiệm này là các CPU hiện đại có hỗ trợ trực tiếp cho các hoạt động số nguyên nguyên tử, ví dụ tiền tố LOCK trong x86 và std::atomicvề cơ bản tồn tại như một giao diện di động cho các lệnh xâm nhập đó: Lệnh Lệnh "khóa" có nghĩa là gì trong hợp ngữ x86?Trong aarch64, LDADD sẽ được sử dụng.

Sự hỗ trợ này cho phép thay thế nhanh hơn các phương pháp tổng quát hơn như std::mutex, có thể làm cho phần đa hướng dẫn phức tạp hơn nguyên tử, với chi phí là chậm hơn so với std::atomicstd::mutexnó làm cho futexcác cuộc gọi hệ thống trong Linux, đó là cách chậm hơn so với các hướng dẫn Userland phát ra bởi std::atomic, xem thêm: Std :: mutex có tạo hàng rào không?

Chúng ta hãy xem xét chương trình đa luồng sau đây làm tăng một biến toàn cục trên nhiều luồng, với các cơ chế đồng bộ hóa khác nhau tùy thuộc vào định nghĩa bộ xử lý trước được sử dụng.

main.cpp

#include <atomic>
#include <iostream>
#include <thread>
#include <vector>

size_t niters;

#if STD_ATOMIC
std::atomic_ulong global(0);
#else
uint64_t global = 0;
#endif

void threadMain() {
    for (size_t i = 0; i < niters; ++i) {
#if LOCK
        __asm__ __volatile__ (
            "lock incq %0;"
            : "+m" (global),
              "+g" (i) // to prevent loop unrolling
            :
            :
        );
#else
        __asm__ __volatile__ (
            ""
            : "+g" (i) // to prevent he loop from being optimized to a single add
            : "g" (global)
            :
        );
        global++;
#endif
    }
}

int main(int argc, char **argv) {
    size_t nthreads;
    if (argc > 1) {
        nthreads = std::stoull(argv[1], NULL, 0);
    } else {
        nthreads = 2;
    }
    if (argc > 2) {
        niters = std::stoull(argv[2], NULL, 0);
    } else {
        niters = 10;
    }
    std::vector<std::thread> threads(nthreads);
    for (size_t i = 0; i < nthreads; ++i)
        threads[i] = std::thread(threadMain);
    for (size_t i = 0; i < nthreads; ++i)
        threads[i].join();
    uint64_t expect = nthreads * niters;
    std::cout << "expect " << expect << std::endl;
    std::cout << "global " << global << std::endl;
}

GitHub ngược dòng .

Biên dịch, chạy và tháo rời:

comon="-ggdb3 -O3 -std=c++11 -Wall -Wextra -pedantic main.cpp -pthread"
g++ -o main_fail.out                    $common
g++ -o main_std_atomic.out -DSTD_ATOMIC $common
g++ -o main_lock.out       -DLOCK       $common

./main_fail.out       4 100000
./main_std_atomic.out 4 100000
./main_lock.out       4 100000

gdb -batch -ex "disassemble threadMain" main_fail.out
gdb -batch -ex "disassemble threadMain" main_std_atomic.out
gdb -batch -ex "disassemble threadMain" main_lock.out

Rất có thể đầu ra điều kiện cuộc đua "sai" cho main_fail.out:

expect 400000
global 100000

và đầu ra "đúng" xác định của những người khác:

expect 400000
global 400000

Tháo gỡ main_fail.out:

   0x0000000000002780 <+0>:     endbr64 
   0x0000000000002784 <+4>:     mov    0x29b5(%rip),%rcx        # 0x5140 <niters>
   0x000000000000278b <+11>:    test   %rcx,%rcx
   0x000000000000278e <+14>:    je     0x27b4 <threadMain()+52>
   0x0000000000002790 <+16>:    mov    0x29a1(%rip),%rdx        # 0x5138 <global>
   0x0000000000002797 <+23>:    xor    %eax,%eax
   0x0000000000002799 <+25>:    nopl   0x0(%rax)
   0x00000000000027a0 <+32>:    add    $0x1,%rax
   0x00000000000027a4 <+36>:    add    $0x1,%rdx
   0x00000000000027a8 <+40>:    cmp    %rcx,%rax
   0x00000000000027ab <+43>:    jb     0x27a0 <threadMain()+32>
   0x00000000000027ad <+45>:    mov    %rdx,0x2984(%rip)        # 0x5138 <global>
   0x00000000000027b4 <+52>:    retq

Tháo gỡ main_std_atomic.out:

   0x0000000000002780 <+0>:     endbr64 
   0x0000000000002784 <+4>:     cmpq   $0x0,0x29b4(%rip)        # 0x5140 <niters>
   0x000000000000278c <+12>:    je     0x27a6 <threadMain()+38>
   0x000000000000278e <+14>:    xor    %eax,%eax
   0x0000000000002790 <+16>:    lock addq $0x1,0x299f(%rip)        # 0x5138 <global>
   0x0000000000002799 <+25>:    add    $0x1,%rax
   0x000000000000279d <+29>:    cmp    %rax,0x299c(%rip)        # 0x5140 <niters>
   0x00000000000027a4 <+36>:    ja     0x2790 <threadMain()+16>
   0x00000000000027a6 <+38>:    retq   

Tháo gỡ main_lock.out:

Dump of assembler code for function threadMain():
   0x0000000000002780 <+0>:     endbr64 
   0x0000000000002784 <+4>:     cmpq   $0x0,0x29b4(%rip)        # 0x5140 <niters>
   0x000000000000278c <+12>:    je     0x27a5 <threadMain()+37>
   0x000000000000278e <+14>:    xor    %eax,%eax
   0x0000000000002790 <+16>:    lock incq 0x29a0(%rip)        # 0x5138 <global>
   0x0000000000002798 <+24>:    add    $0x1,%rax
   0x000000000000279c <+28>:    cmp    %rax,0x299d(%rip)        # 0x5140 <niters>
   0x00000000000027a3 <+35>:    ja     0x2790 <threadMain()+16>
   0x00000000000027a5 <+37>:    retq

Kết luận:

  • phiên bản không nguyên tử lưu toàn cục vào một thanh ghi và tăng thanh ghi.

    Do đó, cuối cùng, rất có thể bốn lần ghi xảy ra trở lại toàn cầu với cùng một giá trị "sai" là 100000.

  • std::atomicbiên dịch sang lock addq. Tiền tố LOCK thực hiện inctìm nạp, sửa đổi và cập nhật bộ nhớ nguyên tử.

  • Tiền tố LOCK lắp ráp nội tuyến rõ ràng của chúng tôi biên dịch gần như giống với điều đó std::atomic, ngoại trừ việc của chúng tôi incđược sử dụng thay vì add. Không chắc tại sao GCC lại chọn add, vì INC của chúng tôi đã tạo ra một giải mã nhỏ hơn 1 byte.

ARMv8 có thể sử dụng LDAXR + STLXR hoặc LDADD trong các CPU mới hơn: Làm cách nào để bắt đầu các luồng bằng C đơn giản?

Đã thử nghiệm trong Ubuntu 19.10 AMD64, GCC 9.2.1, Lenovo ThinkPad P51.

Related questions

MORE COOL STUFF

Cate Blanchett chia tay chồng sau 3 ngày bên nhau và vẫn kết hôn với anh ấy 25 năm sau

Cate Blanchett chia tay chồng sau 3 ngày bên nhau và vẫn kết hôn với anh ấy 25 năm sau

Cate Blanchett đã bất chấp những lời khuyên hẹn hò điển hình khi cô gặp chồng mình.

Tại sao Michael Sheen là một diễn viên phi lợi nhuận

Tại sao Michael Sheen là một diễn viên phi lợi nhuận

Michael Sheen là một diễn viên phi lợi nhuận nhưng chính xác thì điều đó có nghĩa là gì?

Hallmark Star Colin Egglesfield Các món ăn gây xúc động mạnh đối với người hâm mộ tại RomaDrama Live! [Loại trừ]

Hallmark Star Colin Egglesfield Các món ăn gây xúc động mạnh đối với người hâm mộ tại RomaDrama Live! [Loại trừ]

Ngôi sao của Hallmark Colin Egglesfield chia sẻ về những cuộc gặp gỡ với người hâm mộ ly kỳ tại RomaDrama Live! cộng với chương trình INSPIRE của anh ấy tại đại hội.

Tại sao bạn không thể phát trực tuyến 'chương trình truyền hình phía Bắc'

Tại sao bạn không thể phát trực tuyến 'chương trình truyền hình phía Bắc'

Bạn sẽ phải phủi sạch đầu đĩa Blu-ray hoặc DVD để xem tại sao Northern Exposure trở thành một trong những chương trình nổi tiếng nhất của thập niên 90.

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

8 công dụng tuyệt vời của Baking Soda và Giấm

8 công dụng tuyệt vời của Baking Soda và Giấm

Bạn biết đấy, hai sản phẩm này là nguồn điện để làm sạch, riêng chúng. Nhưng cùng với nhau, chúng có một loạt công dụng hoàn toàn khác.

Hạn hán, biến đổi khí hậu đe dọa tương lai của thủy điện Hoa Kỳ

Hạn hán, biến đổi khí hậu đe dọa tương lai của thủy điện Hoa Kỳ

Thủy điện rất cần thiết cho lưới điện của Hoa Kỳ, nhưng nó chỉ tạo ra năng lượng khi có nước di chuyển. Bao nhiêu nhà máy thủy điện có thể gặp nguy hiểm khi các hồ và sông cạn kiệt?

Quyên góp tóc của bạn để giúp giữ nước sạch của chúng tôi

Quyên góp tóc của bạn để giúp giữ nước sạch của chúng tôi

Tóc tỉa từ các tiệm và các khoản quyên góp cá nhân có thể được tái sử dụng như những tấm thảm thấm dầu và giúp bảo vệ môi trường.

Đây là Tổng thống Trump đe dọa 'trợ cấp' xe điện của GM vì đóng cửa nhà máy

Đây là Tổng thống Trump đe dọa 'trợ cấp' xe điện của GM vì đóng cửa nhà máy

Trump và Giám đốc điều hành GM Mary Barra vào năm 2017. Kể từ khi tin tức nổ ra ngày hôm qua rằng General Motors đang cắt giảm việc làm ở Bắc Mỹ để chuẩn bị cho tương lai điện / xe tự hành / khủng khiếp có thể xảy ra của chúng tôi, gần như tất cả mọi người đã tự hỏi: Tổng thống Trump, người đã vận động tranh cử trên một nền tảng sẽ như thế nào của việc mang lại và duy trì việc làm ô tô của Mỹ, phải nói gì về điều này? Bây giờ chúng ta biết.

Bản vá lỗi tiếp theo của Fallout 76 sẽ mang lại một số bản sửa lỗi cần thiết

Bản vá lỗi tiếp theo của Fallout 76 sẽ mang lại một số bản sửa lỗi cần thiết

Bethesda cho biết ngày hôm nay trên Reddit, Fallout 76 sẽ có một bản vá lớn khác vào ngày 4 tháng 12, sẽ bắt đầu cố gắng khắc phục một số vấn đề lớn hơn của trò chơi. Ngoài ra, công ty cho biết họ có kế hoạch giao tiếp nhiều hơn trong tương lai về những gì nhóm Fallout 76 đang làm việc và khi nào các bản cập nhật mới cho trò chơi sẽ ra mắt.

Mạng lưới bệnh viện Công giáo sẽ không cho phép nhân viên phá thai tại phòng khám ngoài giờ làm việc

Mạng lưới bệnh viện Công giáo sẽ không cho phép nhân viên phá thai tại phòng khám ngoài giờ làm việc

Bạn có biết rằng một trong sáu bệnh nhân ở bệnh viện ở Hoa Kỳ được điều trị tại một cơ sở Công giáo? Và bạn có biết rằng quyền sở hữu của Công giáo hạn chế sâu sắc việc thiết lập các thủ tục y tế có thể được thực hiện trong các bệnh viện này, rõ ràng nhất là khi nói đến chăm sóc sức khỏe sinh sản? Bây giờ NPR báo cáo rằng sự gia tăng của các bệnh viện thuộc Công giáo đang gây khó khăn hơn cho các bác sĩ — những người bị cấm bởi các quy định do Hoa Kỳ viết.

Khói từ đám cháy rừng ở California đã đến tận thành phố New York

Khói từ đám cháy rừng ở California đã đến tận thành phố New York

Đường chân trời San Francisco bị che khuất bởi khói và khói mù do cháy rừng phía sau Đảo Alcatraz, Thứ Tư, ngày 14 tháng 11 năm 2018, ở San Francisco.

Nicky Hilton Forced to Borrow Paris' 'I Love Paris' Sweatshirt After 'Airline Loses All [My] Luggage'

Nicky Hilton Forced to Borrow Paris' 'I Love Paris' Sweatshirt After 'Airline Loses All [My] Luggage'

Nicky Hilton Rothschild's luggage got lost, but luckily she has an incredible closet to shop: Sister Paris Hilton's!

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa. Từ Hollywood đến New York và mọi nơi ở giữa, hãy xem các ngôi sao yêu thích của bạn đang làm gì!

17 tuổi bị đâm chết trong khi 4 người khác bị thương trong một cuộc tấn công bằng dao trên sông Wisconsin

17 tuổi bị đâm chết trong khi 4 người khác bị thương trong một cuộc tấn công bằng dao trên sông Wisconsin

Các nhà điều tra đang xem xét liệu nhóm và nghi phạm có biết nhau trước vụ tấn công hay không

Thanh thiếu niên, Gia đình Florida Hội đồng quản trị trường học về Luật 'Không nói đồng tính': 'Buộc chúng tôi tự kiểm duyệt'

Thanh thiếu niên, Gia đình Florida Hội đồng quản trị trường học về Luật 'Không nói đồng tính': 'Buộc chúng tôi tự kiểm duyệt'

Vụ kiện, nêu tên một số học khu, lập luận rằng dự luật "Không nói đồng tính" được ban hành gần đây của Florida "có hiệu quả im lặng và xóa bỏ học sinh và gia đình LGBTQ +"

Đường băng hạ cánh

Đường băng hạ cánh

Cuối hè đầu thu là mùa hoài niệm. Những chiếc đèn đường chiếu ánh sáng của chúng qua những con đường đẫm mưa, và những chiếc lá dưới chân - màu đỏ cam tắt trong bóng chạng vạng - là lời nhắc nhở về những ngày đã qua.

Hãy tưởng tượng tạo ra một chiến lược nội dung thực sự CHUYỂN ĐỔI. Nó có thể.

Hãy tưởng tượng tạo ra một chiến lược nội dung thực sự CHUYỂN ĐỔI. Nó có thể.

Vào năm 2021, tôi khuyến khích bạn suy nghĩ lại mọi thứ bạn biết về khách hàng mà bạn phục vụ và những câu chuyện bạn kể cho họ. Lùi lại.

Sự mất mát của voi ma mút đã mở ra trái tim tôi để yêu

Sự mất mát của voi ma mút đã mở ra trái tim tôi để yêu

Vào ngày sinh nhật thứ 9 của Felix The Cat, tôi nhớ về một trong những mất mát lớn nhất trong cuộc đời trưởng thành của tôi - Sophie của tôi vào năm 2013. Tôi đã viết bài luận này và chia sẻ nó trên nền tảng này một thời gian ngắn vào năm 2013.

Khi bạn không thể trở thành người mà Internet muốn bạn trở thành

Khi bạn không thể trở thành người mà Internet muốn bạn trở thành

Tôi ghét từ "tàu đắm". Mọi người cảm thấy thoải mái trong la bàn đạo đức của riêng mình, và khi làm như vậy, họ thấy mình vượt qua sự phán xét.

Language