Interlocked.exchange作为锁

时间:2014-12-19 15:03:52

标签: c# .net synchronization interlocked

我正在尝试使用Interlocked.Exchange为某些对象初始化函数创建线程安全锁。请考虑以下代码。我想确保if与替换while时的做法相同。我问的原因是代码是否在set消息之前收到退出消息时反复运行。我只是想确认这只是一个gui事情,因为退出时的状态似乎总是正确的。

class Program
{
    private static void Main(string[] args)
    {
        Thread thread1 = new Thread(new ThreadStart(() => InterlockedCheck("1")));
        Thread thread2 = new Thread(new ThreadStart(() => InterlockedCheck("2")));
        Thread thread3 = new Thread(new ThreadStart(() => InterlockedCheck("3"))); 
        Thread thread4 = new Thread(new ThreadStart(() => InterlockedCheck("4")));
        thread4.Start();
        thread1.Start();
        thread2.Start();
        thread3.Start();

        Console.ReadKey();
    }

    const int NOTCALLED = 0;
    const int CALLED = 1;
    static int _state = NOTCALLED;
    //...
    static void InterlockedCheck(string thread)
    {
        Console.WriteLine("Enter thread [{0}], state [{1}]", thread, _state);

        //while (Interlocked.Exchange(ref _state, CALLED) == NOTCALLED)
        if (Interlocked.Exchange(ref _state, CALLED) == NOTCALLED)
        {
            Console.WriteLine("Setting state on T[{0}], state[{1}]", thread, _state);
        }

        Console.WriteLine("Exit from thread [{0}] state[{1}]", thread, _state);
    }
}

2 个答案:

答案 0 :(得分:1)

我不会称之为lock,因为它只能使用一次,但是如果你假设if范围内的语句只执行一次,即使同时从多个线程调用InterlockedCheck

这是因为您从NOTCALLED开始并且仅使用原子CALLED设置Interlocked.Exchange。只有第一个电话才会返回NOTCALLED,而所有后续电话都会返回CALLED

一个更好(更简单)的解决方案是使用.Net的Lazy类,它非常适合初始化:

static Lazy<ExpensiveInstance> _lazy = new Lazy<ExpensiveInstance>(Initialization, LazyThreadSafetyMode.ExecutionAndPublication); 

您可以使用_lazy.Value检索结果,并查询它是否是使用_lazy.IsValueCreated创建的。 Initialization不会运行,直到需要且不超过一次。

答案 1 :(得分:0)

我不明白为什么你在这里使用Interlocked而不是更容易理解的lock语句。就个人而言,我会建议后者。

无论哪种方式,您在“设置”消息之前看到一个或多个“退出”消息的原因是由于线程调度。即使命中Interlocked的第一个线程将始终执行“set”操作,该线程可能会在它有机会执行操作之前被抢占,允许其他线程发出其“退出”消息第一

请注意,根据您的确切需求,使用if与使用while循环相同,很可能无法实现您想要的效果。即命中if的第一个线程是将值设置为CALLED值,因此任何其他线程都将继续运行。如果您真的想在这里初始化某些东西,您可能希望其他线程等待实际执行初始化代码的线程,以便所有线程可以继续知道初始化状态是有效的。

我确实认为这是一个好主意(即对用户不那么混淆,更重要的是,如果真实代码比你显示的更复杂,更有可能产生正确的结果)如果你同步整个方法。

使用Interlocked来实现这一点要比现在的代码复杂得多。它将涉及一个循环和至少一个额外的状态值。但是使用lock语句,它简单易读。它看起来更像是这样:

const int NOTCALLED = 0;
const int CALLED = 1;
static int _state = NOTCALLED;
static readonly object _lock = new object();
//...
static void InterlockedCheck(string thread)
{
    lock (_lock)
    {
        Console.WriteLine("Enter thread [{0}], state [{1}]", thread, _state);

        if (_state == NOTCALLED)
        {
            Console.WriteLine("Setting state on T[{0}], state[{1}]", thread, _state);
            _state = CALLED;
        }

        Console.WriteLine("Exit from thread [{0}] state[{1}]", thread, _state);
    }
}

这样,获取锁的第一个线程可以在任何其他线程执行之前执行其代码的所有,特别是它确保第一个线程中的“set”操作发生在之前任何其他线程中的“退出”操作。