什么是使用闭包,而不是锁共享状态的利弊? [英] What are the pros and cons of using closures instead of locks for shared state?

查看:254
本文介绍了什么是使用闭包,而不是锁共享状态的利弊?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想评估什么是跨一个作家共享状态,单阅读器的情况下,其中的阅读只是消耗了分配状态变量的最新值最快的解决方案作家。共享状态可以是任何管理类型(即引用或值类型)的。



理想情况下,同步解决方案将工作一样快,天真的非同步的解决方案成为可能,因为该方法将被用于两个单线程和多线程情景可能数千次。



读的顺序/写应该没有关系,只要阅读收到一定的时间范围内的最新值(即读者仅读过,从来没有改变,因此更新时间并不重要,只要它不旧的价值之前收到的未来价值。 ..)



天真的解决方案,没有锁定:



<预类=郎-CS prettyprint-覆盖> VAR内存=默认值(INT);
VAR读卡器= Task.Run(()=>
{
,而(真)
{
FUNC(内存);
}
});

VAR作家= Task.Run(()=>
{
,而(真)
{
内存= DateTime.Now.Ticks;
}
});

什么是真正的幼稚解决问题?我想出这些至今:




  1. 无担保的阅读看到最新值(无记忆障碍/挥发性)

  2. 。通过读者消耗
  3. 可能是无效的,如果共享变量的类型不是原始类型或引用类型(如复合值类型)。



这种简单的解决方案将被锁定:



<预类=郎-CS prettyprint-覆盖> VAR门=新的对象();
VAR内存=默认值(INT);
VAR读卡器= Task.Run(()=>
{
,而(真)
{
INT地方;
锁(门){本地=内存;}
FUNC(本地);
}
});

VAR作家= Task.Run(()=>
{
,而(真)
{
锁(门)
{
内存= DateTime.Now.Ticks;
}
}
});

这当然作品,但招致的锁定价格(〜50ns的)在单线程的情况下,当然的上下文切换的价格/中多争螺纹情况。



这是大多数情况下完全可以忽略不计,但在我的情况显著,因为该方法将全线使用上可能数以千计环这就需要。尽可能运行数万次每秒及时



我想过用不变的状态下关闭读取共享状态的最后解决办法:



<预类=郎-CS prettyprint-覆盖> Func键< INT>内存=()=>默认值(INT);
变种读者= Task.Run(()=>
{
而(真)
{
FUNC(存储器());
}
});

VAR作家= Task.Run(()=>
{
,而(真)
{
VAR状态= DateTime.Now.Ticks ;
内存=()=>状态;
}
});

现在会是什么与此有关的潜在问题?我自己的业绩基准这一解决方案VS在单线程的情况下锁定报告〜10ns的。这似乎是一个不错的收益,但是有些注意事项包括:




  1. 仍然没有内存屏障/极易挥发,阅读是不能保证看到最新的关闭(如何常见的是这实际上将是很好的知道吗?)

  2. 的原子性问题就解决了:因为闭包是引用类型,读取/写入已按照标准

  3. 拳击成本保证原子性:基本上都采用封闭意味着在堆中不知何故,这是在每次迭代中发生分配内存。不清楚是什么这个成本真的,但它似乎比锁定更快...



还有什么我失踪?你通常认为这种使用闭包,而不是锁在你的程序?也将是巨大的,知道单读/单写的共享状态等可能快速的解决方案。


解决方案

无论你的第一和第三个例子,当你已经指出,不能保证任务看到由作家分配中的最新值任务。一个缓解因素是,在x86硬件上,所有的存储器存取本质上是挥发性的,但没有什么关于你的问题制约的背景下x86硬件的,并且在假定写入或读取任何情况下不被JIT编译器优化掉了。



马克Gravell有的未受保护的危害极好的示范读/写,其中写入值就是从不的受阅线观察。底线是,如果你没有明确同步访问,你的代码被打破了。



所以,去与第二个例子,这是实际上正确的唯一一个





