Windows下C++实现进程间同步


多线程同步在平常的使用中很常见,可以通过临界区,互斥量,事件等来实现,具体的选择依赖于不同的使用场景。但是进程同步使用的比较少,网上查找资料,也多是以多线程使用来讲解的,并不详细。刚好自己正在做一个升级程序,主程序是一个进程,升级程序是一个进程,我把所有的与升级服务端的交互全部放在了升级程序中,所以当升级程序从服务端获取版本号与本地进行比较时,主程序需要进入等待阶段。当版本号比较完后,再触发升级程序或主程序的下一步操作。此文仅以记录我在完成这个升级过程中踩过的坑。

一,方法介绍

大家都知道,内核对象资源并不只属于创建它的线程(线程所属进程),它是由内核分配的,只能由操作系统内核访问,就算创建它的进程结束了,如果没有主动释放该内核对象,那么内核对象还会一直存在。那么我们也可以利用内核对象的这一个特点来实现两个进程同时访问一个内核对象,然后利用内核对象的所有者的切换,使另一个进程进入等待阶段,以达到两个进程的同步。具体步骤整理如下:(主程序用A表示,升级程序用B表示,本文章以互斥量mutex为例进行讲解,升级架构本文不再讲解,参考我另一篇博客C++实现客户端升级流程
1,A程序启动,调用B程序,在B程序的初始化中创建一个mutex,并将mutex的所有权交给B,同时,A程序等待mutex的所有权(WaitForSingleObject)
2,B程序与服务端进行通信,获取服务端版本信息,与本地进行比较,如果不需要升级,那么B程序释放对mutex的控制,A程序等到了对mutex的控制,程序按正常流程走下去。
3,如果是“立即升级”,那么也很简单,直接kill掉A程序,剩下的走B程序的升级流程(下载文件,替换文件等)
4,如果是“静默升级”,那么首先B程序要释放对mutex的控制,让A程序得到mutex的控制权,能够正常流程走下去,不影响使用。同时,B程序在隐藏在后台下载文件,并监测A程序退出后进行文件的更新替换操作。

二,踩过的坑

虽然流程看起来比较简单,但是在做的过程中碰到了一些坑,也是由于很多函数久未使用,然后就去网上查阅资料,但是很多资料查不到(比如OpenMutex),并且和有资料是错误的(很无语),列举下本次踩过的坑。
1,OpenMutext的使用,
之前一直以为该函数是打开一个已经存在的互斥量,并不改变该互斥量的占用现状(即该互斥量之前被哪个线程/进程占用,那么操作该函数后也保持不变)。结果恰恰不是!!!该函数声明如下:

HANDLE OpenMutex(
DWORD dwDesiredAccess, // access
BOOL bInheritHandle, // inheritance option
LPCTSTR lpName // object name
);

第二个参数决定了该互斥量的占用情况。如果为TRUE,那么对象的线程ID将被置为调用线程的线程ID,由于该ID非零,所以该互斥量处于一个非触发状态,为调用线程所控;如果为FALSE,那么互斥量对象的线程ID和递归计数都将被置为0,这意味着该互斥量不为任何线程所拥有,处于触发状态。

2,CreateMutext的返回值
之前一直以为当创建一个命名的内核对象时,如果该内核对象已经被创建过了,那么返回值应是NULL,同时GetLastError() == ERROR_ALREADY_EXISTS。结果恰恰不是!!!如果已经存在该名称的内核对象,并且两个内核对象的类型一致(内核对象不一致,但是名称相同,第二个调用的返回NULL),系统会在后调用的进程的句柄表中查找一个空白记录项,并将其初始化为指向已经存在的内核对象(注意:不是创建,只是新增一个指向)。

三,正确方式(附代码)

讲解到现在,其实代码已经不重要了,但是为了好理解,我还是写了一个小demo。
进程A创建mutex,并拥有所有权。
Windows下C++实现进程间同步
进程B也是通过CreateMutex来创建同名互斥量,返回的是该互斥量在进程B中的一个新的指向。
Windows下C++实现进程间同步
进程A输入1后,释放控制权,B程序得到控制权,继续走下去。
如果需要完整的升级流程代码,可以联系我。