模块四:进程和多线程: 练习题详解

请问这个程序执行后, 输出结果 Hello World 会被打印几次?

  • fork()
  • fork()
  • fork()
  • print("Hello World\n")

fork 的含义是复制一份当前进程的全部状态

  • 第 1 个 fork 执行 1 次产生 1 个额外的进程。 第 2 个 fork,执行 2 次,产生 2 个额外的进程。第 3 个 fork 执行 4 次,产生 4 个额外的进程。
  • 所以执行 print 的进程一共是 8 个

如果考虑到 CPU 缓存的存在,会对上面我们讨论的算法有什么影响?

  • 对某个地址,和任意时刻,如果所有线程读取值,得到的结果都一样,是一种强一致性,我们称为线性一致性(Sequencial Consistency),
  • 含义就是所有线程对这个地址中数据的历史达成了一致,历史没有分差,有一条大家都能认可的主线,因此称为线性一致。
  • 如果只有部分时刻所有线程的理解是一致的,那么称为弱一致性(Weak Consistency)。

那么为什么会有内存不一致问题呢? 这就是因为 CPU 缓存的存在。

 

  • 如上图所示:假设一开始 A=0,B=0。两个不在同一个 CPU 核心执行的 Thread1、Thread2 分别执行上图中的简单程序。
  • 在 CPU 架构中,Thread1,Thread2 在不同核心,因此它们的 L1\L2 缓存不共用, L3 缓存共享。
  • 在这种情况下,如果 Thread1 发生了写入 A=1,这个时候会按照 L1,L2,L3 的顺序写入缓存,最后写内存。
  • 而对于 Thread2 而言,在 Thread1 刚刚发生写入时,如果去读取 A 的值,就需要去内存中读,这个时候 A=1 可能还没有写入内存。
  • 但是对于线程 1 来说,它只要发生了写入 A=1,就可以从 L1 缓存中读取到这次写入。
  • 所以在线程 1 写入 A=1 的瞬间,线程 1 线程 2 无法对 A 的值达成一致,造成内存不一致。
  • 这个结果会导致 print 出来的 A 和 B 结果不确定,可能是 0 也可能是 1,取决于具体线程执行的时机。

考虑一个锁变量,和 cas 上锁操作,代码如下:

int lock = 0
void lock() {
  while(!cas(&lock, 0, 1)){
    // CPU降低功耗的指令
  }
}
  • 上述程序构成了一个简单的自旋锁(spin-lock)。如果考虑到内存一致性模型,线程 1 通过 cas 操作将 lock 从 0 置 1。
  • 这个操作会先发生在线程所在 CPU 的 L1 缓存中。
  • cas 函数通过底层 CPU 指令保证了原子性,cas 执行完成之前,线程 2 的 cas 无法执行。
  • 当线程 1 开始临界区的时候,假设这个时候线程 2 开始执行,尝试获取锁。
  • 如果这个过程切换非常短暂,线程 2 可能会从内存中读取 lock 的值(而这个值可能还没有写入,还在 Thread 所在 CPU 的 L1、L2 中),线程 2 可能也会通过 cas 拿到锁。
  • 两个线程同时进入了临界区,造成竞争条件。
  • 这个时候,就需要强制让线程 2的读取指令一定等到写入指令彻底完成之后再执行,避免使用 CPU 缓存。Java 提供了一个 volatile 关键字实现这个能力,只需要这样:
volatile int lock = 0;

就可以避免从读取不到对lock的写入问题。

乐观锁、区块链:除了上锁还有哪些并发控制方法?

  •  乐观锁、悲观锁都能够实现避免竞争条件,实现数据的一致性。
  • 比如减少库存的操作,无论是乐观锁、还是悲观锁都能保证最后库存算对(一致性)。
  • 但是对于并发减库存的各方来说,体验是不一样的。悲观锁要求各方排队等待。 乐观锁,希望各方先各自进步。

所以进步耗时较长,合并耗时较短的应用,比较适合乐观锁。

  • 比如协同创作(写文章、视频编辑、写程序等),协同编辑(比如共同点餐、修改购物车、共同编辑商品、分布式配置管理等),非常适合乐观锁
  • 因为这些操作需要较长的时间进步(比如写文章要思考、配置管理可能会连续修改多个配置)。
  • 乐观锁可以让多个协同方不急于合并自己的版本,可以先 focus 在进步上。

悲观锁适用在进步耗时较短的场景

  • 比如锁库存刚好是进步(一次库存计算)耗时少的场景。
  • 这种场景使用乐观锁,不但没有足够的收益,同时还会导致各个等待方(线程、客户端等)频繁读取库存——而且还会面临缓存一致性的问题(类比内存一致性问题)。
  • 这种进步耗时短,频繁同步的场景,可以考虑用悲观锁。类似的还有银行的交易,订单修改状态等。
  • 再比如抢购逻辑,就不适合乐观锁。抢购逻辑使用乐观锁会导致大量线程频繁读取缓存确认版本(类似 cas 自旋锁),这种情况下,不如用队列(悲观锁实现)。

综上

  • 有一个误区就是悲观锁对冲突持有悲观态度,所以性能低;
  • 乐观锁,对冲突持有乐观态度,鼓励线程进步,因此性能高。
  • 这个不能一概而论,要看具体的场景。
  • 最后补充一下,悲观锁性能最高的一种实现就是阻塞队列,你可以参考 Java 的 7 种继承于 BlockingQueue 阻塞队列类型。

还有哪些进程间通信方法

  • 使用数据库
  • 使用普通文件
  • 还有一种是信号,一个进程可以通过操作系统提供的信号。举个例子,假如想给某个进程(pid=9999)发送一个 USR1 信号,那么可以用:
  • kill -s USR1 9999
    

    进程 9999 可以通过写程序接收这个信号。 上述过程除了用kill指令外,还可以调用操作系统 API 完成。

如果磁盘坏了,通常会是怎样的情况

  •  磁盘如果彻底坏了,服务器可能执行程序报错,无法写入,甚至死机。
  • 这些情况非常容易发现。而比较不容易观察的是坏道,坏道是磁盘上某个小区域数据无法读写了。
  • 有可能是硬损坏,就是物理损坏了,相当于永久损坏。也有可能是软损坏,比如数据写乱了。
  • 导致磁盘坏道的原因很多,比如电压不稳、灰尘、磁盘质量等问题。

磁盘损坏之前,往往还伴随性能整体的下降

  • 坏道也会导致读写错误。
  • 所以在出现问题前,通常是可以在监控系统中观察到服务器性能指标变化的。
  • 比如 CPU 使用量上升,I/O Wait 增多,相同并发量下响应速度变慢等。
  • 如果在工作中怀疑磁盘坏了,可以用下面这个命令检查坏道:sudo badblocks -v /dev/sda5
     
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值