C++ 多線程編程時的數據一直性,以及volatile、atomic、mutex的使用選擇


volatile

#include <iostream>
#include <thread>

volatile int total{ 0 };

void func(int) {
    for (int i = 0; i < 10000; ++i) {
        total += i;
    }
}

int main() {
    std::thread t1(func, 0);
    std::thread t2(func, 0);

    t1.join();
    t2.join();

    std::cout << "total: " << total << std::endl;
    return 0;
}

聲明某個變量的值是隨時可能被改變的,每次讀取次變量都從內存地址中直接讀取。
為了防止編譯器的優化而從寄存器中讀取數據,而導致多線程時數據不一致。
但是volatile僅僅是針對編譯器的,對CPU無影響,因此再多核環境下沒有任何作用。

  1. 與平台無關的多線程程序,volatile幾乎無用(JAVA的volatile除外,java的volatile有內存屏障指令)
  2. volatile不保證原子性(一般需使用CPU提供的LOCK指令)
  3. volatile不保證執行順序
  4. volatile不提供內存屏障和內存柵欄
  5. 多核環境中內存的可見性和CPU執行順序不能通過volatile來保障,而是以來於CPU的內存屏障

atomic

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

std::atomic_int total{ 0 };
// 或 std::atomic<int> total{ 0 };

void func(int) {
    for (int i = 0; i < 10000; ++i) {
        total += i;
    }
}

int main() {
    std::thread t1(func, 0);
    std::thread t2(func, 0);

    t1.join();
    t2.join();

    std::cout << "total: " << total << std::endl;
    return 0;
}

C++11中引入了atomic,使得程序相對mutex更加簡潔
編譯器或處理器可能會改變代碼的執行順序。
默認情況下C++11中的原子類型的變量在線程中總是保持着順序執行的特性(memory_order默認參數memory_order_seq_cst 全部存取都按照順序執行,非原子類型沒有必要,因為不需要在線程間同步)。我們稱這樣的特性為“順序一致”。
x86、SPARC(TSO模式)是強順序內存模型平台。
PowerPC、ArmV7 是弱內存模型構架,如果要保證指令執行的順序,通常需要有再匯編指令中加入一條所謂的內存柵欄(memory barrier)指令。如在PowerPC上,就有一條名為sync的內存柵欄指令,其對高度流水化的PowerPC處理器的性能影響很大。
如果在強順序內存模型平台(如x86)上,沒必要指定memory_order參數
如果在弱順序內存模型平台(如PowerPC)上,可以手動指定memory_order參數來提高性能

mutex

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

std::atomic<int> total{ 0 };

void func(int) {
    for (int i = 0; i < 10000; ++i) {
        int temp = total;
        temp += i;
        total = temp;
    }
}

int main() {
    std::thread t1(func, 0);
    std::thread t2(func, 0);

    t1.join();
    t2.join();

    std::cout << "total: " << total << std::endl;
    return 0;
}

再上述的代碼中往往達不到我們想要的效果,因為atomic只能保證 int temp = total; 和 total = temp; 的原子性,但是如果再中間做了一些其他事,是不能保證原子性的,這個時候應該采用mutex

#include <iostream>
#include <thread>
#include <mutex>

int total{ 0 };
std::mutex total_mutex;

void func(int) {
    for (int i = 0; i < 10000; ++i) {
        std::lock_guard<std::mutex> lock(total_mutex);
        int temp = total;
        temp += i;
        total = temp;
    }
}

int main() {
    std::thread t1(func, 0);
    std::thread t2(func, 0);

    t1.join();
    t2.join();

    std::cout << "total: " << total << std::endl;
    return 0;
}

參考文章:《深入理解C++11》
https://www.cnblogs.com/tekkaman/p/10245341.html
https://blog.csdn.net/D_Guco/article/details/74826041?utm_source=blogxgwz5


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM