zoukankan      html  css  js  c++  java
  • C#线程同步技术(二) Interlocked 类

    接昨天谈及的线程同步问题,今天介绍一个比较简单的类,Interlocked。它提供了以线程安全的方式递增、递减、交换和读取值的方法。

    它的特点是:

    1、相对于其他线程同步技术,速度会快很多。

    2、只能用于简单的同步问题。

    比叫好理解,不再赘述,给一个我们常用的单例模式的 Interlocked 实现:

            class SourceManager
            {
                private SourceManager() { }
    
                private static SourceManager sourceManager;
                public static SourceManager Instance
                {
                    get
                    {
                        if (sourceManager == null)
                        {
                            /*
                             
                            lock 实现方式
                            功能与以下 Interlocked.CompareExchange 相同
                             
                            lock (this)
                            {
                            if (sourceManager == null)
                            {
                                sourceManager = new SourceManager();
                            }
                            }
                             
                            */
                            Interlocked.CompareExchange<SourceManager>(ref sourceManager, new SourceManager(), null);
                        }
                        return sourceManager;
                    }
                }
            }

    Interlocked 类用于使变量的简单语句原子化。再用一个例子说明用 Interlocked 实现线程安全资源锁定机制。

    在这个例子中,我们会建立10任务,每个任务会分别循环50000次请求使用资源,而这种资源我们限定同一时间只能有一个线程访问,请求成功则递增 accessed 值,失败则递增 denied 值,因此按我们的预期,accessed 和 denied 的和将会始终是 10*50000 = 500000。且看我们设计的机制:

        class InterlockedCase
        {
            private static int accessed = 0;
            private static int denied = 0;
    
            // 0 没有线程在使用 1 有线程正在使用
            private static int usingResource = 0;
    
            private const int nTaskIterations = 50000;
            private const int nTasks = 10;
    
            public static void Test()
            {
                Task[] tasks = new Task[nTasks];
                for (int i = 0; i < nTasks; i++)
                {
                    tasks[i] = Task.Factory.StartNew(ThreadProc);
                }
                for (int i = 0; i < nTasks; i++)
                {
                    tasks[i].Wait();
                }
                Console.WriteLine("accessed:{0}, denied:{1}, total:{2}", accessed, denied, accessed+denied);
            }
    
            private static void ThreadProc()
            {
                for (int i = 0; i < nTaskIterations; i++)
                {
                    UseResource();
                }
            }
    
            private static bool UseResource()
            {
                if (usingResource == 0)
                {
                    usingResource = 1;
    
                    accessed++;
    
                    usingResource = 0;
                    return true;
                }
                else
                {
                    Interlocked.Increment(ref denied);
                    return false;
                }
            }
        }

    上面例子的运行结果total值却不是我们预期的总请求数 50000!

    在代码中,我们设计了一个访问共享资源的逻辑

    if (usingResource == 0)
    {
      usingResource = 1;

      accessed++;

      usingResource = 0;
      return true;
    }

    错误的原因是我们控制资源的逻辑里 usingResource 的判断和赋值操作并不是原子操作,会导致有多个线程能同时进入内层操纵资源,修改 accessed!导致 accessed 值的统计不准确!

    找到原因,我们把 usingResource 的判断和赋值转为原子操作,就能实现我们的构想了,Interlocked 类正好派上用场!

    改造 UseResource() 函数,输出结果正式我们期望的 500000

            private static bool UseResource()
            {
                if (Interlocked.Exchange(ref usingResource,1) == 0)
                {
                    accessed++;
                    usingResource = 0;
                    return true;
                }
                else
                {
                    Interlocked.Increment(ref denied);
                    return false;
                }
            }

    读到这里,有心的朋友可能会问,usingResource 变量为何设计成整型值?用布尔值不好吗?这正是体现整型值的灵活的地方,我们可以通过更改 UseResource() 函数的逻辑,控制统一时间可以有多少个线程访问资源,而并非只限定一个线程可以访问。

    后话:

    这是第二篇关于线程同步的学习笔记,其实书看得很快,但是文章却写得很慢。我发觉学习线程同步最好的方式就是设计一个反例,并更正它,确认运行结果是否与你预期的一致。在写这篇文章的过程中,我试图设计很多例子,也激发了自己很多的思考,其中有些想法开始是错的,在不断对比思考的过程里,逐渐加深认识了线程资源访问的设计。在大逻辑上,上一篇中 lock 语句会等待资源的释放,直至访问成功完成任务;而本篇中我们的线程会视图访问一些资源,不成功时我们会干别的事情,不会等待。

    希望在这一系列文章写完的时候,我会对线程的同步有一个正确且深刻的认识,这也是我写这些读书笔记的目的。

  • 相关阅读:
    VC++界面编程个性化你的工具栏图标(转)
    C/C++指令 #undef ,#ifdef, #ifndef,#if的用法(转)
    为自定义工具栏按钮添加消息响应函数
    VC++深入详解:函数的重载 (转)
    NP和P问题
    How To Compile A Kernel The Ubuntu Way
    【转】关闭对话框,OnClose和OnCancel MFC中屏蔽ESC和回车关闭对话框
    MFC学习笔记之ClassWizard
    《c专家编程》学习笔记一
    <转>C语言中的文件输入输出函数
  • 原文地址:https://www.cnblogs.com/cnhxz/p/3861893.html
Copyright © 2011-2022 走看看