我实现了这样的代码,以便在不同线程上运行的多个实例使用读写锁和 shared_ptr 读取其他实例的数据。看起来不错,但我对此不是 100% 确定的,我提出了一些关于这些用法的问题。
细节
我有一个名为 Chunk 的类的多个实例,每个实例在专用线程中进行一些计算。一个块需要读取相邻块的数据以及它自己的数据,但它不写入邻居的数据,所以使用读写锁。此外,可以在运行时设置邻居。例如,我可能想在运行时设置一个不同的邻居块,有时只是 nullptr。也可以在运行时删除块。可以使用原始指针,但我认为 shared_ptr 和 weak_ptr 更适合这个,以便跟踪生命周期。shared_ptr 中自己的数据和weak_ptr 中邻居的数据。
我在下面提供了我的代码的更简单版本。ChunkData 有数据和一个互斥体。我使用 InitData 进行数据初始化,之后在专用线程中调用 DoWork 函数。其他函数可以从主线程调用。这似乎有效,但我不是那么自信。特别是关于在多个线程中使用 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;
string result;
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 such as result(string) reading chunk data, so need this.
if (chunkData)
{
shared_lock readLock(chunkData->dataMutex);
if (chunkData->data)
{
// read chunkData->data[i] and modify some members such as result(string)
for (int i = 0; i < chunkData->size; ++i)
{
// Is this fine, or should I write data result outside of readLock scope?
result += to_string(chunkData->data[i]) + " ";
}
}
}
// does this work?
if (shared_ptr<ChunkData> neighbour = neighbourChunkData.lock())
{
shared_lock readLock(neighbour->dataMutex);
if (neighbour->data)
{
// read neighbour->data[i] and modify some members as above
}
}
}
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;
}
}
};
编辑 1
我为 DoWork 函数添加了更多细节。读取块数据并在函数中编辑块的成员变量。
在 Homer512 的回答之后,我提出了其他问题。
A)在 DoWork 函数中,我在读锁中写入了一个成员变量。我是否应该只读取读锁范围内的数据,如果我需要根据读取的数据修改其他数据,是否必须在读锁之外进行?例如,将整个数组复制到读锁中的局部变量,并使用本地修改读锁之外的其他成员。
B)我跟随 Homer512 并修改 GetDataAt/SetDataAt 如下。在解锁 chunkMutex 之前,我会读/写 lock chunkData->dataMutex。我也在 DoWork 函数中这样做。我应该单独做锁吗?例如,创建一个局部变量 shared_ptr 并在 chunkMutex 锁中设置 chunkData 给它,解锁它,然后最后读/写锁定该局部变量的 dataMutex 和读/写数据。
int GetDataAt(int index)
{
lock_guard chunkLock(chunkMutex);
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)
{
lock_guard chunkLock(chunkMutex);
unique_lock writeLock(chunkData->dataMutex);
if (chunkData->data && 0 <= index && index < chunkData->size)
{
chunkData->data[index] = element;
}
}