顺便说一句,尽量使用闭包包装的值去,我会说有没有点在。你可以很有效地包住值的一些集合中的对象,而不是直接由编译生成类为您,并使用该对象的引用,作为读者和作家的共享价值。使用 Thread.VolatileWrite() Thread.VolatileRead()的对象引用地址跨线程的可见性的问题(我假设你使用的是捕捉本地这里…当然如果共享变量是一个字段,你可以将其标记挥发性



该值可以在元组,也可以编写自己的定制类(你想使一成不变的,如元组,以确保防止意外的错误)。



当然,在你的第一个例子,如果你没有使用volatile语义,以解决像 INT A型,其中写和读可以自动完成的能见度问题。


I'm trying to assess what is the fastest solution for sharing state across a single-writer, single-reader scenario, where the reader just consumes the latest value of a state variable assigned by the writer. The shared state can be of any managed type (i.e. reference or value types).

Ideally the synchronization solution would work as fast as the naive non-synchronized solution as possible, since the method will be used for both single-threaded and multi-threaded scenarios potentially thousands of times.

The order of read/writes shouldn't matter, as long as reader receives the latest value within some time frame (i.e. reader will only ever read, never modify, so update timing doesn't matter as long as it doesn't receive a future value before an older value...)

Naive solution, no locking:

var memory = default(int);
var reader = Task.Run(() =>
{
    while (true)
    {
        func(memory);
    }
});

var writer = Task.Run(() =>
{
    while (true)
    {
        memory = DateTime.Now.Ticks;
    }
});

What are really the issues with the naive solution? I've come up with these so far:

  1. No guarantee reader sees the latest value (no memory barrier/volatile)
  2. Values consumed by reader may be invalid if the type of the shared variable is not a primitive type or reference type (e.g. composite value types).

The straightforward solution would be locking:

var gate = new object();
var memory = default(int);
var reader = Task.Run(() =>
{
    while (true)
    {
        int local;
        lock(gate) { local = memory; }
        func(local);
    }
});

var writer = Task.Run(() =>
{
    while (true)
    {
        lock(gate)
        {
            memory = DateTime.Now.Ticks;
        }
    }
});

This of course works, but incurs the price of locking (~50ns) in the single-threaded case, and of course the price of context-switching/contention in the multi-threaded case.

This is totally negligible for most scenarios, but it is significant in my case since the method will be used across the board on potentially thousands of loops which need to be as timely as possible running tens of thousands of times per second.

The last solution I thought about is using immutable state closures to read the shared state:

Func<int> memory = () => default(int);
var reader = Task.Run(() =>
{
    while (true)
    {
        func(memory());
    }
});

var writer = Task.Run(() =>
{
    while (true)
    {
        var state = DateTime.Now.Ticks;
        memory = () => state;
    }
});

Now what would be the potential issues with this? My own performance benchmarks report ~10ns for this solution vs locking in the single-threaded case. It seems like a nice gain, but some considerations include:

  1. Still no memory barrier/volatile so reader is not guaranteed to see the latest closure (how common is this actually? would be good to know...)
  2. The atomicity problem is solved: since closure is a reference type, reading/writing has guaranteed atomicity according to the standard
  3. Boxing costs: basically using a closure implies allocating memory on the heap somehow, which is happening on every iteration. Unclear what the costs of this really are but it seems to be faster than locking...

Is there anything else I'm missing? Do you usually consider this use for closures instead of locks in your programs? Would also be great to know other possible fast solutions for single-reader/single-writer shared state.

解决方案

Both your first and third examples, as you have already pointed out, fail to ensure the reader task sees the most recent value assigned by the writer task. A mitigating factor is that on x86 hardware, all memory access is essentially volatile, but there's nothing about your question that restricts the context to x86 hardware, and in any case that assumes the write or read wasn't optimized out by the JIT compiler.

Marc Gravell has an excellent demonstration of the hazards of unprotected write/read, in which the written value is simply never observed by the reading thread. The bottom line is that if you don't explicitly synchronize access, your code is broken.

So, go with the second example, which is the only one that's actually correct.


By the way, as far as using closures to wrap a value goes, I would say there's no point in that. You can just as effectively wrap some collection of values in an object directly instead of having the compiler generate the class for you, and use that object's reference as the shared value for the reader and writer. Using Thread.VolatileWrite() and Thread.VolatileRead() on the object reference addresses the cross-thread visibility issue (I'm assuming you're using a captured local here…of course if the shared variable is a field, you can just mark it volatile).

The values can be in a Tuple, or you can write your own custom class (which you would want to make immutable, like Tuple, to ensure against accidental bugs).

And of course, in your first example, if you did use volatile semantics, that addresses the visibility issue for a type like int, where the write and read can be done atomically.

这篇关于什么是使用闭包,而不是锁共享状态的利弊?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