并发宏观、微观分析

并发编程是一个复杂的技术领域,微观上涉及到原子性问题、可见性问题和有序性问题,宏观则表现为安全性、活跃性以及性能问题

在设计并发程序的时候,主要是从宏观出发:

  • 安全性方面要注意数据竞争和竞态条件
  • 活跃性方面需要注意死锁、活锁、饥饿等问题
安全性问题

那什么是线程安全呢?其实本质上就是正确性,而正确性的含义就是程序按照我们期望的执行,并发Bug的三个主要源头:原子性问题、可见性问题和有序性问题

所有的代码都需要认真分析一遍是否存在这三个问题呢?当然不是,其实只有一种情况需要:存在共享数据并且该数据会发生变化,通俗地讲就是有多个线程会同时读写同一数据

数据竞争:

当多个线程同时访问同一数据,并且至少有一个线程会写这个数据的时候,如果我们不采取防护措施,那么就会导致并发Bug

竞态条件:

指的是程序的执行结果依赖线程执行的顺序。eg: int count=0;count+=1,如果两个线程完全同时执行,那么结果是1;如果两个线程是前后执行,那么结果就是2,在并发条件下,线程的执行顺序是不确定的。
也可以这么理解静竞态条件:

if (状态变量 满足 执行条件) {
  执行操作
}

解决方案:用互斥这个技术方案,也就是锁。

活跃性问题:

死锁、活锁、饥饿

  • 活锁

有时线程虽然没有发生阻塞,但仍然会存在执行不下去的情况,例如:现实世界里的例子,路人甲从左手边出门,路人乙从右手边进门,两人为了不相撞,互相谦让,路人甲让路走右手边,路人乙也让路走左手边,结果是两人又相撞了,一直谦让下去就是活锁的情况。

解决方案:

等待一个随机的时间再切换,Raft这样知名的分布式一致性算法中也用到了它

  • 饥饿

线程因无法访问所需资源而无法执行下去的情况。在CPU繁忙的情况下,优先级低的线程得到执行的机会很小,就可能发生线程“饥饿”。(也就是一直获取不到锁)

解决方案:

有三种方案:一是保证资源充足,二是公平地分配资源,三就是避免持有锁的线程长时间执行,这三个方案中,方案一和方案三的适用场景比较有限,因为很多场景下,资源的稀缺性是没办法解决的,持有锁的线程执行的时间也很难缩短。倒是方案二的适用场景相对来说更多一些。

使用公平锁,所谓公平锁,是一种先来后到的方案,线程的等待是有顺序的,排在等待队列前面的线程会优先获得资源。

性能问题

性能方面的度量指标有很多,三个指标非常重要,就是:吞吐量、延迟和并发量。

  • 吞吐量:指的是单位时间内能处理的请求数量。吞吐量越高,说明性能越好
  • 延迟:指的是从发出请求到收到响应的时间。延迟越小,说明性能越好
  • 并发量:指的是能同时处理的请求数量,一般来说随着并发量的增加、延迟也会增加。所以延迟这个指标,一般都会是基于并发量来说的。例如并发量是1000的时候,延迟是50毫秒。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容