c# - 证明 int++ 不是原子的可靠方法
问题描述
我已经可以看出这不是由于增量不正确,而是我似乎无法理解的一小部分难题。
我们有以下代码:
internal class StupidObject
{
static public SemaphoreSlim semaphore = new SemaphoreSlim(0, 100);
private int counter;
public bool MethodCall() => counter++ == 0;
public int GetCounter() => counter;
}
以及下面的测试代码来尝试看看它是否是一个原子操作:
var sharedObj = new StupidObject();
var resultTasks = new Task[100];
for (int i = 0; i < 100; i++)
{
resultTasks[i] = Task.Run(async () =>
{
await StupidObject.semaphore.WaitAsync();
if (sharedObj.MethodCall())
{
Console.WriteLine("True");
};
});
}
Console.WriteLine("Done");
Console.ReadLine();
StupidObject.semaphore.Release(100);
Console.ReadLine();
Console.WriteLine(sharedObj.GetCounter());
Console.ReadLine();
我希望看到多个True
's 写入控制台,但我曾经看到一个。
这是为什么?据我了解,++
操作读取值,增加读取值,然后将该值存储到变量中。
那是3个操作。如果我们有竞争条件,线程A
会执行以下操作:
- 读取值为
0
。 - 将读取值增加
1
.
另一个线程B
做了同样的事情,但是将线程击败A
到第三个操作,如下所示:
- 将读取值写入变量。
当A
完成写入递增的读取值时,它应该在完成写入操作后打印回来0
,与线程相同B
。
我是否在设计方面遗漏了一些东西,或者我的测试不足以使这种确切情况成为现实?
True
没有任务并行库的示例(仍然向控制台生成一个):
var sharedObj = new StupidObject();
var resultTasks = new Thread[10000];
for (int i = 0; i < 10000; i++)
{
resultTasks[i] = new Thread(() =>
{
StupidObject.semaphore.Wait();
if (sharedObj.MethodCall())
{
Console.WriteLine("True");
};
});
resultTasks[i].IsBackground = false;
resultTasks[i].Start();
}
Console.WriteLine("Done");
Console.ReadLine();
StupidObject.semaphore.Release(10000);
解决方案
利亚姆所说的 Console.WriteLine是可能的,但还有另一件事。
启动任务不等于启动线程,即使启动线程也不能保证所有线程都会立即启动。启动 100 个短任务可能甚至不会显着填满 .Net 的线程池,因为这些任务很快结束,线程池的管理器可能不会启动超过 3-5 个线程。这不是您想要开始并行 100 增量以相互竞争时想要看到的“立即”和“并行”,对吗?请记住,任务首先排队,然后分配给线程。
请注意,StupidObject 的计数器从零开始,这是该值为零的唯一时刻。如果任何线程赢得比赛并成功写入该整数的更新,您将在所有未来任务中获得 FALSE,因为它已经是1
.
如果线程池队列中有很多任务,首先必须注意这一事实。在程序开始时,线程池缺少线程。他们不是在程序开始时就开始几十个。它们是按需启动的。很可能你用 100 个任务填满了队列,创建了线程池的线程,选择第一个任务,将计数器颠倒到 1,然后线程池可能会启动新线程以更快地使用任务。
为了更好地了解正在发生的事情,而不是打印出“true”,而是收集观察到的值return counter++
:让每个任务运行,完成,将其值存储在 Task's.Result
中,然后运行线程/任务,然后等待所有这些都停止,然后收集 .Results 并写出这些值的直方图。即使您没有看到 5 个零,也可能会看到 3 个 1、7 个 2、2 个 3 等等。
推荐阅读
- database - Neo4j - Java 堆空间 java.lang.OutOfMemoryError: Java 堆空间
- regex - 如何将多个数字序列捕获为重复组?
- django - 使用下载按钮在 django 管理面板中下载模型数据
- flutter - 通过 VSCode 在 android 模拟器上运行颤振应用程序会导致蓝屏(VIDEO_TDR_FAILURE)
- python-3.x - 带有列表和输出处理列表的 Python Lambda 函数调用
- python - 在 JWT 标头中添加额外的数据,简单的 jwt
- javascript - jQuery post 返回空响应
- gpu - AMD Polaris 上某些尺寸的矩阵乘法性能下降
- angular - 如何实现 Auth Guard:Angular
- android - 如何获取 FCM 注册令牌