这可能是一个愚蠢的问题,但请考虑以下伪代码:
struct Person {
std::string name;
};
class Registry {
public:
const std::string& name(int id) const {return _people[id].name;}
void name(int id, const std::string& name) { [[scoped mutex]]; _people[id].name = name;}
private:
std::map<int, Person> _people;
};
在这个简单的示例中,假设Registry是一个将由多个线程访问的单例。我在一个改变数据的操作期间锁定,但在非变异访问期间不锁定。
这个线程是安全的,还是我还应该在读取操作期间锁定?我阻止多个线程同时尝试修改数据,但我不知道如果一个线程试图在另一个线程写入的同时读取会发生什么。
答案 0 :(得分:7)
如果任何线程可以修改数据,那么您需要锁定所有访问权限。
否则,当您处于不确定状态时,其中一个“读取”线程可以访问数据。例如,修改map
需要操作几个指针。
如果您可以保证数据不被修改,则不需要锁定多个线程的多次读取,但这会引入一个您必须密切关注的脆弱场景。
答案 1 :(得分:4)
在修改数据时读取数据不是线程安全的。让多个线程一次读取数据是完全安全的。
这种差异是读写器锁的用途;他们将允许任意数量的读者,但是当作家试图锁定资源时,将不再允许新读者,并且作者将阻止,直到所有当前读者完成。然后作者将继续,一旦完成,所有读者将被允许再次访问。
在修改期间读取数据不安全的原因是数据可能或可能看起来处于不一致状态(例如,对象可能暂时不能满足不变性)。如果读者在那时读取它,那就像程序中的错误一样,无法保持数据的一致性。
// example
int array[10];
int size = 0;
int &top() {
return array[size-1];
}
void insert(int value) {
size++;
top() = value;
}
任意数量的线程可以同时调用top()
,但是如果一个线程正在运行insert()
,那么当这些行像这样交错时会出现问题:
// thread calling insert thread calling top
size++;
return array[size-1];
array[size-1] = value
阅读线程变得垃圾。
当然这只是一种可能出错的方法。一般来说,你甚至不能假设程序的行为就像不同线程上的代码行只是交错一样。为了使该假设有效,语言只是告诉您不能进行数据竞争(即,我们一直在谈论的内容;多个线程访问(非原子)对象,至少有一个线程修改对象) *
*为了完整;所有原子访问都使用顺序一致的内存排序。这对你没有关系,因为你没有直接用原子对象做低级别的工作。
答案 2 :(得分:3)
这个线程是安全的,还是我还应该在读取操作期间锁定?
不线程安全。
根据C ++ 11标准的第1.10 / 4段:
两个表达式评估冲突如果其中一个修改了内存位置(1.7),另一个访问或修改相同的内存位置。
此外,根据第1.10 / 21段:
程序的执行包含数据竞争,如果它在不同的线程中包含两个冲突的动作,其中至少有一个不是原子的,并且在另一个之前都不会发生。 任何此类数据竞争都会导致未定义的行为。 [...]
答案 3 :(得分:0)
这不是线程安全的。
读取操作可以通过地图(它是一棵树)来查找所请求的对象,而写入操作会突然添加或删除地图中的某些内容(或者更糟糕的是,迭代器所处的实际点)。 / p>
如果你很幸运,你会得到一个例外,否则当地图处于不一致的状态时,它将只是未定义的行为。
答案 4 :(得分:0)
我不知道如果一个帖子试图读取会发生什么 同时另一个人正在写作。
没人知道。随之而来的是混乱。
多个线程可以共享只读资源,但是只要有人想要编写它,在编写完成之前,每个人都以任何方式访问它都会变得不安全。
写入不是原子。它们发生在多个时钟周期内。尝试在写入对象时读取的进程可能会找到半修改版本的临时垃圾。
如果预期读取与您的写入同时发生,请锁定您的读取。
答案 5 :(得分:0)
绝对不安全!
如果您要将Person::Name
从“John Smith”改为“James Watt”,您可能会读回“Jame Smith”或“James mith”的值。或者甚至可能是完全不同的东西,因为“为此更改此值”的方式可能不仅仅是将新数据复制到现有位置,而是完全用新分配的内存替换它,其中包含一些完全未定义的内容[包括某些内容]不是有效的字符串]。