标签归档:并发

记一次图片转换引发的shell并发问题

  1. 剧情是这样的,我要拉一批图片url(大约2W条),然后进行图片裁剪/转换,故事就这样开始了。
  2. 我准备用curl拉取这2w多张图片到本地,然后用ImageMagick进行图片裁剪或转换。
  3. 然后我写了我第一版脚本。

    然后我去一台8核16超线程的机器上跑了一遍,用了7200s。我对公司网络带宽和这台机器性能不会有任何疑问,客观原因是基于大量短连接的网络耗时大且文件io频繁,但我觉得他太慢了。
  4. 网上闲逛偶遇shell并发文章觉得很妙,于是我写下了第二版。
    原理就是fifo文件写入与cpu核心数目相等的换行符,每执行一个任务,从fifo读回一个换行符,当读完时(也就是当前任务数=核心数)read就阻塞了。

    机器还是那台机器,只不过时间缩减到了920s。当然他还没有超出预期,然而我也没有写下第三版,因为我知道我把图片放内存,他肯定会更快,但这已经不是我最感兴趣的事儿了。
  5. 我对shell并发这个话题产生了兴趣,于是github了一把,bashreduce出现了(https://github.com/erikfrey/bashreduce)。

下面第一张图是单台8核机器,第二张图是4台4核机器(注: bashreduce中br是一个脚本,brm/brp是一个二进制文件)。可以看到在这么短的时间间隔内提升都非常明显,可以想象在某些极其耗时的操作中他节省下来的时间将是多么可观。

单机上的

多机上的


当然故事还远没有结束,因为他还没有应用到我的生产环境中去。

6. 占坑等着Phoenix++体验 ….

“并行,并发,异步,同步,阻塞,非阻塞”之我见

后台开发中总是在”并行,并发,异步,同步,阻塞,非阻塞”之间转,自己经常就被转得晕头转向。由于对基于经验的答复不满意,因此决定将我这几个月对这些概念千丝万缕的思绪整理出来。

【并发与并行】

  • 并发: 仅仅包含宏观上的意义,即多个任务同时被处理;而微观上可能仅仅是一个cpu分时间片去完成了多个任务,这个时候多个任务是被顺序处理的。
  • 并行: 则更加严格,在宏观和微观上都应该具有多个任务同时被处理的含义,这个意义上多个任务被并行处理的,单cpu永远不可能并行。
=>
  1. 并行和并发是站在两个不同角度上理解产生的概念。
  2. 并行一定是并发,并发却不一定是并行。

【同步与异步】

  • 同步: 任务的执行过程是顺序的。
  • 异步: 事情的执行过程不是线性的。
=>
  1. 异步和同步是从任务被执行的顺序上来区分的,从这个基本概念出发,异步并不见得比同步性能高,也就是说异步模型不见得会比同步模型有优势。
  2. 然而异步和同步这个概念却非常容易和阻塞与非阻塞混淆,因为任何一个任务理论上都不可能瞬间完成,而现实中的任务通常都需要与其他系统交互,因此可能被阻塞是不可避免的。
  3. 异步通常给人一个错觉,就是他比同步快。这里其实是有一个误区,因为一个任务实际被处理的时间并没有太大变化,只有在带阻塞的任务中,异步才可能 比同步快,因为异步快在将同步模式下带阻塞任务执行过程中cpu的等待时间利用起来处理其他任务,借此提升了多任务系统的整体并发能力,可见,一个任务的 异步不一定比同步快,同时,对于处理不阻塞的任务,异步模型也不会比同步模型快。

【阻塞与非阻塞】

  • 阻塞: 任务的执行会使cpu进入等待状态。
  • 非阻塞: 任务的执行不会主动让cpu进行等待。
=>
  1. 阻塞不阻塞的原始语义实际上是看任务在宏观执行过程中对cpu的主动占用情况是否连续来区分的。
  2. 在不考虑cpu时间片耗尽被重新调度或者其他干扰导致被抢占的情况下,非阻塞的任务在获得cpu后将一直执行到完成。
  3. 阻塞或非阻塞的任务与阻塞或非阻塞的接口是有区别的:前者隐含一个前提就是必须是任务执行从开始到结束的整个过程,而后者不用关心任务是否完整完成,仅仅关心当前这一步操作是否会阻塞。

【开发中的现状】

  1. 大部分业务都可能阻塞(网络IO,文件IO),常见的网络收发,数据库操作等,因此在追求海量高性能的server开发中很较少用到同步模型,这也是为什么我们潜意识里会觉得同步模型慢且容易与阻塞纠缠不清的原因 — 计算机世界的任务模型原本大多是阻塞任务。
  2. 开发中大多是将带阻塞的任务分解成多个不阻塞的部分(这就是异步模型中所谓的分割点),只有在这样的前提下配合异步模型才能大幅提升系统的并发能力,这就是为什么在高性能server开发中异步与非阻塞通常被混为一谈的原因 — 计算机处理海量任务只有这样才能整体最快。
  3. 对server业务,肯定提倡使用异步模型。但异步模型对开发者并不友好,因为人类的思维习惯是线性分析事务,这就是为什么大部分开发者愿意使用同步模型的原因 — 人类看待事物的习惯本就带有顺序性。

并发之libtask

【概述】
提到任务并发首先想到的肯定是传统的多进程,多线程模型,不过linux提供了函数库实现用户层的上下文切换 (sys/ucontext.h,ucontext.h)。libtask就是在单进程单线程下通过用户层的上下文切换来实现多任务并发的 库,libtask很小,轻量到只有几个文件。libtask与传统多进程或多线程并发模型相比的有两点优越性:

  • 用户层面的上下文切换只需要保存寄存器信息,开销很小
  • 没有资源竞争,不需要加锁

【主要结构】

【使用中的注意】

  • libtask核心文件是task.h,taskimpl.h,task.c,其他文件不是必须的
  • 根据业务修改维护任务指针的slot增长算法,原算法是每次用满时向上增加64个slot
  • libtask包含了main,自己只需要实现任务逻辑即可,任务逻辑入口taskmain(int argc, char *argv[])
  • 任务之间的通信,chann实现得有点繁琐,这些东西最好根据业务自身来定制

【libtask相关】
libtask源码
Mongrel2基于libtask实现