开发者

Threading and un-safe variables

开发者 https://www.devze.com 2022-12-11 15:03 出处:网络
I have code listed here: Threading and Sockets. The answer to that question was to modify isListening with volatile. As I remarked, that modifier allowed me to access the variable from another thread

I have code listed here: Threading and Sockets.

The answer to that question was to modify isListening with volatile. As I remarked, that modifier allowed me to access the variable from another thread. After reading MSDN, I realized that I was reading isListening from the following newly created thread process.

So, my questions now:

开发者_C百科
  • Is volatile the preferred method,since I am basically making a non-thread safe request on a variable? I have read about the Interlocked class and wondered if this was something that would be better to use in my code. Interlocked looks similar to what lock(myObj) is doing - but with a little more 'flair' and control. I do know that simply applying a lock(myObj) code block around isListening did not work.

  • Should I implement the Interlocked class?

Thank you for your time and responses.


If all you are doing is reading and writing a variable across multiple threads in C#, then you do not have to worry about synchronizing access to (locking) that variable providing its type is bool, char, byte, sbyte, short, ushort, int, uint, float, and reference types. See here for details.

In the example from your other post, the reason you have to mark the field as volatile is to ensure that it is not subject to compiler optimizations and that the most current value is present in the field at all times. See here for details on the volatile keyword. Doing this allows that field to be read and written across threads without having to lock (synchronize access to) it. But keep in mind, the volatile keyword can only be used for your field because it is of type bool. Had it been a double, for example, the volatile keyword wouldn't work, and you'd have to use a lock.

The Interlocked class is used for a specialized purpose, namely incrementing, decrementing, and exchanging values of (typically) numeric types. These operations are not atomic. For example, if you are incrementing a value in one thread and trying to read the resulting value in another thread, you would normally have to lock the variable to prevent reading intermediate results. The Interlocked class simply provides some convenience functions so you don't have to lock the variable yourself while the increment operation is performed.

What you are doing with the isListening flag does not require use of the Interlocked class. Marking the field as volatile is sufficient.


Edit due to lunchtime rushed answer..

The lock statement used in your previous code is locking an object instance that is created in the scope of a method so it will have no effect on another thread calling into the same method. Each thread must be able to lock the same instance of an object in order to synchronise access to the given block of code. One way to do this (depending on the semantics you require) is to make the locking object a private static variable of the class that it is used in. This will allow multiple instances of a given object to synchronise access to a block of code or a single shared resource. If synchronisation is required for individual instances of an object or a resource that is instance specific then static should be emitted.

Volatile doesn't guarantee that reads or writes to the given variable will be atomic amongst different threads. It is a compiler hint to preserve ordering of instructions and prevents the variable from being cached inside a register. In general unless you are working on something extremely performance sensitive (low locking / lock free algorithms, data structures etc.) or really know you are doing then I would opt for using Interlocked. The performance difference between using volatile / interlocked / lock in most applications will be neglible, so if you are unsure its best to use what ever gives you the safest guarantee (read Joe Duffy's blog & book).

For example using volatile in the example below is not thread safe and the incremented counter does not reach 10,000,000 (when I ran the test it reached 8848450) . This is because volatile only guarentees reading the latest value (e.g. not cached from a register for example). When using interlocked the operation is thread safe and the counter does reach 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();
    }
}

If you search for 'interlocked volatile' you will find a number of answers to your question. The one below for example addresses your question:

A simple example below shows

Volatile vs. Interlocked vs. lock


"One way to do this is to make the locking object a private static variable of the class that it is used in." Why should it be static? You can access the same function from multiple threads as long as they work on different object. I am not saying that it would not work, but would seriously slow the speed of the application without any advantages. Or am I missing something?

And here is what MSDN says about volatiles: "Also, when optimizing, the compiler must maintain ordering among references to volatile objects as well as references to other global objects. In particular,

A write to a volatile object (volatile write) has Release semantics; a reference to a global or static object that occurs before a write to a volatile object in the instruction sequence will occur before that volatile write in the compiled binary.

A read of a volatile object (volatile read) has Acquire semantics; a reference to a global or static object that occurs after a read of volatile memory in the instruction sequence will occur after that volatile read in the compiled binary.

This allows volatile objects to be used for memory locks and releases in multithreaded applications."

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号