C# 证明int++;它不是原子的

C# 证明int++;它不是原子的,c#,multithreading,asynchronous,.net-core,async-await,C#,Multithreading,Asynchronous,.net Core,Async Await,我已经可以看出这不是由不正确的增量造成的,但我似乎不能完全理解其中的一小部分 我们有以下代码: internal class StupidObject { static public SemaphoreSlim semaphore = new SemaphoreSlim(0, 100); private int counter; public bool MethodCall() => counter++ == 0; public int GetCoun

我已经可以看出这不是由不正确的增量造成的,但我似乎不能完全理解其中的一小部分

我们有以下代码:

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();

利亚姆所说的Console.WriteLine是可能的,但还有另一件事

启动任务并不等于启动线程,甚至启动线程也不能保证所有线程都会立即启动。启动100个短任务甚至可能不会显著填满.Net的线程池,因为这些任务结束得很快,线程池的管理器启动的线程可能不会超过3-5个。这不是你想要看到的“立即”和“平行”,当你想要开始平行的100个增量来相互竞争时,对吗?请记住,任务首先排队,然后分配给线程

请注意,StupidObject的计数器以零开始,这是唯一一个值为零的时刻。如果任何线程赢得比赛并成功地将更新写入该整数,那么在以后的所有任务中都会得到FALSE,因为它已经是
1

如果线程池的队列上有许多任务,那么首先必须注意这个事实。在程序启动时,线程池缺少线程。它们不是在程序开始时就开始的。它们是按需启动的很可能您用100个任务填充队列,创建线程池的线程,选择第一个任务,将计数器设置为1,然后可能线程池启动新线程以更快地使用任务


为了更好地了解正在发生的事情,不要打印“true”,而是收集返回计数器+++观察到的值:让每个任务运行,完成,将其值存储在任务的
.Result
,然后运行线程/任务,然后等待所有这些任务停止,然后收集。结果并编写这些值的直方图。即使你看不到5个零,也许你会看到3个1,7个2,2个3等等。

你在写一个单元测试来测试
int
本身吗?为什么?对大多数人来说,简单的存在和框架开发人员创建它不是为了欺骗粗心的初学者的假设应该足够了。荒谬的证明int++不是原子的(作为一个注释更好)@TheGeneral在运行了大约15次后,仅仅是偶然的机会,我实际上得到了一个零值@pinkfloydx33哈,我称之为胜利<代码>如果任何线程赢得了比赛并成功地将更新写入该整数,那么在以后的所有任务中,您都会得到FALSE,因为它已经是1。->但事实确实如此吗?
++
操作不是按照我描述的方式工作吗?因为如果是,那么多个线程可以并且应该打印相同的初始读取值。@但是“其他线程”何时读取初始值?他们不会在任务创建时这样做。只捕获对StupidObject的对象引用。线程检查值的唯一时刻是实际执行x++时,此时读取“x”。但是什么时候?当程序启动时,您只有创建和启动任务的主线程。调用第一个Task.Run时,将创建第一个线程,并且可以立即自由运行。或者一会儿之后。你们有一个管理竞赛:Main会在第一个线程运行x++之前运行多个线程吗?@SpiritBob:(继续)正如我所写的,情况更糟,因为启动一个任务并不等于启动一个线程。当然,当创建第一个任务时,稍后会创建一个线程来处理它。但其他任务可能会排队。现在有两个任务:主任务和线程池上的一个任务。再也没有了。在threadpool检测到暂停并决定启动更多任务之前,99.99%的几率其他线程醒来并执行x++操作。
SemaphoreSlim
是创建竞争条件的最佳方法。您准备好要同步的线程,然后同时释放它们以进行竞争。当线程
A
到达
++
操作时,它开始逐个执行其每个操作。当另一个线程超过线程
A
的3个操作时,两个线程都应该返回
0
。你可以更详细地了解我在第一个问题中试图解释的内容。我还包括了一个长寿线程的例子。
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);