c# - 在这种情况下,不可变变量线程安全吗?
问题描述
考虑以下代码示例,该变量i
是不可变int
类型。递增后i
,新值存储在相同的内存位置。因此,如果多个线程正在从该位置读取数据,其中一些线程可能会在写入过程中获取损坏的数据。那么这个不可变类型的线程如何安全呢?是否有任何内部 CLR 逻辑可以解决这个问题?
public class Test
{
int i = 10;
public unsafe int Run()
{
fixed (int* ip = &i)
{
Console.WriteLine($"address of i before updation: {((IntPtr)ip).ToString()}");
}
i = i + 1;
fixed (int* ipNew = &i)
{
Console.WriteLine($"address of i after updation: {((IntPtr)ipNew).ToString()}");
}
return i;
}
}
更新:
我根据之前不清楚的评论更新了代码。现在如果,类Test
由客户端启动一次,Run
方法由多个线程调用。会i
被认为是线程安全的吗?
解决方案
要回答您的更新,不,代码不会是线程安全的。您正在读取 i,增加值,然后写入 i。这三个步骤不是一个逻辑单元,其他线程可以在读写步骤之间与 i 交互。
例如,您有三个线程,AB 和 C。发生的事情使 B 比其他线程运行得慢。
A: Read i to thread local memory location A the value 10
B: Read i to thread local memory location B the value 10
A: Add 1 to thread local memory location A
A: Write 11 to i from thread local memory location A
B: Add 1 to thread local memory location B
C: Read i to thread local memory location C the value 11
C: Add 1 to thread local memory location C
C: Write 12 to i from thread local memory location C
B: Write 11 to i from thread local memory location B
因为 3 个线程不是“原子”的 3 个操作可以在 B 必须执行的 3 个步骤之间完成工作,这会导致您有错误的结束值。
处理此问题的正常方法是锁定 3 个操作,因此只有一个线程可以一次执行此操作,
lock(someObject)
{
i = i + 1;
}
使用使操作原子化的工具
Interlocked.Increment(ref i);
或检查 i 的值在读取开始和您要执行的写入之间没有改变,如果它确实改变了,请重试操作。
int iOriginal, iNew;
do
{
iOriginal = i;
iNew = iOriginal + 1;
} while(iOriginal != Interlocked.CompareExchange(ref i, iNew, iOriginal)
人们说不可变值是线程安全的原因是他们指的是将引用的副本传递给另一个函数,一旦创建了该引用的副本,您就无需担心另一个线程会在您更改该对象的值时正在使用它。但是,如果您没有制作引用的副本(例如在您的示例中,您在函数范围之外使用了共享变量),您就会遇到线程之间使用的引用的非不变性问题。
简单来说,值 10 是不可变的,同名的变量i
不是。如果您跨线程共享变量(我不是在谈论变量具有的对象引用/值,而是变量本身),那么您正在使用一个可变对象。
推荐阅读
- vbscript - 如何在 VBScript 中返回 ArrayList?
- python - 在列表中查找特定字符
- css - DIV/SPAN 标签未在 GRID 的最后一个完整显示
- spring-boot - 如何将 ConstraintViolationException 500 错误转换为 400 错误请求?
- python - Compare two dictionaries returned from REST framework using pytest
- postgresql - 如何通过层次结构连接加入同一张表?
- php - 为什么不保存到数据库,excel导入
- javascript - Ag-Grid setRowData 没有设置网格内的数据
- javascript - 以 url 作为数据源的对象仅在 IE11 中以损坏的样式呈现
- gradle - Gradle 在执行单元测试时构建每个子项目