0

我在这里列出了代码:Threading and Sockets

这个问题的答案是isListeningvolatile. 正如我所说,该修饰符允许我从另一个线程访问变量。读完MSDN后,我意识到我是从下面新创建的线程进程中读取isListening。

所以,我现在的问题:

  • volatile首选方法,因为我基本上是在对变量发出非线程安全请求吗?我已经阅读了有关 Interlocked 类的信息,并想知道在我的代码中使用它是否会更好。Interlocked 看起来与lock(myObj)正在做的事情相似——但具有更多的“天赋”和控制力。我知道简单地应用一个lock(myObj)代码块isListening是行不通的。

  • 我应该实现 Interlocked 类吗?

感谢您的时间和回复。

4

3 回答 3

2

如果您所做的只是在 C# 中跨多个线程读取和写入变量,那么您不必担心同步访问(锁定)该变量,只要其类型为 bool、char、byte、sbyte、short、ushort、int 、uint、float 和引用类型。 有关详细信息,请参见此处

在您另一篇文章的示例中,您必须将字段标记为的原因volatile是确保它不受编译器优化的影响,并且该字段中始终存在最新值。有关关键字的详细信息,请参见此处。volatile这样做允许跨线程读取和写入该字段,而无需锁定(同步访问)它。但请记住,volatile关键字只能用于您的字段,因为它是 bool 类型。例如,如果它是一个双精度关键字,那么volatile关键字将不起作用,并且您必须使用锁。

该类Interlocked用于特定目的,即递增、递减和交换(通常)数字类型的值。这些操作不是原子的。例如,如果您在一个线程中增加一个值并试图在另一个线程中读取结果值,您通常必须锁定变量以防止读取中间结果。该类Interlocked只是提供了一些便利功能,因此您不必在执行增量操作时自己锁定变量。

您对isListening标志所做的事情不需要使用Interlocked该类。将字段标记为 volatile 就足够了。

于 2009-11-18T14:24:20.337 回答
1

由于午餐时间匆忙回答而编辑..

之前代码中使用的 lock 语句锁定了在方法范围内创建的对象实例,因此它不会影响另一个调用同一方法的线程。每个线程必须能够锁定对象的同一实例,以便同步访问给定的代码块。一种方法(取决于您需要的语义)是使锁定对象成为它所使用的类的私有静态变量。这将允许给定对象的多个实例同步访问代码块或单个共享资源。如果对象的各个实例或特定于实例的资源需要同步,则应发出静态。

Volatile 不保证对给定变量的读取或写入在不同线程之间是原子的。这是一个编译器提示,用于保留指令的顺序并防止变量被缓存在寄存器中。一般来说,除非您正在处理对性能极为敏感的事情(低锁定/无锁算法、数据结构等)或者真的知道您正在做的事情,否则我会选择使用 Interlocked。在大多数应用程序中使用 volatile / interlocked / lock 之间的性能差异可以忽略不计,因此,如果您不确定最好使用能够为您提供最安全保证的方法(阅读 Joe Duffy 的博客和书籍)。

例如,在下面的示例中使用 volatile 不是线程安全的,并且递增的计数器未达到 10,000,000(当我运行测试时,它达到了 8848450)。这是因为 volatile 仅保证读取最新值(例如,不从寄存器缓存)。使用互锁时,操作是线程安全的,并且计数器确实达到 10,000,000。

public class Incrementor
{
    private volatile int count;

    public int Count
    {
        get { return count; }   
    }

    public void UnsafeIncrement()
    {
        count++;
    }

    public void SafeIncrement()
    {
        Interlocked.Increment(ref count);
    }
}

[TestFixture]
public class ThreadingTest
{
    private const int fiveMillion = 5000000;
    private const int tenMillion = 10000000;

    [Test]
    public void UnsafeCountShouldNotCountToTenMillion()
    {
        const int iterations = fiveMillion;
        Incrementor incrementor = new Incrementor();
        Thread thread1 = new Thread(() => UnsafeIncrement(incrementor, iterations));
        Thread thread2 = new Thread(() => UnsafeIncrement(incrementor, iterations));

        thread1.Start();
        thread2.Start();

        thread1.Join();
        thread2.Join();

        Assert.AreEqual(tenMillion, incrementor.Count);
    }

    [Test]
    public void SafeIncrementShouldCountToTenMillion()
    {
        const int iterations = fiveMillion;
        Incrementor incrementor = new Incrementor();
        Thread thread1 = new Thread(() => SafeIncrement(incrementor, iterations));
        Thread thread2 = new Thread(() => SafeIncrement(incrementor, iterations));

        thread1.Start();
        thread2.Start();

        thread1.Join();
        thread2.Join();

        Assert.AreEqual(tenMillion, incrementor.Count);
    }

    private void UnsafeIncrement(Incrementor incrementor, int times)
    {
        for (int i =0; i < times; ++i)
            incrementor.UnsafeIncrement();
    }

    private void SafeIncrement(Incrementor incrementor, int times)
    {
        for (int i = 0; i < times; ++i)
            incrementor.SafeIncrement();
    }
}

如果您搜索“互锁易失性”,您会找到许多问题的答案。例如,下面的一个解决了您的问题:

下面的一个简单示例显示

易失性与互锁性与锁定性

于 2009-11-18T14:29:52.503 回答
0

“做到这一点的一种方法是使锁定对象成为使用它的类的私有静态变量。” 为什么它应该是静态的?只要它们在不同的对象上工作,您就可以从多个线程访问相同的函数。我并不是说它不起作用,而是会严重降低应用程序的速度而没有任何优势。还是我错过了什么?

以下是 MSDN 关于 volatile 的说明:“此外,在优化时,编译器必须保持对 volatile 对象的引用以及对其他全局对象的引用之间的顺序。特别是,

对 volatile 对象的写入(volatile write)具有 Release 语义;在指令序列中写入易失性对象之前发生的对全局或静态对象的引用将发生在已编译二进制文件中的易失性写入之前。

对 volatile 对象的读取(volatile read)具有 Acquire 语义;在指令序列中读取易失性内存之后发生的对全局或静态对象的引用将发生在编译二进制文件中的易失性读取之后。

这允许易失性对象用于多线程应用程序中的内存锁定和释放。”

于 2009-11-18T15:37:37.483 回答