# 10.3 什么时候使用锁？

很明显，锁限制了并发性，也限制了性能。那这带来了一个问题，什么时候才必须要加锁呢？我这里会给你们一个非常保守同时也是非常简单的规则：如果两个进程访问了一个共享的数据结构，并且其中一个进程会更新共享的数据结构，那么就需要对于这个共享的数据结构加锁。

![](/files/-MPHFsAyq6JFa34zETh1)

这是个保守的规则，如果一个数据结构可以被多个进程访问，其中一个进程会更新这个数据，那么可能会产生race condition，应该使用锁来确保race condition不会发生。

但是同时，这条规则某种程度上来说又太过严格了。如果有两个进程共享一个数据结构，并且其中一个进程会更新这个数据结构，在某些场合不加锁也可以正常工作。不加锁的程序通常称为lock-free program，不加锁的目的是为了获得更好的性能和并发度，不过lock-free program比带锁的程序更加复杂一些。这节课的大部分时间我们还是会考虑如何使用锁来控制共享的数据，因为这已经足够复杂了，很多时候就算直接使用锁也不是那么的直观。

矛盾的是，有时候这个规则太过严格，而有时候这个规则又太过宽松了。除了共享的数据，在一些其他场合也需要锁，例如对于printf，如果我们将一个字符串传递给它，XV6会尝试原子性的将整个字符串输出，而不是与其他进程的printf交织输出。尽管这里没有共享的数据结构，但在这里锁仍然很有用处，因为我们想要printf的输出也是序列化的。

所以，这条规则并不完美，但是它已经是一个足够好的指导准则。

> 学生提问：有没有可能两个进程同时acquire锁，然后同时修改数据？
>
> Franz教授：不会的，对于锁来说不可能同时被两个进程acquire，我们之后会看到acquire是如何实现的，现在从acquire的说明来看，任何时间最多只能有一个进程持有锁。

因为有了race condition，所以需要锁。我们之前在kfree函数中构造的race condition是很容易被识别到的，实际上如果你使用race detection工具，就可以立即找到它。但是对于一些更复杂的场景，就不是那么容易探测到race condition。

那么我们能通过自动的创建锁来自动避免race condition吗？如果按照刚刚的简单规则，一旦我们有了一个共享的数据结构，任何操作这个共享数据结构都需要获取锁，那么对于XV6来说，每个结构体都需要自带一个锁，当我们对于结构体做任何操作的时候，会自动获取锁。

可是如果我们这样做的话，结果就太过严格了，所以不能自动加锁。接下来看一个具体的例子。

![](/files/-MPJLWRpCi_6xZDI6_4F)

假设我们有一个对于rename的调用，这个调用会将文件从一个目录移到另一个目录，我们现在将文件d1/x移到文件d2/y。

如果我们按照前面说的，对数据结构自动加锁。现在我们有两个目录对象，一个是d1，另一个是d2，那么我们会先对d1加锁，删除x，之后再释放对于d1的锁；之后我们会对d2加锁，增加y，之后再释放d2的锁。这是我们在使用自动加锁之后的一个假设的场景。

![](/files/-MPMbipXO98admOpvsPt)

在这个例子中，我们会有错误的结果，那么为什么这是一个有问题的场景呢？为什么这个场景不能正常工作？

在我们完成了第一步，也就是删除了d1下的x文件，但是还没有执行第二步，也就是创建d2下的y文件时。其他的进程会看到什么样的结果？是的，其他的进程会看到文件完全不存在。这明显是个错误的结果，因为文件还存在只是被重命名了，文件在任何一个时间点都是应该存在的。但是如果我们按照上面的方式实现锁的话，那么在某个时间点，文件看起来就是不存在的。

所以这里正确的解决方法是，我们在重命名的一开始就对d1和d2加锁，之后删除x再添加y，最后再释放对于d1和d2的锁。&#x20;

![](/files/-MPMd4CdT80r9WukCk5o)

在这个例子中，我们的操作需要涉及到多个锁，但是直接为每个对象自动分配一个锁会带来错误的结果。在这个例子中，锁应该与操作而不是数据关联，所以自动加锁在某些场景下会出问题。

> 学生提问：可不可以在访问某个数据结构的时候，就获取所有相关联的数据结构的锁？
>
> Frans教授：这是一种实现方式。但是这种方式最后会很快演进成big kernel lock，这样你就失去了并发执行的能力，但是你肯定想做得更好。这里就是使用锁的矛盾点了，如果你想要程序简单点，可以通过coarse-grain locking（注，也就是大锁），但是这时你就失去了性能。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/lec10-multiprocessors-and-locking/10.3-when-use-lock.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
