【发布时间】:2014-10-15 14:53:26
【问题描述】:
同时运行两个线程会在同时从两个线程写入和读取变量时产生奇怪的行为。它可以是线程安全的,但并非在所有情况下都是如此。
线程安全示例:TThread.Terminated
Boolean Terminated 只读取 FTerminated,它只设置一次,因为它是一个布尔值,所以写入过程是原子的。因此,该值可以在 MainThread 和线程中读取,并且始终是线程安全的。
我的例子:我有一个字符串,它只写一次。与 TThread.Terminated 不同,我的字符串的写入不是原子的,因此读取它本身不是线程安全的。但是在特殊情况下可能有一种线程安全的方式:我有一种情况,我只想将字符串与另一个字符串进行比较。我只在它们相同的情况下做一些事情(如果它们不相等并不重要,因为字符串还没有完全写入)。所以我想这是否可能是线程安全的。那么当字符串被写入时究竟会发生什么,如果我在字符串写到一半时读取字符串可能会出现什么问题?
写字符串的步骤:
- 引用计数 = 1:
- 如果新字符串比旧字符串长,则分配额外的内存
- 复制字符
- 设置新的字符串长度
- 如果新字符串比旧字符串短,则释放内存
- 引用计数 > 1(由于写时复制语义,需要一个新的字符串实例):
- 为新的字符串实例分配内存
- 将字符复制到新位置并设置字符串长度
- 将字符串实例指针定位到新位置
在什么情况下读取在同一时刻写入的字符串是安全的?
- 引用计数 = 1:
- 只有(并且在这种情况下总是)安全读取如果步骤顺序如上所列,并且在设置长度之前读取字符串只会返回设置长度(不是所有分配的字节)李>
- 引用计数 > 1:
- 只有在将指向字符串的指针设置为最后一步(因为设置此指针是原子操作)或在指向字符串已设置,“参考计数 = 1”的情况适用于新字符串
向拥有如此深厚知识的人提问:我的假设是否正确?如果是,我可以安全地依赖它吗?或者依赖这个实现细节是一个糟糕的主意,甚至不值得考虑所有这些,只是在将字符串写入另一个线程时不要在不受保护的情况下读取它们?
【问题讨论】:
-
对于Delphi字符串,如果你有一个线程写入和一个线程读取,它永远不会是线程安全的。用锁保护。我的答案中的
TThreadsafe<T>类可以完成这项工作:stackoverflow.com/questions/19703274/… -
@David 当然,这将是一种简单的方法,但我想更深入地研究它。如果我不需要,为什么要同步到线程?你能解释一下为什么在我概述的情况下它不是线程安全的吗?
-
如果你不需要同步,那么肯定不要。我不认为这是容易或困难之间的选择。我并不是建议您选择简单的方法。我认为正确性应该是这里的目标。你不同意吗?
-
由于您只写入一次字符串,因此您可以通过添加额外的布尔值来避免锁定。写入字符串,并将布尔值设置为 true。如果要读取字符串,请在尝试读取字符串之前检查布尔值。这有效,但增加了复杂性。没有任何好处。像临界区这样的锁只有在发生争用时才会昂贵。由于您只写一次,因此实际上不会发生争用。
-
@David 可能会发生争用,因为我多次尝试读取这个字符串,直到它相等。这可以满足于编写它的那一刻。无论如何,这可以称为微优化,我只是为了学习它而深入研究它。总之,您是说在没有任何保护措施的情况下读取该字符串可能会导致读取未初始化的数据甚至访问冲突?
标签: string multithreading delphi