最近搞一个性能统计工具,这个工具有个小功能是:逐帧统计某算法的性能指标,听起来复杂,简单说就是,需要达到程序的代码段a和c,分别位于(包裹)另一个段程序的算法执行逻辑前后。
调研了几种进程同步方法,发现信号量是最合适的,但这玩意,自从毕业后就没用过了,由于自己之前都是用的java,python等高级语言偏多,而且都是web方面,基本上没有进程间同步的场景,突击学习下,顺便也梳理研发过程中的一些小taps。
解释常用语
1 | sem_t *p_abc = sem_open("/abc", O_CREAT |O_RDWR, 0666, 0); |
oflag参数可以是0、O_CREAT(创建一个信号量)或O_CREAT|O_EXCL(如果没有指定的信号量就创建),如果指定了O_CREAT,那么第三个和第四个参数是需要的;其中mode参数指定权限位,value参数指定信号量的初始值,通常用来指定共享资源的书面。该初始不能超过SEM_VALUE_MAX,这个常值必须低于为32767。二值信号量的初始值通常为1,计数信号量的初始值则往往大于1。
如果指定了O_CREAT(而没有指定O_EXCL),那么只有所需的信号量尚未存在时才初始化它。所需信号量已存在条件下指定O_CREAT不是一个错误。该标志的意思仅仅是“如果所需信号量尚未存在,那就创建并初始化它”。但是所需信号量等已存在条件下指定O_CREAT|O_EXCL却是一个错误。
坑01之初始化
需要特别留意:O_CREAT 并不是创建信号量的意思。而仅仅是对信号量不存在时,自动创建信号量(可以认为一种兼容性扩展)。
所以以上代码执行后,未必会得到一个value=0的信号量。
如果执行前:“/abc”不存在,则新建一个“abc”且value=0,符合预期。
但如果执行前已经有:“/abc”,则value依然保持原来,这一点不符合我们直观理解的变量赋值。(直观理解:”/abc”不存在则新建且初值0,存在则设置初值0)
所以以上代码执行后,信号量“/abc”可能是任何值,这一点极其容易出错,一旦出错就会导致程序死锁,而本身也很难通过代码复核发现(主要就是和直观理解不一致,导致其“看起来逻辑上很像对的”)。
那么如何保证,这个信号量取值一定0呢?
也简单,先把这个信号量删除即可(sem_unlink)。删除后可以确保sem_open走O_CREAT逻辑,达到初始化为0的效果。
坑02中断注册
程序被Ctrl+c或kill时,会以一种非正常方式退出,这种方式终止的进程,存在资源无法释放的风险(持有的锁,信号量等)。如果是程序内部资源,倒也无所谓,对高级语言有内存回收机制,对c、c++这种不是那么高级的高级语言(无自动gc),其进程结束后,整个内存资源依然会被操作系统接管,这些都影响可控。但是一旦涉及多个进程共享的信息(锁,信号量),如果进程kill后没有释放相关资源,就会导致其他正常进程陷入死锁,或者无限等待的状态(总之,人家就不正常了)。
所以需要研发中类似,“finally”这样机制,不过这里的finally是针对整个程序,而非某一段代码块。
简单来说就是需要注册SIGIGN信号对应的中断处理函数,中断处理函数内部要将相关信号量维护在正常状态(”正常状态”就是假如程序运行正常结束的状态,最简单的就是删了信号量,每次用的时候新建,当然这部分逻辑要能自洽。不能SIGIGN的时候删除,然后 sem_open的时候又不加参数 O_CREAT,导致程序报错)。