C#锁和新手多线程问题

7
一些关于在.NET中使用多线程的新手问题,我认为这将有助于加强我试图吸收的一些概念 - 我已经阅读了几篇关于多线程的材料(包括Albahari电子书),但感觉我只需要确认一些问题来帮助巩固这些概念。
1.锁定范围保护共享代码区域 - 假设有一个线程执行一个方法,在循环中递增一个简单的整数变量x - 然而,这不会保护可能在另一个线程上的另一个方法中也会改变变量x的代码...
由于这是两个不同的代码区域,可能影响相同的变量,我们是否通过使用相同的锁变量锁定两个代码区域来解决这个问题? 如果您使用不同的锁变量锁定了两个代码区域,那么这将无法保护该变量,对吗?
2.进一步说明这个例子,如果由于某种原因,一个方法中的代码进入了一些无限循环并永远不释放锁变量 - 另一个方法中的第二个代码区域如何检测到这一点呢?
3.选择锁变量如何影响锁的行为?我已经阅读了许多关于这个主题的帖子,但似乎从来找不到一个明确的答案 - 在某些情况下,人们明确使用一个对象变量专门用于此目的,其他时候人们使用lock(这) ,最后有时我看到人们使用类型对象。
不同的锁变量选择如何影响锁的行为/范围,什么情况下使用其中之一是有意义的?
4.假设您有一个包装在类中的哈希表,公开添加,删除,获取和某种计算方法(例如每个对象表示一个数量,该方法对每个值求和),并且所有这些方法都被锁定 - 但是,一旦将对该集合中对象的引用提供给其他代码并传递应用程序,该对象(而不是哈希表)现在将超出该类方法周围的锁定范围... 那么,如何保护对从哈希表中取出的这些实际对象的访问/更新,这可能会干扰Calculate方法?
感谢提供任何启发,这将有助于加强我的理解 - 谢谢!

1
请每个问题只提一个问题。 - John Saunders
4
@JohnSaunders - 通常是这样的,但这是一系列(强烈)相关的问题。 - H H
1
请不要关闭此问题。这个问题很有用,而且有很多有用的答案。子问题是相关联的。 - usr
@usr:这不是 [so] 的工作方式。如果OP喜欢,应该单独提出问题,并包含所有相关链接,但它们需要成为独立的问题。这不是一个讨论论坛。 - John Saunders
2个回答

2

1) 是的

2) 这是死锁

3) 你想要阻塞的代码部分是类的实现细节。使用lock(this)lock(this.GetType())暴露锁对象会引发麻烦,因为现在外部代码可以锁定相同的对象并意外或恶意地阻止你的代码。锁对象应该是私有的。

4) 不太清楚你的意思,你肯定不希望直接暴露Hashtable。只需将其保留为类的私有字段,封装它即可。

然而,随着公共方法和属性数量的增加,你能够安全地将类暴露给客户端代码并使用线程的可能性会迅速降低。当客户端代码持有属性值时,细粒度锁定会创建许多线程竞争的机会。例如,返回的Count属性值。当它在for循环中使用时,Count属性可能已经改变。只有最仔细的设计才能避免这些陷阱,这是一个严重的头痛。

此外,细粒度锁定非常低效,因为它不可避免地在代码的最内部进行。锁定不是那么昂贵,大约100个CPU周期,但它很快就会累加。特别是当类对象实际上没有在多个线程中使用时,浪费了很多努力。

然后你别无选择,只能声明你的类不支持多线程,并且客户端代码需要以线程安全的方式使用它。这也是许多.NET类不支持多线程的核心原因。这是使线程变得如此难以正确处理的最大原因,最不可能正确处理它的程序员负责处理最困难的事情。


1

1) 你是正确的。必须使用相同的锁对象来保护两个不同的代码区域,例如增加变量x。

2) 这被称为死锁,是多线程编程中的难点之一。有一些算法可以用来防止死锁,比如银行家算法。

3) 有些语言使锁定变得容易,例如在.Net中,你只需创建一个对象并将其用作共享锁即可。这对于在给定进程内同步代码非常有用。Lock(this)仅适用于所涉及的对象。但是请尽量避免这种情况,而是创建一个私有对象并使用它。Lock(this)可能会导致死锁情况。底层的锁对象可能只是一个临界区的包装器。如果您想要跨不同进程保护资源,则需要更重的命名互斥体,这需要对内核对象进行锁定,并且很昂贵,因此除非必须,否则不要使用。

4)你需要确保那里也应用了锁定。但是当人们调用此引用上的方法时,他们调用使用同步的方法。


问题在于,如果 Get 方法返回底层集合中的任意对象(例如 .NET 中 Object 类的实例)的引用/指针,一旦封装存储该对象的集合的类返回对其的引用,但不是对象本身,例如从同步的 Get 方法返回一个引用并传递到应用程序的其他线程不安全代码区域后,如何保护此对象?我想除非该任意对象本身具有同步方法以保护其自身状态,否则这是不可能的,正确吗? - blue18hutthutt
1
如果您从Get方法返回一个应该受到保护的对象,那么您将会遇到麻烦。良好的设计将确保您不会仅仅“泄漏”(返回)应该受到保护的方法内部的内容。因为这样一来,调用者就可以操纵这个对象。现在您有两段代码可以更改此对象,而只有一个受到保护。您的代码不再是线程安全的。 - Science_Fiction

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接