我应该*始终*同步访问从多个线程使用的所有双字段/属性/变量?

时间:2012-07-06 10:41:23

标签: c# synchronization

注意我倾向于编写无锁代码,因此我尽量避免使用任何类型的锁。相反,我只使用while(true)循环,因为我有很多CPU能力。

根据http://msdn.microsoft.com/en-us/library/aa691278%28VS.71%29.aspx double变量更新不是原子的。

我担心两个问题:

  • 如果一个线程修改了字段或属性的变量而另一个线程同时读取 我希望有一个先前值或新值,但我不想收到一些奇怪的东西。即如果一个线程将值从5.5更改为15.15,我希望在另一个线程中包含这两个数字中的一个,而不是5.15或15.5或其他任何内容。
  • 如果一个线程已经更新了值,而另一个线程在后读取,我希望收到最新的,最新的值。我认为 volatile 关键字可以帮助解决这个问题,但似乎不能,因为“Volatile不能保证值的新鲜度。它会阻止一些优化,但不能保证线程同步。”正如are c# primitive arrays volatile?
  • 所述

问题:

  • 我是否更正未经同步可能会出现这两个问题?
  • 如果你能给我一些简短的例子证明没有同步它就行不通了 - 那会很好
  • 我应该如何访问双字段或变量或属性以始终具有真正的最新值?“同步”是否会保证“新鲜度”?最快的方法是什么? spinlock还是什么?

目前我在程序中使用了很多doubledecimal变量/字段/属性,而且几乎每个都工作正常,所以我真的很困惑,因为我从不同的线程访问它们而没有任何同步和只是有效...但现在我想,使用float进行“内置同步化”可能会更好

4 个答案:

答案 0 :(得分:3)

是的,你需要做点什么。 doubledecimal 不保证是原子,所以如果你不保护它,你可能会得到一个撕裂的价值 - 即你的第一颗子弹是完全正确的。

Re volatile;这是没有意义的; 不允许拥有volatiledouble的{​​{1}}字段,因此最简单的答案是:decimal

lock失败是皇家PITA;但是这里有一个以double为特征的撕裂值示例(请注意,即使数据相同,成功/失败的数量也会改变每次迭代;这是线程调度的随机性):

decimal

关键点;该语言使using System; using System.Threading; static class Program { private static decimal shared ; static void Main() { Random random = new Random(12345); decimal[] values = new decimal[20]; Console.WriteLine("Values:"); for (int i = 0; i < values.Length; i++) { values[i] = (decimal)random.NextDouble(); Console.WriteLine(values[i]); } Console.WriteLine(); object allAtOnce = new object(); int waiting = 10; shared = values[0]; int correct = 0, fail = 0; for(int i = 0 ; i < 10 ; i++) { Thread thread = new Thread(() => { lock(allAtOnce) { if (Interlocked.Decrement(ref waiting) == 0) { Monitor.PulseAll(allAtOnce); } else { Monitor.Wait(allAtOnce); } } for(int j = 0 ; j < 1000 ; j++) { for(int k = 0 ; k < values.Length ; k++) { Thread.MemoryBarrier(); var tmp = shared; if(Array.IndexOf(values, tmp) < 0) { Console.WriteLine("Invalid value detected: " + tmp); Interlocked.Increment(ref fail); } else { Interlocked.Increment(ref correct); } shared = values[k]; } } if (Interlocked.Increment(ref waiting) == 10) { Console.WriteLine("{0} correct, {1} fails", Interlocked.CompareExchange(ref correct, 0, 0), Interlocked.CompareExchange(ref fail, 0, 0)); Console.WriteLine("All done; press any key"); Console.ReadKey(); } }); thread.IsBackground = false; thread.Start(); } } } 的原子性无保证。实际上,我希望你会好起来的,但线程引起的大多数细微问题都是由于使用“我期待”而不是“我可以保证”。

答案 1 :(得分:0)

如果你想保证在另一个线程操作它之前执行并完成一段代码,请用lock包围该代码块。

你可能很幸运,线程可能永远不会使用变量,但要确保它永远不会发生,确保采取预防措施不会受到伤害。

看看这里 - 这可能会有所帮助:http://msdn.microsoft.com/en-us/library/ms173179%28v=vs.80%29.aspx

答案 2 :(得分:0)

一般答案是 - 应为所有“共享”变量同步更新。要获得准确答案,请查看代码段。

答案 3 :(得分:0)

是的,如果多个线程同时读/写一个double,你需要锁定以确保得到正确的结果。

这是一个失败的例子

[TestFixture]
public class DoubleTest
{
    private double theDouble;

    [Test]
    public void ShouldFailCalledParallell()
    {
        theDouble = 0;
        const int noOfTasks = 100;
        const int noOfLoopInTask = 100;
        var tasks = new Task[noOfTasks];
        for (var i = 0; i < noOfTasks; i++)
        {
            tasks[i] = new Task(() =>
                                    {
                                        for (var j = 0; j < noOfLoopInTask; j++)
                                        {
                                            theDouble++;
                                        }
                                    });
        }
        foreach (var task in tasks)
        {
            task.Start();
        }
        Task.WaitAll(tasks);
        theDouble.Should().Be.EqualTo(noOfTasks * noOfLoopInTask);
    }
}