当优化扩展到多核时
"软件开发没有银弹,我们能做的就是选择和平衡;"
上一篇文章我们聊了在单线程下程序优化的5个方向(ref:《程序优化的5个方向》);当单核优化到极值后,就到了多任务的情况;
想起来很清晰,单个任务分解成多个任务,让多个cpu同时来工作,并行执行,效率自然就上去了;
但,未必就这么简单;
任务分解的粒度
首先,我们需要确定,我们的单个任务是否可以分解;比如解析很多个文件,这样的任务划分成多个很简单;但如果是一个耗时的串行逻辑计算,后期的计算依赖前期的结果,这样就不好拆分;这种形式可能需要在更高层次上来拆分;
数据竞争
编程就是计算和数据;计算并行了,但数据还是访问同一份,访问共同的资源会产生资源竞争;
如果不进行控制,可能导致同一份数据重复计算(多个读的场景)或是脏数据的产生(有回写的场景);
引入锁
为了让数据访问有序进行,需要引入锁来防止脏数据;
控制锁的粒度,是个需要精心考虑的话题;
比如对于大量读少量写的场景,相比一视同仁的加锁,使用读写锁能显著提升效率;
我们日常能接触到的产品中,数据库是个用锁高手,在更新数据的时候,是锁住行,还是列、或是表,不同的粒度性能相差明显;
惊群现象
考虑这样的场景:多个线程都在等在一个锁,如果可以拿到锁,线程就开始工作(线程池)
当锁被释放时,如果唤醒多个线程可能会产生 惊群现象;
解决方案:
使用单线程方案/处理accpet连接 处理等待锁的操作,让任何时刻只有一个线程在等待锁;
更多细节参考:
《客户-服务器程序设计方法》中 预先创建线程池,每个线程各自accept 一节
数据复制
让每个线程使用自己的数据,让数据不共有,这样能去掉资源竞争,去掉锁;
将数据复制为多份,减少竞争,各自访问各自的数据;
但这又引入了一个新的问题:如果各个线程回写了数据,如何保证这么数据的一致性?
毕竟它们代表的其实是一份数据;
涉及到数据的一致性,多份数据之间的同步又是个难题;
数据分片
那好,换个思路,不使用数据复制;我们使用数据分片;分片这个思想更容易想到,既然“计算”被划分为多个小任务了,那么数据也可以同样处理;
将数据分片,每份数据存的内容不相同,它们之间没有共同点;
这样,数据访问没有数据竞争,同时由于数据不同,也不涉及到数据一致性同步的问题;
但,分片远远没有想的那么美好;
分片导致了每个线程看到数据不再是全集,而是片段;这就注定了这个线程只能处理这部分的特定数据;这样,线程之间的计算失去了可替换性;某种工作只能在特定的线程上处理;
而如果有个任务需要访问所有的数据,这样就变得更加复杂;
原来,分片之后,我们将难题向上推了,推到线程层面,需要考虑到业务逻辑层面的处理;
这样,可能更加复杂;
ok,想要速度更快,使用多核来处理,需要面对更多的问题;
将单机扩展多机集群,涉及到架构层面来看,其实我们的面对的问题是类似的;
参考:《大型网站技术架构》读书笔记[2] - 架构的模式
软件开发没有银弹,我们能做的就是选择和平衡;