在服务器端软件中, 并发和并行性通常被认为是不同的概念。在服务器中, 支持并发 i/o 意味着服务器能够通过执行与那些客户端仅有一个计算单元对应的几个流来为多个客户端提供服务。在这种情况下, 并行性意味着服务器能够同时执行多个操作 (具有多个计算单元), 这是不同的。
例如, 一个酒保可以照顾几个客户, 而他只能准备一次饮料。这样他就可以不并行地提供并发性。
单线程程序绝对可以通过使用 i/o 复用机制和事件循环 (Redis用的是事件循环), 在 i/o 级别上提供并发性。
并行性具有成本: 在现代硬件上可以找到多个套接字/多个内核, 线程之间的同步非常昂贵。另一方面, 像 Redis 这样的高效存储引擎的瓶颈往往是网络–CPU前面的陷阱。因此, 孤立的事件循环 (不需要同步) 被视为构建高效、可伸缩的服务器的良好设计。
Redis 操作是原子的事实仅仅是单线程事件循环的结果。有趣的一点是, 原子性是以不额外的成本提供的 (它不需要同步)。用户可以利用它来实现乐观锁定和其他模式, 而无需支付同步开销。
如果 Redis 是单线程的, 那么为什么需要锁定机制呢?
Redis 确实 (大多是) 单线程, 但当多个客户端尝试在相邻的时间接近时进行不同的事情时, 则需要锁定。在 RiA 中讨论的锁定完全是关于-确保只有一个客户端/线程执行特定任务, 或者确保更新不会出错。
这里有一个例子, 为什么你需要锁定, 尽管 Redis 有单线程性: 假设你有一个值在 Redis, 一个数字, 例如存储在一个名为 foo 的键值下。您的应用程序的代码读取该数字 (GET foo), 做一些事情 (ADD 1) 并将其写回 (SET)。当您在单个线程中运行代码时, 这就是它的外观:
应用程序 Redis
App Redis
|---- GET foo ---->|
|<------ 1 --------|
| |
| thinking... |
| |
|--- SET foo 2 --->|
|<----- OK --------|
现在让我们看看两个应用程序客户端尝试这样做时会发生什么:
App 1 Redis App 2
|---- GET foo ---->| |
|<------ 1 --------|<--- GET foo -----|
| |------- 1 ------->|
| thinking... | |
| | thinking...|
|--- SET foo 2 --->| |
|<----- OK --------|<--- SET foo 2 ---|
| |------ OK ------->|
在这里你可以立即看到发生了什么,因为没有锁定, 尽管服务器 (主要是) 单线程的-得到的结果不是 3, foo 的值最终是2。当您添加更多的线程/客户/应用程序时, 当多个作者尝试修改数据而不进行协调 (即锁定) 时, 事情会变得更加欢快而严重。
乐观锁定只是一种方法, Redis 通过监视机制提供内置。然而, 有时, 乐观-尽管看上去容易且快乐-通常不是正确的解决方案, 所以你需要实施更好/先进/不同的机制, 以防止竞态条件。可以说, 这样的锁可能在 Redis 之外实现, 但如果您已经使用它, 那么在其中管理您的锁也是有意义的。