iT邦幫忙

2

C++ 隨手筆記 原子操作

原子操作

在古代希臘時期,亞里士多德就曾經發想,如果物體不斷分割下去,是否會有無法再繼續切割的情況?因此亞里士多德假想的一種粒子,它無法繼續切割,這個假想粒子稱為原子。由於原子無法分割的特性,所以它只有 "存在" 和 "虛無" 兩種狀態,這是非常簡單與優美的思想,我們希望這種簡潔的想法也能在程式語言上表現出來,希望運算只有"完成" 和 "沒完成" 兩種狀態,這種想法稱為原子操作。

原子操作的優勢

在現代的電腦中,我們對一個整數做加法,如

int a++;

這個加法不是一步完成的,所以在加法的過程中會有一個中間態,假設此時有另一個執行緒讀取它,就會有 data race 問題。但如果用一些方法使它變成原子性的,讓它只有 "完成" 和 "沒完成" ,就可以保證當另一個執行緒讀取它時有正確性。

範例

在 C++11 時引入原子操作的類別

#include <atomic> // atomic

std::atomic<float> a; // 類型為 float
std::atomic<int> b; // 類型為 int
std::atomic<std::uint64_t> c; // 類型為 std::uint64_t

沒有原子操作

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

static int cnt = 0;

int main(int argc, char **argv) {

    std::vector<std::thread> threads;
    
    threads.emplace_back([&](){
        for (int i = 0; i < 10000000; ++i) {
            cnt++;
        }
    });
    
    threads.emplace_back([&](){
        for (int i = 0; i < 10000000; ++i) {
            cnt++;
        }
    });

    for (auto &t : threads) {
        t.join();
    }

    std::cout << " count : " << cnt << std::endl;
    
    return 0;
}

有原子操作

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

static std::atomic<int> cnt{0};

int main(int argc, char **argv) {

    std::vector<std::thread> threads;
    
    threads.emplace_back([&](){
        for (int i = 0; i < 10000000; ++i) {
            cnt++;
        }
    });
    
    threads.emplace_back([&](){
        for (int i = 0; i < 10000000; ++i) {
            cnt++;
        }
    });

    for (auto &t : threads) {
        t.join();
    }

    std::cout << " count : " << cnt << std::endl;
    
    return 0;
}

實際運行後可以發現 cnt 不一樣。

後記

原子操作有兩個常用的函式

std::atomic<int> a;

a.store(100); // 儲存 100 到 a 
auto b = a.load() // 拿出 a 儲存的數字

當然還有一系列強大的函式,透過設計,可以用來取代 mutex,這有機會以後再介紹。


尚未有邦友留言

立即登入留言