为什么读取操作不是线程安全的?

15

我在思考为什么从内存读取数据不是线程安全的。根据我所见,例如这个问题,从内存中读取数据似乎并不是线程安全的。

我已经使用Python编程了一段时间,现在开始学习C++。我从未听说过在Python中读取数据不是线程安全的。

如果我误解了,请纠正我,如果没有,请告诉我为什么从内存中读取数据不是线程安全的。


1
我建议阅读http://en.wikipedia.org/wiki/Readers-writers_problem。 - Digital Da
2
请注意,本问题的答案不适用于Java。Java是一种不同的语言,在Java中从变量读取不一定是线程安全的...即使没有其他内容在写入它。有些东西可以保证它是线程安全的(volatile、synchronize等),但默认情况下不是线程安全的,根据JLS的规定。(有些人正在使用这个Q&A来证明关于Java的不正确陈述...) - Stephen C
4个回答

22

读取数据是线程安全的,没有问题......直到有些东西写入你正在读取的位置,然后......希望你在数据被更改之前或之后进行了读取(在这些情况下,没有问题),但有时候,就在你真的不想它发生的时候,你会读到写操作的一半,然后得到完全错误的数据。

缓解这种问题的方法是确保你只在写入之前或之后进行读取,这需要你检查是否正在进行写入,并使用某种同步锁。然而,这会使事情变慢,因为你显然要检查锁定,然后才能读取,而不是直接读取。如果你正在使用基本数据类型(例如int),那么你可以使用CPU同步来大大加速这个过程。

对于Python来说,很可能语言运行时已经为你同步了Python数据,如果没有,那么迟早会遇到相同的线程读取问题。(快速谷歌搜索表明,如果你不小心,Python也会出现相同的问题。


嗨@bjbaanb,如果没有同步,同时由2个不同的线程更新1个变量,会发生什么?我知道这可能不是正确的问题,但它有一些关系。提前致谢。 - user19481364
1
@Daniel 通常情况下,最后一个写入的线程会覆盖之前的所有内容。但是,不能保证您将在另一个线程之后编写所有内容,您可能会得到每个作者数据的一半和一半。即仍然会出现损坏的情况。这取决于数据的大小,一个字节可能完全隔离,而2-64位字guid可能不会,像字符串这样的较大数据结构几乎肯定会出现故障。这就是(其中之一的原因)为什么读写锁始终专门用于写入,但允许多个读取器的原因。 - gbjbaanb
谢谢,@gbjbaanb。所以,当至少有一个线程(可能是多个线程中的一个)更新了某些将要被读取的内容时,总是建议使用锁定,对吗?这样做主要是为了避免不一致性。 - user19481364
1
@Daniel 为什么不直接提出一个新问题呢? - gbjbaanb

9

如果多个线程正在读取相同的位置,只要没有人试图在那里写入,它就是线程安全的。

考虑一下如果线程A正在读取某些东西,同时线程B正在写入正在被读取的内存。这将产生“竞态条件”。读取结果可能会变得无效或每次启动时都不同。


嗨,@Andrew,这适用于任何编程语言吗?例如Python? - user19481364

9

从内存中读取数据是线程安全的,但是从同时可能被写入的内存中读取数据是不安全的。

在Python中,这个问题不太严重,因为许多对象都是不可变的,因此在这些情况下只有引用被修改,而不是内存本身。


1

同时阅读相同的内容是安全的。问题在于当有东西同时写入时。

考虑以下代码:

int arr_len = 3;
int* arr = new int[arr_len];

void thread_1()
{
    std::cout << arr[arr_len-1];
}

void thread_2(int new_len)
{
    int* temp = new int[new_len];
    for(int i = 0; i < arr_len && i < new_len; ++i)
        temp[i] = arr[i];
    arr_len = new_len;
    delete arr;
    arr = temp;
}

假设arr[arr_len]是按顺序执行的(首先读取arr_len,然后是arr)。

当两个线程交替运行时会发生什么? 有三种可能:

  • 没有问题-你很幸运!
  • arr_len大于arr - UB :(
  • arr无效(已删除)- UB :(

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接