C#线程同步技术(二) Interlocked 类

2022-11-03,,,

接昨天谈及的线程同步问题,今天介绍一个比较简单的类,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 = ;
private static int denied = ; // 0 没有线程在使用 1 有线程正在使用
private static int usingResource = ; private const int nTaskIterations = ;
private const int nTasks = ; public static void Test()
{
Task[] tasks = new Task[nTasks];
for (int i = ; i < nTasks; i++)
{
tasks[i] = Task.Factory.StartNew(ThreadProc);
}
for (int i = ; 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 = ; i < nTaskIterations; i++)
{
UseResource();
}
} private static bool UseResource()
{
if (usingResource == )
{
usingResource = ; accessed++; usingResource = ;
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,) == )
{
accessed++;
usingResource = ;
return true;
}
else
{
Interlocked.Increment(ref denied);
return false;
}
}

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

后话:

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

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

C#线程同步技术(二) Interlocked 类的相关教程结束。

《C#线程同步技术(二) Interlocked 类.doc》

下载本文的Word格式文档,以方便收藏与打印。