首页 > 解决方案 > 两个对象是否需要两个锁?

问题描述

如果我有两个共享资源,这些资源会在它们自己的同时运行的单独任务中更新。第二个任务从第一个任务检查共享资源的状态,然后更新它自己的共享资源。在其中一项任务完成后,我会检查两个共享资源的状态。我需要两个单独的锁来使这个线程安全还是一个就足够了?例如:

private void example()
{
    object lockObj = new object();

    int x = 0;
    int y =0;

    List<Task> tasks = new List<Task>();

    Task task1 = Task.Factory.StartNew(() =>
    {
        try
        {
            int z = doComputation()
            resultofComputation = z;
        }
        catch
        {
            resultofComputation=-1;
        }
        finally
        {
            lock(lockObj)
            {
                x = resultofComputation;
            }
        }
            
    }

    tasks.Add(workTask);

    Task task2 = Task.Factory.StartNew(() =>
    {
        try
        {
            checkOnstatusofThing(ref x);
            lock(lockObj)
            {
                y +=x;
            }
        }
        finally
        {

        }
    }

    Task.WaitAny(tasks.ToArray());
    if(x =3 || y ==9)
    {
        return -1;
    }
    return 0;
}

checkOnstatusofThing(ref int x)
{
    if(x == 5)
    {
        return;
    }
}

标签: c#thread-safetylocking

解决方案


使用单个锁对象是安全的选择。您定义构成共享状态的变量,并在每次从任何线程写入和读取这些变量时都小心翼翼地使用锁。如果您这样做,那么您的应用程序的正确性将很容易证明(按照多线程的标准很容易,这本来就很困难)。

为了尽量减少对锁的争用,您应该尽快释放它。您应该避免在持有锁时做任何与共享状态无关的事情。例如,如果您必须使用共享变量作为参数调用方法,请获取变量的快照,并将快照用作参数。

int snapshot;
lock (lockObj)
{
    snapshot = sharedState;
}
MethodCall(snapshot);

如果您遵循这个建议,那么锁的争用应该是最小的,并且应该不会显着影响您的应用程序的性能。但是,如果您的基准测试表明锁的争用过多,那么您可以考虑引入多个锁以增加锁定方案的粒度并减少争用。请注意,此更改将大大增加您的应用程序的复杂性。死锁将成为可能,因此您必须熟悉像五个用餐哲学家这样的经典同步问题及其解决方案。


推荐阅读