C#多线程和同步

4

我有一个私有的静态字段,用于同步(锁定)。现在我有两个函数,我不希望它们同时执行。所以我做了这个:

public class Synchronization
{
    private static object _lock = new object();

    public void MethodA()
    {
        lock (_lock)
        {
            Console.WriteLine("I shouldn't execute with MethodB");
        }
    }

    public void MethodB()
    {
        lock (_lock)
        {
            Console.WriteLine("I shouldn't execute with MethodA");
        }
    }
}

我知道锁定一个对象可以防止单个函数的并行执行,但是如果我在不同的方法中同时使用相同的锁对象,是否也会起到同样的作用?简单来说,就是在另一个函数中已经有一个锁定的对象,其他线程能否获取该对象的锁?


2
为什么不自己试一试呢? - user57508
3
请注意,这样做会同步类的每个实例。如果不想这样做,请删除锁对象中的 "static" 部分。 - Willem van Rumpt
3
我理解你的意思是:@Andreas,我说的是理论和实践。验证机器翻译代码也非常困难。因此,你的建议很幼稚。 - Tim Lloyd
3
“只是给自己尝试一下”并没有告诉提问者如何验证他的说法。如果提问者在解决问题的语义方面遇到困难,那么你应该能够看到你的建议存在的明显问题。这是一个随意的评论。 - Tim Lloyd
2
@Andreas 或许你可以考虑开一个新问题:“如何通过单元测试验证多线程代码?”并自己回答。这是允许的,也会很有用。验证多线程代码是困难的。 - Tim Lloyd
显示剩余6条评论
5个回答

5
一次只有一个线程可以获取锁,因此该状态对单个锁实例上的所有线程是排他的。因此,在您的示例中,对于类Synchronization的所有实例,每次只能执行一个方法体,因为您的锁是静态的。如果您想要每个类实例的锁定,则不要将锁对象标记为静态。
您对同步的假设是正确的。
请注意,您应该将锁对象标记为“readonly”以获得完全可靠的解决方案。因为代码现在的情况是,锁对象可能会被重新分配,从而破坏锁定语义,例如:
public class Synchronization
{
    private static object _lock = new object();

    public void MethodA()
    {
        lock (_lock)
        {
            Console.WriteLine("I shouldn't execute with MethodB");
        }
    }

    public void MethodB()
    {
        //This shouldn't be allowed!
        _lock = new object();

        lock (_lock)
        {
            Console.WriteLine("I shouldn't execute with MethodA");
        }
    }
}

锁定对象应标记为只读,即:

private static readonly object _lock = new object();

你应该增强“仅限于任何给定实例的一个方法体”,因为你正在处理静态锁 :) - user57508
@Andreas,我已经详细说明了静态锁的含义。 - Tim Lloyd

2

我相信你正确地阅读了MSDN上的Lock语句


1

首先,_lock 不应该是静态的。否则你想让多个实例相互锁定吗?其次,在一个类中应该只有一个同步方法。更重要的是,你应该避免在类中的同步方法之间存在依赖关系。否则,你会冒着调用者错误执行和出现意外行为的风险。

例如,考虑以下代码:

class Synchronized
{
    object lockObj = new object();
    int counter = 100;

    public void Decrement()
    {
        lock (this.lockObj)
        {
            this.counter--;
        }
    }

    public int IsZero()
    {
        lock (this.lockObj)
        {
            return this.counter == 0;
        }
    }
}

现在,人们可以如何使用共享的同步实例呢?

像这样使用它

while (!synchronized.IsZero())
{
    synchronized.Decrement();
}

现在线程1调用Decrement,计数器变为0,立即线程2调用Decrement,因为它在Decrement方法中等待锁定,而不是在IsZero方法中。计数器现在为-1,循环无限。

问题不在于锁定机制编写有误,而在于调用者没有正确使用它。如果您的Synchronized类只公开了一个同步方法,那么您不会让程序员盲目地相信它是安全的。

应该像这样:

class Synchronized
{
    object lockObj = new object();
    int counter = 100;

    public bool IfNotZeroDecrement()
    {
        lock (this.lockObj)
        {
            if (this.counter > 0)
                this.counter--;

            return this.counter > 0;
        }
    }    
}

/// Usage:
while (synchronized.IfZeroDecrement())
{
}

我不同意在一个类中只有一个同步方法。你的例子只是实现得很差。如果不能减小到0以下很重要,那么你的减量应该处理这个问题。 - btlog
1
@btlog 在这个简单的例子中很明显,但在更复杂的情况下可能不那么明显。你不应该把所有责任都放在代码使用者身上,否则以后可能会遇到严重的问题,而这些问题很难调试。安全、干净地编写代码,每个人都会从中受益。 - František Žiačik
推荐阅读此(和其他)相关文章:http://blog.objectmentor.com/articles/2008/04/08/clean-code-whew “方法之间的依赖关系可能会破坏并发代码” - František Žiačik

1
你做得很对。你创建了两个临界区,它们不会同时进入。
所以MethodA和MethodB不会同时“活动”。并且同时只有一个MethodA(和MethodB)是活动的。
这对于你创建的所有对象都有效。我的意思是:从任何对象中只能有一个线程在MethodA或MethodB中执行。如果你想要锁定仅限于一个对象内部,你可以将_lock对象设置为非静态的。

1

锁是基于锁定目标对象而不是锁定语句所在的方法来授予的。因此,在您的情况下,多个线程可能会进入各种方法,但一次只有一个线程能够执行在lock语句内的任何代码。


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