例如考虑
class ProcessList {
private
std::vector<std::shared_ptr<SomeObject>> list;
Mutex mutex;
public:
void Add(std::shared_ptr<SomeObject> o) {
Locker locker(&mutex); // Start of critical section. Locker release so will the mutex - In house stuff
list.push_back(std::make_shared<SomeObject>(o).
}
void Remove(std::shared_ptr<SomeObject> o) {
Locker locker(&mutex); // Start of critical section. Locker release so will the mutex - In house stuff
// Code to remove said object but indirectly modifying the reference count in copy below
}
void Process() {
std::vector<std::shared_ptr<SomeObject>> copy;
{
Locker locker(&mutes);
copy = std::vector<std::shared_ptr<SomeObject>>(
list.begin(), list.end()
)
}
for (auto it = copy.begin(), it != copy.end(); ++it) {
it->Procss(); // This may take time/add/remove to the list
}
}
};
一个线程运行Process
。多个线程运行添加/删除。
引用计数是否安全且始终正确 - 或者是否应将互斥锁置于其周围?
答案 0 :(得分:1)
是的,标准(至第20.2.2.2节,至少从N3997开始),其目的是要求引用计数是线程安全的。
对于像Add
这样的简单案例:
void Add(std::shared_ptr<SomeObject> o) {
Locker locker(&mutex);
list.push_back(std::make_shared<SomeObject>(o).
}
...标准中的保证足够强大,您不需要互斥锁,因此您可以拥有:
void Add(std::shared_ptr<SomeObject> o) {
list.push_back(std::make_shared<SomeObject>(o).
}
对于某些操作,虽然线程安全引用计数必然会消除您的互斥锁,但一点也不清楚。例如,在Process
内,您有:
{
Locker locker(&mutes);
copy = std::vector<std::shared_ptr<SomeObject>>(
list.begin(), list.end()
)
}
这将整个副本作为原子操作执行 - 在复制期间没有其他任何东西可以修改列表。这样可以确保您的副本能够像启动副本时一样精确地为您提供列表的快照。如果删除互斥锁,则引用计数仍然有效,但您的副本可能会反映复制过程中所做的更改。
换句话说,shared_ptr
的线程安全性只能确保每个单独的增量或减量都是原子的 - 它不能保证整个列表的操作都是原子的,就像在这种情况下互斥锁一样。
由于您的list
实际上是vector
,因此您应该能够将复制代码简化为copy = list
。
另请注意,您的Locker
似乎是std::lock_guard
提供的内容的一部分。看来你可以使用:
std::lock_guard<std::mutex> locker(&mutes);
......相当容易。
答案 1 :(得分:0)
使用互斥量进行引用计数将是一个开销。
在内部,互斥锁使用原子操作,基本上互斥锁执行内部线程安全引用计数。因此,您可以直接使用原子进行引用计数,而不是使用互斥量,实际上可以完成双重工作。
答案 2 :(得分:-2)
除非您的CPU架构具有原子递增/递减并且您将其用于引用计数,否则,它不安全; C ++不保证x ++ / x--操作在任何标准类型上的线程安全性。
如果您的编译器支持它们,请使用atomic<int>
(C ++ 11),否则您将需要锁定。
进一步参考: