System.Threading.Interlocked
对象允许加法(减法)和比较作为原子操作。似乎只是不做相等的CompareExchange以及作为原子比较的GreaterThan / LessThan将是非常有价值的。
假设的Interlocked.GreaterThan
是IL的一个功能还是CPU级功能?既?
缺少任何其他选项,是否可以在C ++或直接IL代码中创建此类功能并将该功能公开给C#?
答案 0 :(得分:40)
你可以build other atomic operations out of InterlockedCompareExchange
。
public static bool InterlockedExchangeIfGreaterThan(ref int location, int comparison, int newValue)
{
int initialValue;
do
{
initialValue = location;
if (initialValue >= comparison) return false;
}
while (System.Threading.Interlocked.CompareExchange(ref location, newValue, initialValue) != initialValue);
return true;
}
答案 1 :(得分:3)
您对此实施有何看法:
// this is a Interlocked.ExchangeIfGreaterThan implementation
private static void ExchangeIfGreaterThan(ref long location, long value)
{
// read
long current = Interlocked.Read(ref location);
// compare
while (current < value)
{
// set
var previous = Interlocked.CompareExchange(ref location, value, current);
// if another thread has set a greater value, we can break
// or if previous value is current value, then no other thread has it changed in between
if (previous == current || previous >= value) // note: most commmon case first
break;
// for all other cases, we need another run (read value, compare, set)
current = Interlocked.Read(ref location);
}
}
答案 2 :(得分:3)
使用这些辅助方法,您不仅可以交换价值,还可以检测它是否被替换。
用法如下:
int currentMin = 10; // can be changed from other thread at any moment
int potentialNewMin = 8;
if (InterlockedExtension.AssignIfNewValueSmaller(ref currentMin, potentialNewMin))
{
Console.WriteLine("New minimum: " + potentialNewMin);
}
以下是方法:
public static class InterlockedExtension
{
public static bool AssignIfNewValueSmaller(ref int target, int newValue)
{
int snapshot;
bool stillLess;
do
{
snapshot = target;
stillLess = newValue < snapshot;
} while (stillLess && Interlocked.CompareExchange(ref target, newValue, snapshot) != snapshot);
return stillLess;
}
public static bool AssignIfNewValueBigger(ref int target, int newValue)
{
int snapshot;
bool stillMore;
do
{
snapshot = target;
stillMore = newValue > snapshot;
} while (stillMore && Interlocked.CompareExchange(ref target, newValue, snapshot) != snapshot);
return stillMore;
}
}
答案 3 :(得分:2)
实际上并非如此,但将并发视为以两种形式出现是有用的:
这不是真的,因为基于软件锁的并发最终是在堆栈的某个地方(通常在内核中)使用无锁原子指令实现的。然而,锁定自由原子指令最终都会最终获得内存总线上的硬件锁。因此,实际上,无锁并发和基于锁的并发是相同的。
但从概念上讲,在用户应用程序层面,它们是两种不同的做事方式。
基于锁的并发性基于“锁定”对关键代码段的访问的想法。当一个线程“锁定”一个关键部分时,没有其他线程可能在同一个关键部分内运行代码。这通常通过使用“互斥量”来完成,它与os调度程序接口并导致线程在等待进入锁定的临界区时变得不可运行。另一种方法是使用“自旋锁”,这会导致线程在循环中旋转,没有任何用处,直到临界区变为可用。
锁定自由并发基于使用原子指令(CPU特别支持)的想法,这些指令由硬件保证以原子方式运行。 Interlocked.Increment是无锁并发的一个很好的例子。它只调用执行原子增量的特殊CPU指令。
锁定自由并发很难。随着关键部分的长度和复杂性的增加,它变得特别困难。临界区中的任何步骤都可以同时由任意数量的线程同时执行,并且它们可以以极其不同的速度移动。您必须确保尽管如此,整个系统的结果仍然是正确的。对于类似增量的东西,它可以很简单(cs只是一条指令)。对于更复杂的关键部分,事情会变得非常复杂。
基于锁的并发性也很难,但并不像无锁并发那么难。它允许您创建任意复杂的代码区域,并且知道任何时候只有1个线程正在执行它。
无锁并发有一个很大的优势:速度。如果使用正确,它可能比基于锁的并发快几个数量级。旋转循环对于长时间运行的关键部分是不利的,因为它们会浪费CPU资源。互斥体对于小型关键部分可能不好,因为它们会带来很多开销。它们至少涉及模式切换,在最坏的情况下涉及多个上下文切换。
考虑实施托管堆。每次调用“新”被称为操作系统都会很糟糕。它会破坏你的应用程序的性能。但是,使用无锁并发可以使用互锁增量来实现gen 0内存分配(我不确定这是否就是CLR的功能,但如果不是这样,我会感到惊讶。这可能是一个巨大的节省。
还有其他用途,例如无锁数据结构,如持久堆栈和avl树。他们通常使用“cas”(比较和交换)。
然而,基于锁定的并发和无锁并发的原因实际上是等价的原因是因为每个的实现细节。
自旋锁通常在其循环条件下使用原子指令(通常为cas)。互斥锁需要在其实现中使用自旋锁或内部内核结构的原子更新。
反过来使用硬件锁实现原子指令。
无论如何,他们都有各自的权衡取舍,通常以性能与复杂性为中心。互斥锁可以比无锁代码更快更慢。无锁代码可以比互斥锁更复杂。适当的使用机制取决于具体情况。
现在,回答你的问题:
执行互锁比较交换的方法,如果少于将调用者表示它没有使用锁。您不能使用单个指令以相同的方式实现增量或比较交换。您可以通过循环中的互锁比较交换来模拟它进行减法(计算小于)。您也可以使用互斥锁(但这意味着锁定,因此在名称中使用“interlocked”会产生误导)。是否适合构建“模拟互锁通过cas”版本?那要看。如果代码被频繁调用,并且几乎没有线程争用,那么答案是肯定的。如果没有,您可以将具有中等高常数因子的O(1)运算转换为无限(或非常长)循环,在这种情况下,使用互斥锁会更好。
大部分时间都不值得。
答案 4 :(得分:2)
更新到我在这里发表的后一篇文章:我们找到了一种更好的方法,通过使用额外的锁定对象来进行更大的比较。我们编写了许多单元测试,以验证锁和Interlocked可以一起使用,但仅限于某些情况。
代码如何工作:Interlocked使用读取或写入是原子的内存屏障。需要使用sync-lock来进行大于比较的原子操作。所以现在的规则就是在这个类中没有其他操作在没有这个同步锁的情况下写入值。
我们用这个类得到的是一个互锁的值,可以非常快速地读取,但写入需要更多一点。在我们的应用程序中,读取速度大约快2-4倍。
此处代码为view:
见这里:http://files.thekieners.com/blogcontent/2012/ExchangeIfGreaterThan2.png
此处为复制和粘贴代码:
public sealed class InterlockedValue
{
private long _myValue;
private readonly object _syncObj = new object();
public long ReadValue()
{
// reading of value (99.9% case in app) will not use lock-object,
// since this is too much overhead in our highly multithreaded app.
return Interlocked.Read(ref _myValue);
}
public bool SetValueIfGreaterThan(long value)
{
// sync Exchange access to _myValue, since a secure greater-than comparisons is needed
lock (_syncObj)
{
// greather than condition
if (value > Interlocked.Read(ref _myValue))
{
// now we can set value savely to _myValue.
Interlocked.Exchange(ref _myValue, value);
return true;
}
return false;
}
}
}
答案 5 :(得分:1)
所有互锁操作都直接支持硬件。
互锁操作和原子数据类型是不同的东西。原子类型是库级功能。在某些平台和某些数据类型中,使用互锁指令实现原子。在这种情况下,它们非常有效。
在平台根本没有互锁操作或某些特定数据类型不可用的情况下,库使用适当的同步(crit_sect,mutex等)实现这些操作。
我不确定是否真的需要Interlocked.GreaterThan
。否则它可能已经实现。如果你知道一个有用的好例子,我相信这里的每个人都会很高兴听到这个。
答案 6 :(得分:0)
大于/小于等于已经是原子操作。这并不能解决应用程序的安全并发行为。
让他们成为联锁家庭的一部分毫无意义,所以问题是:你究竟想要实现什么目标?