我已经可以看出它不是由错误的增量引起的,但是似乎只有一小部分我似乎无法捕获。
我们有以下代码:
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
写入控制台,但我从未见过一个。这是为什么?据我了解,
++
操作读取该值,递增读取的值,然后将该值存储到变量中。这些是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);
最佳答案
Liam 关于Console.WriteLine所说的是可能的,但是还有另一件事。
启动任务并不等于启动线程,甚至启动线程也不能保证所有线程都会立即启动。启动100个短任务可能甚至不会显着填满.Net的线程池,因为这些任务很快结束,并且线程池的管理器可能不会启动超过3-5个线程。当您要开始并行执行100个增量以相互竞争时,这不是您想看到的“立即”和“并行”,对吧?请记住,任务首先要排队,然后分配给线程。
请注意,StupidObject的计数器从零开始,这是该值为零的唯一时刻。如果ANY线程在竞赛中获胜并成功写入该整数的更新,您将在以后的所有任务中得到FALSE,因为它已经是1
。
而且,如果线程池的队列中有许多任务,则首先必须注意到这一事实。在程序启动时,线程池缺少线程。它们不是在程序启动时就几十个启动的。它们按需启动。 最有可能是,您用100个任务填充了队列,创建了线程池的线程,选择第一个任务,将计数器颠倒为1,然后也许线程池启动了新线程以更快地使用任务。
为了获得更好的图像,而不是打印出“true”,而是收集return counter++
观察到的值:让每个任务运行,完成,将其值存储在Task的.Result
中,然后运行线程/任务,然后等待所有操作停止,然后收集.Results并编写这些值的直方图。即使您看不到5个零,也许您也会看到3个,7个二,2个三等。
关于c# - 证明int++不是原子的可靠方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63187627/