c# - 证明int++不是原子的可靠方法

标签 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 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/

    相关文章:

    c# - 检查对象是否为空

    c# - ProcessStartInfo 卡在 "WaitForExit"上?为什么?

    c# - C# Windows 窗体鼠标事件问题中的自定义控件

    django - 如何在 Django View 中最好地启动异步作业请求?

    javascript - 异步 js 函数和日志记录错误

    c# - 如何在给定角度的情况下找到椭圆上的点

    java - Thread.currentThread().getName() 和 getName() 有什么区别?

    java - 停止重命名的线程

    multithreading - 编译器优化破坏多线程代码

    javascript - 如何做出 JSON.parse promise ? (或异步)