您的位置 首页 php

10-15k的PHP面试题|并发控制篇

分布式锁的类型和原理

● 乐观锁

乐观锁首先假设数据没有冲突,不会阻塞对资源的修改,而是在资源被实际修改之前,检查锁定期间是否被其他进程修改,如果资源被修改了,就返回错误信息给用户,让用户决定后续操作。
● 悲观锁

在资源处理过程中,让资源处于锁定状态,其他试图修改资源的进程将被阻塞。悲观锁主要分为:

 ● 共享锁:又称读锁,如果资源被加上了共享锁,则只能读取而不能修改资源,其他事务也只能再对该资源加共享锁,而不能加排他锁。也就是在释放共享锁之前只能读取资源,而不能对资源做任何修改。一个共享锁可以同时被多个进程占有。

● 排他锁:又称写锁,如果资源被加上了排它锁,则该事务可以读取和修改资源,但其他事务不能再对该资源加任何锁,直到排它锁被释放为止。一个排它锁在同一时刻只能被一个进程所占有。
  

● 公平锁

多个线程按照申请锁的顺序获得锁,所有线程都排队等待。队列中的第一个线程获得锁,其他线程都被阻塞,直到锁被释放。

优点是:所有线程都能获得锁,不会出现饥饿现象。

缺点是:吞吐量下降

● 非公平锁
多个线程并发尝试获取锁,获取不到的线程重新进入等待状态,锁被释放后再次抢占。
优点是:吞吐量提高
缺点是:竞争可能导致某些线程一直获取不到锁。

● RedLock
RedLock 是排他锁的一种,是用于解决缓存服务(如 redis)单点崩溃造成分布式锁不可用的容灾策略。原理是,从 3 个独立的缓存服务中获取锁,如果能够从至少两个服务中获取到锁,就证明加锁成功。这样就能保证即使某个缓存节点挂掉,分布式锁服务仍然可用。
存在的问题:

● 为了避免与某个故障的节点通信时间过长。需要为加锁操作设置一个快速失败时间。

● 如何释放锁:判断该节点上的锁是不是自己设置的,如果是就删除;向所有节点发出释放锁的请求(即使认为没有从该节点获取锁也要发出请求)。

● 多个服务同时抢占锁,造成没有任何服务能够获得大部分数量的锁(3个服务抢3个锁,每个服务获得一个)。解决方案是在检测到自己没有获得大部分锁时,立即释放锁,并在随机时间后尝试重新获取

同步器的类型和原理

● 计数信号量
用来控制同时访问特定资源的线程数量(控制并发量)。计数信号量是一种具有最大许可数量的锁,其他线程从信号量获取许可,当发放的许可数量达到最大值时,资源将被锁定,其他线程无法继续获得许可,直到某个许可被释放为止。这种机制限制了资源在同一时间的并发访问量。

● CountDownLatch(wait group)
CountDownLatch 能够阻塞一个线程,并等待其他线程执行完毕后再继续执行。它的内部包含一个计数器,初始值是等待的线程数量,每当一个线程执行完毕,就将计数器减 1,当计数器被减到 0 时结束阻塞状态。

常见的并发模型

● Fork/Join 模型:将任务划分为多个小任务,并行计算,等待计算结果后 再合并为一个大任务。

● Actor 模型:是 Erlang 的并发模型;Actor 是模型中的 worker,Actor 的状态是封闭的,外部无法访问,多个 actor 之间通过异步消息传递信息,actor 可以对消息做出相应,还可以派生出新的 actor,actor 内部可以阻塞,但对于主线程是非阻塞的。

● CSP 模型:是 Golang 的并发模型,与 Actor 类似,但与 actor 不同的是,CSP 使用 channel 发送消息,而不是直接发送给 worker,CSP 的发送动作是同步阻塞的(SCP 有 buffering channel 的概念,在 buffer 未满的情况下是非阻塞的)。相对的, Actor 则是在内部维护一个消息队列,也是按顺序解析消息的,但通信过程不会阻塞。CSP 的 worker 之间没有耦合,而 Actor 可能产生耦合。

● 生产消费模型:使用缓存保存任务,开启多个线程生产任务,再开启多个线程从缓存中取出任务进行处理。生产者与消费者不存在耦合,并且可以根据生产速度调整消费者数量。

● Master-Worker 模型:Master 进程负责接收任务,并把任务分配给 Worker,worker 进程负责处理子任务,并将结果返回给 master,接收到结果后进行归纳汇总,得到最终结果。

● Promise 模型:Promise 指代用于取得计算结果的代理或容器,该容器初始并不包含结果,只有当计算完成时结果才被填充。可以通过等待或回调获取结果。

Unix 环境下的 I/O 模型

● 阻塞式 I/O:当发起 read 等操作进行读取时,内核会检查数据是否准备就绪,如果没有准备好,则线程会进入阻塞状态开始等待,直到数据准备完毕后,内核主动将数据复制到应用程序缓冲区。

● 非阻塞式 I/O:当发起 read 等操作时,内核会检查数据是否准备就绪,如果没有准备好,则立刻返回一个错误,如果检测到数据准备完毕,则返回可用状态,用户可以通知内核将数据复制到应用程序缓冲区。

● I/O 多路复用:阻塞一个线程,让内核不断轮询句柄状态,当有任何一个句柄的数据准备完毕,就会返回通知给调用方,调用方通过遍历获取可用句柄进行读取。

● 信号驱动式 I/O:内核检测到数据准备后,通过信号通知用户进程,用户进程收到信号后,从内核将数据复制到应用程序缓冲区。

● 异步I/O(AIO):内核检测到数据准备后,内核主动将数据复制到应用程序缓冲区,然后通过信号通知用户进程数据准备完毕。

为什么多路复用需要搭配非阻塞 I/O

数据可能会被其他进程读走,内核也可能会校验和丢弃某些已准备好的数据,但 select 则将这些句柄标记为已准备好,这时没有 accept 到的线程将被阻塞。

协程中调用阻塞式 I/O 会阻塞整个线程吗

会,协程是用户态的调度任务,操作系统中并没有协程的概念,所以以线程的角度看,协程实际上是同步阻塞执行的。而某些支持协程的编程语言,会将语言层面的同步阻塞语法在底层实现为操作系统层面的异步功能,从而支持协程内部的“阻塞”操作。

文章来源:智云一二三科技

文章标题:10-15k的PHP面试题|并发控制篇

文章地址:https://www.zhihuclub.com/148839.shtml

关于作者: 智云科技

热门文章

网站地图