【问题标题】:Usage of the C# lock keywordC# lock 关键字的使用
【发布时间】:2011-05-27 21:13:58
【问题描述】:

我对C#锁的理解如下,请帮我验证一下是否正确。

public class TestLock
{
    private object threadLock = new object();
    ...
    public void PrintOne()
    {
        lock (threadLock)
        {
            // SectionOne
        }
    }

    public void PrintTwo()
    {
        lock (threadLock)
        {
            // SectionTwo
        }
    }
    ...
}

案例 I> Thread1 和 Thread2 同时尝试调用 PrintOne。 由于 PrintOne 受到实例锁的保护,在任何时候,只有 一个线程可以独占进入SectionOne。

这是正确的吗?

案例二> Thread1 和 Thread2 同时尝试调用 PrintOne 和 PrintTwo 分别(即 Thread1 调用 PrintOne 和 Thread2 调用 PrintTwo) 由于两个打印方法都被同一个实例锁保护着,所以在任何时候, 只有一个线程可以独占访问 SectionOne 或 SectionTwo,但不能同时访问两者。

这是正确的吗?

【问题讨论】:

  • 总结以下答案:代码仅对每个实例都是线程安全的。当实例共享资源时,@oleski 有正确答案(= 否)
  • 因此 -1 表示不包括共享数据/资源。请编辑。
  • 这是一个有效的观点,但原始问题并没有说明实例是否是分开的,因此,从编写代码/问题的方式来看,可以安全地假设他在谈论针对同一实例进行的调用。所以,我不同意你的反对意见,但这是你的特权。
  • 你不需要在标题中添加标签。这就是标签的用途!

标签: c# multithreading locking


【解决方案1】:
如果您的所有线程都使用类的相同实例,

1 和 2 仅 为真。如果他们使用不同的实例,那么这两种情况都是false

样本

public class TestLock
{
    private  object threadLock = new object();

    public void PrintOne()
    {
        lock (threadLock)
        {
            Console.WriteLine("One");
            var f = File.OpenWrite(@"C:\temp\file.txt"); //same static resource
            f.Close();
        }
    }

    public void PrintTwo()
    {
        lock (threadLock)
        {
            Console.WriteLine("Two");
            var f = File.OpenWrite(@"C:\temp\file.txt"); //same static resource
            f.Close();
        }
    }
}

以及测试代码

static void Main(string[] args)
{
    int caseNumber = 100;

    var threads = new Thread[caseNumber];
    for (int i = 0; i < caseNumber; i++)
    {
        var t = new Thread(() =>
                                {
                                    //create new instance
                                    var testLock = new TestLock();
                                    //for this instance we safe
                                    testLock.PrintOne();
                                    testLock.PrintTwo();
                                });
        t.Start();
        //once created more than one thread, we are unsafe
    }
}

一种可能的解决方案是在锁定对象声明和使用它的方法中添加一个静态关键字。

private  static object threadLock = new object();

更新 konrad.kruczynski 提出的好观点

...“线程安全”也被假定为 语境。例如,我可以采取 您的文件打开代码以及 使用静态锁生成异常 - 只是采取另一个应用程序 领域。因此建议 OP 应该使用系统范围的 Mutex 类或 那样的。因此静态情况 只是推断为实例之一。

【讨论】:

  • 代码是线程安全的每个实例。 OP 提到了一次“实例锁定”,但对实际共享的内容含糊不清。
  • @Henk 我同意,他提到了实例锁,会更新我的答案。 tnx.
  • 锁的性能与锁的争用有多少直接相关。所以你真的不应该使用静态锁,除非你正在修改静态数据,如果你正在使用静态锁,你不应该使用它,除非是为了保护静态数据。
  • @Yaur 在我的示例中,我修改了文件。它只是系统中的一个文件。系统中不能有多个同名文件,因此在这种情况下,我将我的文件视为静态资源。我通过打开它进行修改来编写它。请测试我的代码并自己查看。
  • @oleksii,您的示例在技术上没有任何问题。它突出了在 个实例共享资源时使用实例锁的问题。但是,如果这是一个真实的示例,您可能希望将锁 和两个打印方法设为静态。
