锁定正在同步的对象还是使用专用的锁定对象?

时间:2016-07-29 08:11:05

标签: c#

据我从同事和网络上了解到,锁定正在同步的对象是不好的做法,但我不明白为什么?

以下类应该将设置加载到字典中,并且它还有一种方法来检索设置。

public class TingbogSettingService : ITingbogSettingService
{
    private readonly ISettingRepository _settingRepository;
    private readonly ICentralLog _centralLog;
    private Dictionary<string, ISetting> _settingDictionary = new Dictionary<string, ISetting>();


    public TingbogSettingService(ISettingRepository settingRepository, ICentralLog centralLog)
    {
        _settingRepository = settingRepository;
        _centralLog = centralLog;
    }

    public ISetting GetSetting(string settingName)
    {
        ISetting setting;
        if (!_settingDictionary.TryGetValue(settingName, out setting))
        {
            return null;
        }
        return setting;
    }

    public void LoadSettings()
    {
        var settings = _settingRepository.LoadSettings();
        try
        {
            lock (_settingDictionary)
            {
                _settingDictionary = settings.ToDictionary(x => x.SettingName);
            }                
        }
        catch (Exception ex)
        {
            _centralLog.Log(Targets.Database, LogType.Error, $"LoadSettings error: Could not load the settings", new List<Exception>() { ex });
        }
    }
}

在LoadSettings函数期间,我想锁定_settingDictionary,以便阻止GetSetting,直到加载新设置。

我应该使用专用的锁定对象吗?

例如:

private static readonly object m_Lock = new object();
…
lock (m_Lock)

修改
我认为lock(_settingDictionary)会锁定_settingDictionary本身,但我现在意识到他的情况并非如此。我想要的是阻止其他线程访问_settingDictionary,直到加载新设置(LoadSettings方法完成)。由于只有1个线程正在更新_settingDictionary,我想我根本不需要锁定。

至于结束问题 - 之前已经提出了类似的问题,是的,但情况并不相同。我从你的答案中学到了很难在你们中间挑选一个胜利者。

5 个答案:

答案 0 :(得分:5)

这是一个相当广泛的主题,但让我关注代码中的一个主要问题:_settingDictionary 更改

您不会锁定该字段,您可以锁定该实例。这意味着,当您锁定_settingDictionary,然后更改 _settingDictionary时,您就无法阻止任何并发访问 - 任何人都可以锁定 new _settingDictionary

lock不会阻止访问您要锁定的对象。如果需要同步,则必须同步对该对象的所有访问权限,包括_settingDictionary.TryGetValueDictionary不是线程安全的。

你应该锁定的主要指南是这样的:

  • 锁定对象对于锁定器是私有的 - 如果它不是私有的,则其他一些类可能会对您的对象进行锁定,这可能会导致死锁。
  • 该字段应为readonly - 这不是严格的要求,但它使事情变得更容易。重点是你不能锁定一个在锁定时可能会改变的对象;其他人试图同时取得锁定成功
  • 锁定对象是一种引用类型 - 这种情况不言而喻,但你无法锁定,例如:一个int字段,因为当你试图锁定它时它是盒装的 - 实际上,这与前一点相同 - 每个人都锁定它们自己的对象实例,从而消除了所有同步。

强制性免责声明:多线程 hard 。真的很难。确保您了解正在发生的事情以及可能发生的事情。您编写的任何多线程代码必须以正确的方式编写,首先是。对于C#/ .NET中的多线程,http://www.albahari.com/threading/是一个很好的启动器。

答案 1 :(得分:3)

当您锁定类的外部用户可以访问的对象时,您所谈论的问题就会发生 - 最常见的是对象本身,即lock (this)

如果您的代码锁定在this而不是_settingDictionary,则其他人可能会对您的代码进行死锁,如下所示:

TingbogSettingService svc = ...
lock (svc) {
    Task.Run(() => {
        svc.LoadSettings();
    });
}

当您锁定私有对象(例如_settingDictionary)时,会避免上述有害影响,因为代码外的任何人都无法锁定同一个对象。

