lock(){}锁定资源,还是锁定一段代码?

时间:2009-04-15 18:54:03

标签: c# multithreading concurrency

我仍然感到困惑......当我们写下这样的事情时:

Object o = new Object();
var resource = new Dictionary<int , SomeclassReference>();

...并且有两个代码块可以在访问o ...

时锁定resource
//Code one
lock(o)
{
  // read from resource    
}

//Code two
lock(o)
{
  // write to resource
}

现在,如果我有两个线程,一个线程执行的代码从resource读取而另一个写入它,我想锁定resource,这样当它被读取时,写入者将不得不等待(反之亦然 - 如果它被写入,读者将不得不等待)。锁构造能帮助我吗? ......或者我应该使用其他东西吗?

(我在本例中使用Dictionary,但可以是任何内容)

我特别关注两种情况:

  1. 两个线程试图执行相同的代码行
  2. 尝试使用同一资源的两个线程
  3. lock会在这两种情况下提供帮助吗?

7 个答案:

答案 0 :(得分:12)

其他大多数答案都会解决您的代码示例,因此我会尝试在标题中回答您的问题。

锁实际上只是一个标记。拥有令牌的人可以参加舞台。因此,您锁定的对象与您尝试同步的资源没有明确的连接。只要所有读者/作者都同意相同的标记,它就可以是任何东西。

当试图锁定某个对象时(即通过调用某个对象上的Monitor.Enter),运行时会检查该锁是否已被某个线程持有。如果是这种情况,则尝试锁定的线程被挂起,否则它将获取锁定并继续执行。

当持有锁的线程退出锁定范围(即调用Monitor.Exit)时,锁被释放,任何等待的线程现在都可以获得锁。

最后关于锁的几点要记住:

  • 只要您需要锁定,但不再锁定。
  • 如果您使用Monitor.Enter/Exit代替lock关键字,请确保将Exit的号码置于finally块中,以便即使在案件中也会释放锁定一个例外。
  • 公开对象锁定会使得更难以概述谁锁定以及何时锁定。应该封装理想的同步操作。

答案 1 :(得分:5)

是的,使用锁是正确的方法。您可以锁定任何对象,但正如其他答案所述,锁定资源本身可能是最简单和最安全的。

但是,您可能希望使用读/写锁对而不是单个锁,以减少并发开销。

这样做的理由是,如果你只有一个线程写入,但是几个线程读取,你不希望读取操作阻止其他读操作,而只是读取块写入,反之亦然。

现在,我更像是一个java人,所以你必须改变语法并挖掘一些文档以在C#中应用它,但是rw-locks是Java中标准并发包的一部分,所以你可以写点如下:

public class ThreadSafeResource<T> implements Resource<T> {
    private final Lock rlock;
    private final Lock wlock;
    private final Resource res;

    public ThreadSafeResource(Resource<T> res) {
        this.res = res;
        ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
        this.rlock = rwl.readLock();
        this.wlock = rwl.writeLock();
    }

    public T read() {
        rlock.lock();
        try { return res.read(); }
        finally { rlock.unlock(); }
    }

    public T write(T t) {
        wlock.lock();
        try { return res.write(t); }
        finally { wlock.unlock(); }
    }
}

如果有人能提出C#代码示例......

答案 2 :(得分:2)

这两个代码块都锁定在这里。如果线程1锁定第一个块,而线程2尝试进入第二个块,则必须等待。

答案 3 :(得分:2)

lock(o){...} 语句编译为:

Monitor.Enter(o)
try { ... }
finally { Monitor.Exit(o) }

如果另一个线程已经调用了,那么对Monitor.Enter()的调用将阻塞该线程。只有在其他线程在对象上调用Monitor.Exit()之后才会解除阻塞。

答案 4 :(得分:2)

  

在两种情况下都会锁定帮助吗?   是。

     

lock(){}会锁定资源吗?   它锁定了一段代码?

lock(o)
{
  // read from resource    
}

的语法糖
Monitor.Enter(o);
try
{
  // read from resource 
}
finally
{
  Monitor.Exit(o);
}

Monitor 类包含用于同步对代码块的访问的对象集合。 对于每个同步对象,监控保留:

  1. 对当前持有同步对象锁定的线程的引用;即轮到这个线程执行。
  2. “就绪”队列 - 在为此同步对象提供锁定之前阻塞的线程列表。
  3. “等待”队列 - 阻塞的线程列表,直到它们被 Monitor.Pulse() Monitor.PulseAll()
  4. 因此,当一个线程调用lock(o)时,它被置于o的就绪队列中,直到它被锁定为o,此时它继续执行其代码。

答案 5 :(得分:0)

假设您只涉及一个流程,这应该可行。如果您希望在多个流程中工作,则需要使用“Mutex”。

哦,而且“o”对象应该是一个单例或者需要锁定的所有区域,因为真正被锁定的是该对象,如果你创建一个新对象,那么新的对象将不会被锁定然而。

答案 6 :(得分:0)

实施它的方式是一种可接受的方式来完成您需要做的事情。改进你的方法的一种方法是在字典本身上使用lock(),而不是用于同步字典的第二个对象。这样,资源本身不会传递额外的对象,而是跟踪它自己的监视器上是否存在锁定。

在某些情况下,使用单独的对象可能很有用,例如同步对外部资源的访问,但在这种情况下,它会产生开销。