【解决方案2】:

案例一:检查✓

案例二:检查✓

不要忘记锁定只是线程同步的一种方式。对于其他用户方法,请阅读:Thread Synchronization

直接来自 MSDN 示例:

public class TestThreading
{
    private System.Object lockThis = new System.Object();

    public void Process()
    {    
        lock (lockThis)
        {
            // Access thread-sensitive resources.
        }
    }    
}

【讨论】:

  • @Teoman 请试试我的示例代码,你会得到一个 IOException,文件被使用。我相信这是一个有效的案例。锁定对象必须是静态的。如果两个线程使用该类的不同实例,就会有两个锁实例,代码就成为线程不安全的。
  • @oleskii:如果同步区域不修改静态资源,则不需要静态锁。您不能在不共享数据的两个上下文之间存在竞争条件。
  • 您的示例不包含任何静态数据,更不用说任何正在修改的静态数据,因此不可能出现与静态数据相关的竞争条件。将同步对象设为静态所获得的只是死锁的机会。现在,如果两个对象共享(非静态)对共享对象的引用,则可能会出现问题 - 但在这种情况下,静态锁对您没有帮助。这个问题最好通过首先不让多个线程修补非线程安全对象来解决。
  • @oleskii:没错,尝试打开已获得排他锁的文件将导致 IOExceptioon。这不是您可以通过使用 lock 语句来防止的事情,因为文件也可能被另一个对象、进程、程序甚至(在网络环境中)计算机锁定。这是一个需要通过适当的异常处理来处理的问题,而不是线程同步。
  • 外部资源和静态字段不是一回事,把两者混为一谈是不明智的。它将导致糟糕的决策,例如,使用仅适用于受限于单个进程的问题(如线程锁)的同步方法来尝试处理与不受单个进程约束的资源相关的并发问题,例如文件。
【解决方案3】:

是的,是的。案例是正确的。

【讨论】:

  • @oleksii:正如你所说,只有每个实例是安全的。然而 OP 写的是关于实例锁的。
  • 我猜@Henk 是有道理的,这个问题在这个特定问题上很模糊
  • @oleksii:是的,但是从上下文中也假设了“线程安全”。例如,我可以获取您的文件打开代码并使用静态锁生成异常 - 只需获取另一个应用程序域。因此建议 OP 应该使用系统范围的 Mutex 类或类似的东西。因此,静态案例仅被推断为实例之一。我认为没有理由拒绝投票。
【解决方案4】:

您的理解是 100% 正确的。因此,例如,如果您想允许分别进入这两个方法,您将需要两个锁。

【讨论】:

  • +1 for "允许分别进入这两种方法,你需要两个锁"
【解决方案5】:

是的,你在这两个方面都是正确的。

【讨论】:

    【解决方案6】:

    这里是基本的(或多或少)

    1) 对实例数据使用实例锁

    public class InstanceOnlyClass{
        private int callCount;
        private object lockObject = new object();
    
        public void CallMe()
        {
            lock(lockObject)
            {
                callCount++;
            }
         }
    }
    

    2) 对静态数据使用静态锁

    public class StaticOnlyClass{
        private int createdObjects;
        private static object staticLockObject = new object();
    
        public StaticOnlyClass()
        {
            lock(staticLockObject)
            {
                createdObjects++;
            }
        }
    }
    

    3) 如果您要保护静态和实例数据,请使用单独的静态和实例锁

    public class StaticAndInstanceClass{
        private int createdObjects;
    
        private static object staticLockObject = new object();
    
        private int callCount;
    
        private object lockObject = new object();
    
        public StaticAndInstanceClass()
        {
            lock(staticLockObject)
            {
                createdObjects++;
            }
        }
    
        public void CallMe()
        {
            lock(lockObject)
            {
                callCount++;
            }
         }
    }
    

    基于此,如果您正在访问实例数据,则您的代码很好,但如果您正在修改静态数据,则不安全

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-23
      • 2013-02-08
      • 1970-01-01
      相关资源
      最近更新 更多