![](/img/trans.png)
[英]boost::shared_mutex vs boost::mutex for multiple threads writing?
[英]About shared_mutex and shared_ptr across multiple threads
我實現了這樣的代碼,以便在不同線程上運行的多個實例使用讀寫鎖和 shared_ptr 讀取其他實例的數據。 看起來不錯,但我對此不是 100% 確定的,我提出了一些關於這些用法的問題。
我有多個名為 Chunk 的 class 實例,每個實例都在專用線程中進行一些計算。 一個塊需要讀取相鄰塊的數據以及它自己的數據,但它不寫入鄰居的數據,所以使用讀寫鎖。 此外,可以在運行時設置鄰居。 例如,我可能想在運行時設置一個不同的鄰居塊,有時只是 nullptr。 也可以在運行時刪除塊。 可以使用原始指針,但我認為 shared_ptr 和 weak_ptr 更適合這個,以便跟蹤生命周期。 shared_ptr 中自己的數據和weak_ptr 中鄰居的數據。
我在下面提供了我的代碼的更簡單版本。 ChunkData 有數據和一個互斥體。 我使用 InitData 進行數據初始化,之后在專用線程中調用 DoWork function。 其他函數可以從主線程調用。 這似乎有效,但我不是那么自信。 特別是關於在多個線程中使用 shared_ptr 。
如果一個線程調用 shared_ptr 的 reset()(在 ctor 和 InitData 中)而其他線程將它與 weak_ptr 的鎖(在 DoWork 中)一起使用,會發生什么? 這需要鎖dataMutex或chunkMutex嗎?
復制(在 SetNeighbour 中)怎么樣? 我也需要鎖嗎?
我認為其他部分還可以,但是如果您發現任何危險,請告訴我。 感謝。
順便說一句,我考慮過存儲 Chunk 的 shared_ptr 而不是 ChunkData,但決定不使用這種方法,因為我不管理的內部代碼具有 GC 系統,它可以在我不期望的時候刪除指向 Chunk 的指針它。
class Chunk
{
public:
class ChunkData
{
public:
shared_mutex dataMutex; // mutex to read/write data
int* data;
int size;
ChunkData() : data(nullptr) { }
~ChunkData()
{
if (data)
{
delete[] data;
data = nullptr;
}
}
};
private:
mutex chunkMutex; // mutex to read/write member variables
shared_ptr<ChunkData> chunkData;
weak_ptr<ChunkData> neighbourChunkData;
public:
Chunk(string _name)
: chunkData(make_shared<ChunkData>())
{
}
~Chunk()
{
EndProcess();
unique_lock lock(chunkMutex); // is this needed?
chunkData.reset();
}
void InitData(int size)
{
ChunkData* NewData = new ChunkData();
NewData->size = size;
NewData->data = new int[size];
{
unique_lock lock(chunkMutex); // is this needed?
chunkData.reset(NewData);
cout << "init chunk " << name << endl;
}
}
// This is executed in other thread. e.g. thread t(&Chunk::DoWork, this);
void DoWork()
{
lock_guard lock(chunkMutex); // we modify some members reading chunk data, so need this.
if (chunkData)
{
shared_lock readLock(chunkData->dataMutex);
if (chunkData->data)
{
// ready chunkData->data[i] and modify some members
}
}
// does this work?
if (shared_ptr<ChunkData> neighbour = neighbourChunkData.lock())
{
shared_lock readLock(neighbour->dataMutex);
if (neighbour->data)
{
// ready neighbour->data[i] and modify some members
}
}
}
shared_ptr<ChunkData> GetChunkData()
{
unique_lock lock(chunkMutex);
return chunkData;
}
void SetNeighbour(Chunk* neighbourChunk)
{
if (neighbourChunk)
{
// safe?
shared_ptr<ChunkData> newNeighbourData = neighbourChunk->GetChunkData();
unique_lock lock(chunkMutex); // lock for chunk properties
{
shared_lock readLock(newNeighbourData->dataMutex); // not sure if this is needed.
neighbourChunkData = newNeighbourData;
}
}
}
int GetDataAt(int index)
{
shared_lock readLock(chunkData->dataMutex);
if (chunkData->data && 0 <= index && index < chunkData->size)
{
return chunkData->data[index];
}
return 0;
}
void SetDataAt(int index, int element)
{
unique_lock writeLock(chunkData->dataMutex);
if (chunkData->data && 0 <= index && index < chunkData->size)
{
chunkData->data[index] = element;
}
}
};
我有幾點意見:
~ChunkData
:您可以將data
成員從int*
更改為unique_ptr<int[]>
以獲得相同的結果,而無需顯式析構函數。 您的代碼是正確的,只是不太方便。
~Chunk
:我認為您不需要鎖或調用重置方法。 到析構函數運行時,根據定義,沒有人應該引用 Chunk object。 所以鎖永遠不會有爭議。 並且重置是不必要的,因為shared_ptr
析構函數會處理它。
InitData
:是的,需要鎖,因為 InitData 可以與 DoWork 競爭。 您可以通過將 InitData 移動到構造函數來避免這種情況,但我認為這種划分是有原因的。 您還可以將shared_ptr
更改為std::atomic<std::shared_ptr<ChunkData> >
以避免鎖定。
像這樣編寫 InitData 效率更高:
void InitData(int size)
{
std::shared_ptr<ChunkData> NewData = std::make_shared<ChunkData>();
NewData->size = size;
NewData->data = new int[size]; // or std::make_unique<int[]>(size)
{
std::lock_guard<std::mutex> lock(chunkMutex);
chunkData.swap(NewData);
}
// deletes old chunkData outside locked region if it was initialized before
}
make_shared
避免了為引用計數器分配額外的 memory。 這也將所有分配和釋放移出臨界區。
DoWork
:您的評論“准備好 chunkData->data[i] 並修改一些成員”。 你只拿了一個shared_lock
但說你修改了成員。 好吧,它是閱讀還是寫作? 或者您的意思是說您修改了 Chunk 而不是 ChunkData,而 Chunk 受其自己的互斥體保護?
SetNeighbour
:您需要鎖定自己的 chunkMutex 和鄰居的。 您不應該同時鎖定兩者以避免哲學家就餐的問題(盡管std::lock
解決了這個問題)。
void SetNeighbour(Chunk* neighbourChunk)
{
if(! neighbourChunk)
return;
std::shared_ptr<ChunkData> newNeighbourData;
{
std::lock_guard<std::mutex> lock(neighbourChunk->chunkMutex);
newNeighbourData = neighbourChunk->chunkData;
}
std::lock_guard<std::mutex> lock(this->chunkMutex);
this->neighbourChunkData = newNeighbourData;
}
GetDataAt
和SetDataAt
:您需要鎖定 chunkMutex。 否則你可能會與 InitData 競爭。 不需要使用std::lock
因為鎖的順序永遠不會交換。編輯1:
DoWork
: if (shared_ptr<ChunkData> neighbour = neighbourChunkData.lock())
行不會使鄰居保持活動狀態。 將變量聲明移出 if 以保留引用。我擔心的是,如果 InitData 仍在運行或等待運行,您的 DoWork 可能無法繼續。 你想如何處理這個問題? 我建議你可以等到工作完成。 像這樣的東西:
class Chunk
{
std::mutex chunkMutex;
std::shared_ptr<ChunkData> chunkData;
std::weak_ptr<ChunkData> neighbourChunkData;
std::condition_variable chunkSet;
void waitForChunk(std::unique_lock<std::mutex>& lock)
{
while(! chunkData)
chunkSet.wait(lock);
}
public:
// modified version of my code above
void InitData(int size)
{
std::shared_ptr<ChunkData> NewData = std::make_shared<ChunkData>();
NewData->size = size;
NewData->data = new int[size]; // or std::make_unique<int[]>(size)
{
std::lock_guard<std::mutex> lock(chunkMutex);
chunkData.swap(NewData);
}
chunkSet.notify_all();
}
void DoWork()
{
std::unique_lock<std::mutex> ownLock(chunkMutex);
waitForChunk(lock); // blocks until other thread finishes InitData
{
shared_lock readLock(chunkData->dataMutex);
...
}
shared_ptr<ChunkData> neighbour = neighbourChunkData.lock();
if(! neighbour)
return;
shared_lock readLock(neighbour->dataMutex);
...
}
void SetNeighbour(Chunk* neighbourChunk)
{
if(! neighbourChunk)
return;
shared_ptr<ChunkData> newNeighbourData;
{
std::unique_lock<std::mutex> lock(neighbourChunk->chunkMutex);
neighbourChunk->waitForChunk(lock); // wait until neighbor has finished InitData
newNeighbourData = neighbourChunk->chunkData;
}
std::lock_guard<std::mutex> ownLock(this->chunkMutex);
this->neighbourChunkData = std::move(newNeighbourData);
}
};
這樣做的缺點是,如果從未調用過 InitData 或者它因異常而失敗,您可能會死鎖。 有一些方法可以解決這個問題,例如使用std::shared_future
shared_future ,它知道它是有效的(在計划 InitData 時設置)以及它是否失敗(記錄關聯的promise
或packaged_task
的異常)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.