Writing Self-Clean Code (0x01) - Process
进程通常比较容易理解,通常学习C语言入门的时候,写Hello World就要写main 函数了。
进程的清理
按说进程退出时,通常是没有什么好清理的。因为现代操作系统随着进程的退出,大部分的资源都已经清理干净了。但是也不排除一些特殊的应用方式需要在进程结束的时候做一些清理工作,比如:
- 以Linux系统而言,多线程的应用在主线程退出的时候,会去清理全局变量。而此时如果其他线程还在访问这些全局变量的话,就可能出现内存访问错误,直接后果可能就是得到一个很不优雅的退出。
- 进程退出时希望做一些业务上的事情:记录日志、向Socket发送一些数据。但是可能main 函数的代码控制权并不在你的手里。这种情况在大型的软件开发中、司空见惯;当然你可能说这种类型的项目,通常架构上都会在设计的时候去考虑这些东西吧。但是事实上很多时候这些规模的项目往往可能由大量的软件新手来完成;亦往往负责架构设计的人对业务很熟悉,但是对编程、设计往往火候不是那么够(这个有点扯远了……)
- 应对一些异常的退出情况
总的来说,进程的清理本来应该不是个问题,原本都应该在 main 函数退出前,把所有改做的事情都做了,但是客观或真主管的原因要求我们去面对一些进程退出的问题。
进程的退出方式
通常来说进程的退出有这么几种方式:main 函数返回、调用exit、调用 _exit (区别于exit)、收到了一个异常信号(如:段错误)、pthread_exit主线程,等等等等。 其实可以归结为两类:退出前完成相关的用户清理(典型:exit)、退出前不进行相关的用户清理(典型:_exit) 考虑以下程序代码(Linux GCC):
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <signal.h> #include <pthread.h> class C01 { public: C01() {} ~C01() { printf("destructor of C01\n"); } }; static C01 o1; int main(int argc, char *argv[]) { if (argc > 1) { if (strcmp("exit", argv[1]) == 0) { exit(0); } else if (strcmp("_exit", argv[1]) == 0) { _exit(0); } else if (strcmp("pthread_exit", argv[1]) == 0) { pthread_exit(0); } else if (strcmp("abort", argv[1]) == 0) { abort(); } else if (strcmp("raise", argv[1]) == 0) { if (argc > 2) { int sig = atoi(argv[2]); { raise(sig); } } } } return 0; }
通过观察变量o1 的析构是否被调用来观察各种退出方式,是否完成了用户的清理工作(本例中是清理全局变量)。 此处省略运行过程若干字、若干图;仅仅列出结果供大家参考:
| 退出进程的方式 | 是否进行用户清理 | 备注说明 |
|---|---|---|
| main 函数返回 | 是 | main 函数退出后会执行相关的用户清理 |
| 调用 exit 方式 | 是 | |
| 调用 _exit 方式 | 否 | |
| 调用 pthread_exit 方式 | 是 | |
| 调用 abort 方式 | 否 | 实际上abort 就是 raise (SIGABRT) |
| 调用 raise 方式 | 否 | 由此其实不难得出结论:对于进程收到SIGTERM、SIGSEGV等异常的信号时,也是不进行清理的 |
注册自己的清理
了解了几类进程退出方式和清理的关系之后。我们再熟悉一个函数atexit。
int atexit(void (*function)(void));
这是一个注册回调函数的接口。顾名思义就是注册了的函数在进程退出的时候会被调用到(其实就是我们上面所说的用户清理操作)。这个函数是可以反复调用的,并且被注册的函数是以类似栈的方式被保存起来,在进程退出是逐一反过来调用(LIFO) 参考如下代码:
#include <stdio.h> #include <stdlib.h> void cb01() { printf("cb01\n"); } void cb02() { printf("cb02\n"); } int main(int argc, char *argv[]) { atexit(cb01); atexit(cb02); return 0; }最后运行效果如下:
cb02 cb01
Windows 上的情况
Windows 上的exit 或者 _exit 主要依赖于 CRT,其他情况也和Linux 的行为保持一致,例如异常退出是不会清理的等等。 需要注意的一点:因为Windows API 显然比CRT 更加底层,所以结束主线程的方式的时候如果采用的API方式 (TerminateThread)是不会调用清理的;如果需要清理那么你需要调用CRT 中相应功能的函数 _endthreadex.
当前暂无评论 »