注意:在代码中使用锁定不会使其成为线程安全的,因为GetSetting方法在读取时不会锁定_settingDictionary。此外,您在_settingDictionary内重新分配lock的事实使得锁定无关紧要,因为在重新分配之后,另一个线程可以在锁中输入受保护的部分。

答案 2 :(得分:3)

对此没有“正确”或“错误”的答案,但有一些指导方针和一些事项需要注意。

首先,有很多人认为微软应该永远不会允许锁定任意对象。相反,他们应该将锁定功能封装到特定的类中,并避免其他所有对象的潜在开销。

允许锁定任意对象的最大问题是,如果锁定一个对第三方代码公开可用的对象,则无法控制其他人可能锁定同一对象。你可以把你的代码编写到这封信中,点击每一个I,它仍然会导致死锁,因为其他一些第三方代码会锁定你控制之外的同一个对象。

因此,仅此一点就足以说明“不要永远锁定您公开提供的对象”。

但是,如果要同步访问的对象是私有的,该怎么办?那么,它变得更加模糊。大概你可以完全控制你自己编写的代码,因此如果你锁定字典,作为一个例子,它就可以正常工作。

不过,我的建议是始终设置一个单独的对象来锁定,养成这个习惯,如果你后来决定将以前的私人对象暴露给公众并忘记,那么你就不会轻易犯错误将锁定语义与它分开。

最简单的锁定对象就是object

private readonly object _SomethingSomethingLock = new object();

也知道,虽然我认为你已经这样做了,锁定一个对象不会“锁定对象”。任何其他不打扰锁的代码仍可以正常访问该对象。

这也是我刚刚注意到的关于你的代码的事情。

执行此操作时:

lock (x)

您没有锁定x,而是锁定<{>}对象,x在锁定时指向 。< / p>

查看此代码时,这很重要:

lock (_settingDictionary)
{
    _settingDictionary = settings.ToDictionary(x => x.SettingName);
}                

这里有两个对象:

  1. settingDictionary
  2. lock (_settingDictionary)引用的词典
  3. .ToDictionary(...)返回的新词典
  4. 您锁定了第一个对象,但不在第二个上。这是另一种情况,拥有一个专用的锁定对象不仅有意义,而且也是正确的,因为上面的代码在我看来是错误的。

答案 3 :(得分:1)

最好使用未被代码块修改或在某些其他方法中用于其他目的的专用对象。这样,对象只有一个责任,因此您不要将它的用法混合为同步对象,可能在某个时候将其设置为null或者由另一个方法重新初始化。

lock (_settingDictionary)不锁定()之间指定的字典,它使用_settingDictionary作为同步对象锁定下一个代码块(要知道该块是否已输入左侧通过另一个线程在该对象上设置一些标志)。

答案 4 :(得分:1)

你可以<table id="tableID"> <tr> <td> <input name="name" class="compulsory1" type="text" value="1" /> </td> <td> <input name="name1" class="compulsory1" type="text" value="2" /> </td> <td> <input name="name2" class="compulsory1" type="text" value="3" /> </td> <td> <script type="text/javascript"> var tdsCompulsory = document.getElementsByClassName('compulsory1')[0].value; var cData = []; sum = 0; for(var i in tdsCompulsory){ if(typeof tdsCompulsory[i].textContent != 'undefined') cData.push(tdsCompulsory[i].textContent); } console.log(cData); for(var i in cData){ sum +=parseInt(cData[i]); } alert (sum); </script> </td> </tr> </table>

  • 专用非静态对象lock
  • 专用静态对象(您的示例):private readonly object m_Lock = new object();
  • 对象本身private static readonly object m_Lock = new object();
  • lock (_settingDictionary)this ...

前两个是好的,但实际上是不同的。锁定静态对象意味着锁定在所有类的实例之间共享。锁定非静态对象意味着对于每个类的实例,锁是不同的。

第三个选项是OK,它与第一个选项相同。唯一的区别是对象不是只读的(使用只读字段稍微好一点,因为你确保它不会改变)。

由于各种原因,最后一个选项是错误选项,请参阅Why is lock(this) {...} bad?

因此,请小心锁定的内容,您的示例在初始代码使用非静态对象时使用静态对象。这些是非常不同的用例。