并发杂谈

最近面试了不少人,发现好多人对并发有误解。故今天就在这里聊一聊并发相关的问题。

关于并发这个事,很多人面试者都会讲:“我们用户量不大,没有多少人用,所以并发不高,对并发不太了解,blabla。”每每听到这样的话,我都感觉很无奈。估计人家想说的是高并发,但其实我只想问并发。很多人都会犯这个概念混淆的错误,并发≠高并发,二者有联系,但并不能画等号。

下面容我一步一步把这个概念讲清楚。

并发到底是啥子意思

百度百科如是说:

并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。

并且还给出了并行的概念,并且把并发和并行做了对比。

当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

注意,以上解释与我们平时所谈的‘’并发”根本不是一回事,工程开发中的并发其实就是:并行+并发,总之一句话,同时执行。

在操作系统中也许我们区分并发与并行的概念,但工程上并不关心操作系统怎么过的,我们只关心同时执行。确切的说,谈到并发,我们关心以下几四个问题:

  • 如何让程序同时执行?
  • 什么情况下应该让程序同时执行?
  • 如何解决程序同时执行过程中所产生的问题?
  • 如何让程序有效率的同时执行?

问题四,即所谓的并发安全,或者并发问题。问题二、三,则涉及到并发优化。

而所谓的“高并发”根本不在此列。通常我们讲的高并发,其实想要解决的问题应该是:保持原有资源的前提下,如何让系统能高效支持更多用户的同时访问?
举个极端点的例子,Redis支持高并发么?

当然!

但它TMD是单线程的!

并发所关心的问题

前文所述,并发关心四个问题。下面我逐个谈一下这几个问题。
(以下问题在阐述中不区分 并行、并发,同时执行,即并行=并发=同时执行)

  1. 如何让程序同时执行?

    这个不同语言有不同的实现方式,不管开线程也好,开进程也好,甚至使用协程也好,跟我们关系都不大。
    重点在于编程语言会提供一套让程序同时执行的语法,学会这套语法,就可以当做学会了让程序同时执行的方法。
    毕竟对于一个工程师来说,应用最重要。至于实现原理,百度百度,领会一下就得了。

  2. 什么情况下应该让程序同时执行?

    换一种方式问这个问题,为啥我们要让程序同时执行?其实答案就两个字:效率。

    当并行比串行快时,自然选择并行。当并行没有串行快的时候,还玩个毛线啊,果断串行就完了。

    那什么时候并行会比串行快呢?这是后就必须提到一个概念:IO。
    这概念解释起来有点麻烦,具体深入请自行Google。简单来说就是IO相关的操作是需要等待的。读写文件要等,网络操作要等,接收键盘鼠标等外设的信息也要等。
    使用单线程,当碰到这些必须要等的操作就只能干瞪眼,傻站着。而这时候其实是可以让程序做其他事情的,也就是通过并发执行,在程序碰到傻等的事情的时候去做别的,效率自然就提上来了。

    换而言之,对于跑在单核处理器的程序而言,程序中IO操作越多,傻等的时间越长,就越应该引入并发提升程序的性能。反之,若基本没啥IO操作,就没有必要并发。
    当然,现在基本都是多核心处理器了,即使没有IO等待,只要逻辑本身可以拆分并行,同样可以通过并行的手段加速。只不过效果欠佳,有可能都抵不过线程切换的开销。

    总结:IO操作越多,IO时间越长,越应并发加速我们的程序。

  3. 如何解决程序同时执行过程中所产生的问题?

    我们通常讲的并发问题,实际上就是在说并发安全问题。面试中经常问的volatile,synchronize,lock,threadlocal之类的其实就是解决并发安全相关的手段。

    经过多年的沉淀,大多数面试者对这些问题都早有准备,百度上的答案也一搜一大把。以至于问来问去,把最应该问的问题都忘记了,那就是:并发问题的根源是什么?什么情况下会出现并发问题?

    答案及其简单:共享可变

    ThreadLocal破坏了共享,所以安全。Immutalbe,CopyOnWrite 破坏了可变,所以安全。当必须既要共享又要可变时,才会使用诸如自旋,原子操作,加锁这样的手段。

    往往大多数面试者碰到并发问题,第一反应就是加锁。问及有没有别的手段,往往一脸茫然,这就是对并发问题的根源没有理解的缘故。

    那为啥共享+可变就会产生问题呢?

    实际上共享+可变,就意味着并发执行的程序会读写同一份数据。问题就产生在读了一个变量的值,并根据当前的值做了一系列操作,而整个过程并不是原子性的。执行的操作是基于变量当前值的操作,如果在操作过程中另一个进程或线程改变了变量的值,整个操作就是在错误值得基础上操作,从而导致错误的执行结果。

    经典的并发累加结果错误就是这个原因导致的。要想保证程序的正确性,就要确保这种基于某个值的一系列操作过程中,这个值不能被改变。当然解决这样的问题就需要加锁。

    总结:首先要想办法破坏根源,要优先考虑能不能不共享,能不能CopyOnWrite。当不能规避时,就要在开发过程中注意状态量,注意并发状态下的if,保证操作的原子性。

  1. 如何让程序有效率的同时执行?

    这个问题挺大的,但在实际的开发中,往往会变成具体的问题。比如说,究竟开多少个线程合适?如何加锁才能对程序性能影响最小?在具体到系统中各个组件的调参,或者更换编程模型。

    这些东西,随着多年的经验积累,大多已有成熟的解决方案。小弟才疏学浅,此处不敢在献丑,以免贻笑大方。

也说说高并发

​ 讲真这个问题我是不太敢谈的,毕竟没有玩过那种超大并发的项目。所以取个巧,重点只谈谈高并发与并发的关系。

​ 如前文所说,并发可以提升效率。而高并发系统,效率必然不会差,通过并发手段提速是必然会发生。也就是所并发只是保证高并发的手段之一,只是高并发的一小部分。

​ 高并发系统中常用的手段,诸如负载均衡,反向代理,缓存加速,异步消峰,最终一致。还有中间环节的各种优化,包括数据库SQL,代码,系统配置,业务流程的优化,等等诸多手段。这些其实都与我们常谈论的并发其实并没有多大的关系。

写在后面

​ 技术更新换代很快,而基础理论不会大变,投资基础理论的学习收益永远不会太差。

​ 一个基础扎实,头脑清晰的人,无论在什么环境下都能快速成长;而基础一团浆糊,贪心浮躁的人,工作经验再多都是虚长年龄而已。

​ 不要因为没有接触高并发系统的条件就拒绝学习并发,二者并不是一个概念。

​ 即便使用系统的人很少,也可能产生并发问题;同理,一个小的项目也能使用并发技术加速。

​ 我们改造不了环境,就不要抱怨,努力的提升自己才是王道。加油!

你可能感兴趣的:(并发杂谈)