[英]Is this lock free design thread safe?
在不同的線程中,我執行以下操作:
共享變量:
std::shared_ptr<Data> dataPtr;
std::atomic<int> version_number;
線程1,生產者接收新數據並執行
dataPtr.reset(newdata);
version_number++;
消費者正在執行的其他線程:
int local_version=0;
std::shared_ptr<Data> localPtr;
while(local_version!=version_number)
localPtr=dataPtr;
...operation on my_ptr...
localPtr.reset();
local_version=version_number.load();
在這里,我知道使用者可能會跳過某些版本,如果他們正在處理數據並且新的更新持續進行,那對我來說很好,我不需要他們來處理所有版本,僅是他們可以使用的最后一個版本。 我的問題是,這行是原子的:
localPtr=dataPtr;
我將始終獲取dataPtr中最新版本的內容還是將其緩存或可能導致設計錯誤?
ks
正如haavee指出的那樣,多個線程可以安全地同時執行
localPtr = dataPtr;
因為僅讀取共享變量,並且在過程中更新的共享元數據塊具有特殊的線程安全保證。
但是,在
dataPtr.reset(newdata); // in producer, a WRITE to the shared_ptr
localPtr = dataPtr; // in consumer, an access to the same shared_ptr
因此此設計不是線程安全的。
根據http://en.cppreference.com/w/cpp/memory/shared_ptr :是。 my_ptr = dataPtr
是線程安全的。
多個線程可以在shared_ptr的不同實例上調用所有成員函數(包括副本構造函數和副本分配),而無需額外同步,即使這些實例是副本並共享同一對象的所有權。
雖然不能保證您認為要加載的版本將是您要加載的版本; 生產者對指針的設置以及版本號的增加不是atomic
操作,消費者也不是指針讀取以及消費者都不能更新版本號。
這段代碼對我來說似乎很虛構。 如果隨機數的消費者查看相同的數據有什么好處? (盡管這在技術上是線程安全的,但是這將在您的代碼中發生。)
如果要讓第一個使用者使用其他人沒有的方案的數據,則可能需要原子地將dataPtr交換為每個使用者的empty()shared_ptr。 然后,在交換之后,消費者檢查他必須非空的內容並進行計算。 所有其他執行相同操作的消費者在各自進行交換后將獲得empty()共享的ptr。
從代碼中刪除版本號后,您便獲得了一次鎖定的一次免費使用的生產者消費者方案。
std::shared_ptr<Data> dataPtr;
void Producer()
{
std::shared_ptr<Data> newOne = std::shared_ptr<Data>::make_shared();
std::atomic_exchange(dataPtr, newOne);
}
// called from multiple threads
void Consumer()
{
std::shared_ptr<Data> mine;
std::atomic_exchange(mine,dataPtr);
if( !mine.empty() )
{ // compute, using mine. Only one thread is lucky for any given Data instance stored by producer.
}
}
編輯:從這里找到的文檔中可以看出,shared_ptr :: swap()不是原子的。 相應地調整了代碼。 編輯2:生產者已更正。 還有一個理由不要首先使用這些東西。
對於您描述的用例,當您真的不在乎某些消費者是否時不時地錯過某些東西時,這里是一個完整的實現,它將版本號與數據打包在一起。 該模板還允許將其用於其他類型。 也許有更多的構造函數,刪除等添加...
#include "stdafx.h"
#include <cstdint>
#include <string>
#include <memory>
#include <atomic>
#include <thread>
#include <vector>
#include <iostream>
#include <chrono>
template <class _T>
class Versioned
{
_T m_data;
uint32_t m_version;
static std::atomic<uint32_t> s_next;
public:
Versioned(_T & data)
: m_data(data)
, m_version(s_next.fetch_add(1UL))
{}
~Versioned()
{
}
const _T & Data() const
{
return m_data;
}
uint32_t Version() const
{
return m_version;
}
};
template <class _T>
std::atomic<uint32_t> Versioned<_T>::s_next;
typedef Versioned<std::string> VersionedString;
static volatile bool s_running = true;
static std::shared_ptr<VersionedString> s_dataPtr;
int _tmain(int argc, _TCHAR* argv[])
{
std::vector<std::thread> consumers;
for (size_t i = 0; i < 3; ++i)
{
consumers.push_back(std::thread([]()
{
uint32_t oldVersion = ~0UL;
std::shared_ptr<VersionedString> mine;
while (s_running)
{
mine = std::atomic_load(&s_dataPtr);
if (mine)
{
if (mine->Version() != oldVersion)
{
oldVersion = mine->Version();
// No lock taken for cout -> chaotic output possible.
std::cout << mine->Data().c_str();
}
}
}
}));
}
for (size_t i = 0; i < 100; ++i)
{
std::shared_ptr<VersionedString> next = std::make_shared<VersionedString>(std::string("Hello World!"));
std::atomic_store(&s_dataPtr, next);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
s_running = false;
for (auto& t : consumers)
{
t.join();
}
return 0;
}
從概念上講,您的“無鎖”方案只是浪費時間和CPU。
如果您不擔心丟失中間版本,只需讓您的生產者將其輸出限制為消費者可以應付的頻率,並使用共享隊列或任何經過驗證的任務間通信機制來傳遞數據包。
實時系統都是關於確保響應能力的,一個好的設計試圖在此之上設置一個合理的上限,而不是為了保持冷靜而燒掉CPU。
C ++ 11和新的“非阻塞”時尚狂潮通過誘使每個人和他的狗相信幾個原子變量將解決每個同步問題而造成了極大的傷害。 事實上,他們不會。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.