4

这可能是一个愚蠢的问题,但请考虑以下伪代码:

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 是一个可以被多个线程访问的单例。我在改变数据的操作期间锁定,但在非改变访问期间没有锁定。

这个线程是安全的,还是在读取操作期间我也应该锁定?我正在阻止多个线程同时尝试修改数据,但我不知道如果一个线程同时尝试读取另一个线程正在写入会发生什么。

4

6 回答 6

7

如果任何线程都可以修改数据,那么您需要锁定所有访问权限。

否则,您的“读取”线程之一可能会在数据处于不确定状态时访问数据。map例如,修改 a需要操作多个指针。您的阅读线程可以访问地图,而部分(但不是全部)map已调整。

如果您可以保证数据没有被修改,则不需要锁定来自多个线程的多次读取,但是这会引入一个您必须密切关注的脆弱场景。

于 2013-03-21T15:17:41.587 回答
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

阅读线程得到垃圾。

当然,这只是事情可能出错的一种可能方式。一般来说,您甚至不能假设程序的行为就像不同线程上的代码行会交错一样。为了使该假设有效,该语言仅告诉您不能进行数据竞争(即,我们一直在谈论的内容;多个线程访问(非原子)对象,并且至少有一个线程修改该对象) *。

*为了完整性;所有原子访问都使用顺序一致的内存排序。这对您来说无关紧要,因为您没有直接使用原子对象进行低级工作。

于 2013-03-21T15:18:02.880 回答
3

这个线程是安全的,还是在读取操作期间我也应该锁定?

不是线程安全的。

根据 C++11 标准的第 1.10/4 段:

如果其中一个修改了内存位置 (1.7) 而另一个访问或修改了相同的内存位置,则两个表达式求值会发生冲突。

此外,根据第 1.10/21 段:

如果程序的执行包含不同线程中的两个冲突操作,则该程序的执行包含数据竞争,其中至少一个不是原子的,并且两者都不会在另一个之前发生。任何此类数据竞争都会导致未定义的行为。[...]

于 2013-03-21T15:19:12.957 回答
0

它不是线程安全的。

读取操作可能正在遍历映射(它是一棵树)以查找请求的对象,而写入操作会突然从映射中添加或删除某些内容(或者更糟糕的是,迭代器所在的实际点)。

如果你很幸运,你会得到一个异常,否则当你的地图处于不一致状态时,它只是未定义的行为。

于 2013-03-21T15:19:33.833 回答
0

我不知道如果一个线程试图读取另一个线程正在写入会发生什么。

没人知道。混乱就会随之而来。

多个线程可以共享一个只读资源,但是一旦有人想写它,在写完之前每个人都以任何方式访问它变得不安全。

为什么?

写入不是原子的。它们发生在多个时钟周期内。一个进程试图读取一个对象,因为它被写入可能会发现一个半修改版本,临时垃圾。

所以

如果预期它们与您的写入同时进行,请锁定您的读取。

于 2013-03-21T15:19:34.037 回答
0

绝对不安全!

如果您Person::Name从“John Smith”更改为“James Watt”,您可能会读回“Jame Smith”或“James mith”的值。或者甚至可能是完全不同的东西,因为“为此更改此值”的方式可能不仅仅是将新数据复制到现有位置,而是将其完全替换为新分配的一块内存,其中包含一些完全未定义的内容 [包括不是有效的字符串]。

于 2013-03-21T15:20:20.677 回答