首页 > 代码库 > C#线程同步技术(二) Interlocked 类

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 语句会等待资源的释放,直至访问成功完成任务;而本篇中我们的线程会视图访问一些资源,不成功时我们会干别的事情,不会等待。

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