在一个项目中并发执行任务时多数情况下都会选择多线程,但有时候也会选择多进程,例如可以同时运行n个记事本编辑不同文本,由一个命令跳转到另外一个命令,或者使用不同进程进行协作。在学习多进程之前我们必须了解一个进程如何获取进程,进程如何结束,如何调用外部进程等操作。
自ANSI C开始就支持退出程序和析构,无论在任何时候调用exit()都会直接退出程序,exit()可以向主机环境返回退出结果,exit(0)表示正常退出,其他值表示退出异常,EXIT_SUCCESS和EXIT_FAILURE分别代表0和1,这两个常量是在stdlib.h中定义的。在main()中使用return等同于调用exit(),exit()在程序结束时会关闭所有打开的流,清空栈和堆上的内存,然后将控制权返回主机环境并报告状态。
程序结束可能还会输出某些信息到控制台,或者执行某些操作系统命令,此时有机会执行析构函数。atexit()可以接受一个函数指针作为析构函数,当程序结束时会先调用析构函数,然后再执行exit()。例如:
#include
#include
void tellEnd()
{
puts("The end!");
}
int main(void)
{
atexit(tellEnd);
puts("exit");
}
传入atexit()的析构函数要求没有参数且返回类型为void,可以将多个析构函数注册给atexit(),ANSI C保证析构函数列表不少于32个。
Sleep()函数也包含在window.h中,它既可以暂停进程又可以暂停线程,在window中单位为毫秒,在linux中单位为秒,sleep()常常用于死循环中留出时间响应键盘按键操作,或让一个线程等待一定时间让其它线程优先执行。下例每隔1秒钟调用echo命令显示一行文字。
#include
#include
#include
int main(void)
{
long t=clock();
char str1[20];
char str2[30]="";
for (int i = 0; i < 10; i++)
{
t = clock() - t;
ltoa(t, str1, 10);
memset(str2, 0, sizeof(str2)); //清空字符串str2
strcat(str2, "echo ");
strcat(str2, str1);
system(str2);
Sleep(1000);
}
}
system()包含在windows.h中,windows系统中system()直接在控制台调用一个command命令,在Linux/Unix系统中,system函数会调用fork函数产生子进程,由子进程来执行command命令,命令执行完后随即返回原调用的进程。例如在windows中打开记事本:
#include
#include
int main()
{
system("notepad");
return(0);
}
在window中调用system()需要导入Windows.h,system()除了调用控制台命令外还经常用于打开其它程序,本例中调用了记事本notepad.exe,system()还可以调用一个bat程序,而bat也可以调用C生成的exe,因此C语言编写的命令可以和系统命令互相调用,实际上系统中很多命令也是C写的。
当我们使用生成的exe调用外部命令时,system()首先搜索当前目录下有没有执行文件,没有则去系统环境变量设置的全局路径中搜索,因为记事本在windows的全局变量路径中,所以可以直接打开。对于当前目录来说有些IDE的当前目录就是exe所在的目录,有些则是项目所在的目录。对于某些版本的VS来说当前目录是项目文件.vcxproj文件所在的目录,要知道当前目录是什么可以使用system(“dir”)来显示。当解决方案中有多个项目时,VS会自动将其它项目生成的exe放入到公用调试目录中方便我们调用,但可能由于当前目录非exe所在的目录依然不能直接调用,而使用system(“cd mydir”)命令经测试也无法修改当前目录,这就给调用带来了麻烦,要设置当前目录,可以通过vs中的项目属性面板修改,如下:
在调试选项中将工作目录设置为exe发布目录,但修改项目当前目录会影响外部静态库或动态库的相对路径,如果希望通过代码修改只能调用windows api提供的接口设置当前目录,有3个函数比较常用:
DWORD GetCurrentDirectory(DWORD nBufferLength,LPWSTR lpBuffer)
获取当前目录,将目录保存在字符串lpBuffer中,nBufferLength是要保存的字符串长度,如执行成功,返回复制到lpFileName的实际字符数。
DWORD WINAPI GetModuleFileName(HMODULE hModule, LPTSTR lpFilename, DWORD nSize)
获取exe所在的路径名(包括exe自身名称),将路径名保存到lpFilename中,nSize是要保存的字符数,hModule是一个模块的句柄。可以是一个DLL模块,或者是一个应用程序的实例句柄。如果该参数为NULL,该函数返回该应用程序全路径。
BOOL WINAPI SetCurrentDirectory(LPCTSTR lpPathName)
对于路径名来说由于可能包含中文,因此需要使用宽字符进行设置,如下:
#include
#include
#include
int main(int argc, char* argv[])
{
//获取当前目录
wchar_t curDir[MAX_PATH] = L"";
GetCurrentDirectory(MAX_PATH, curDir);
printf("%ls\n", curDir);
//获取exe文件路径名
wchar_t exeName[MAX_PATH] = L"";
GetModuleFileName(NULL, exeName, MAX_PATH);
printf("%ls\n", exeName);
puts(argv[0]);//通过main参数名获取exe文件路径名
//将exe目录从路径名中解析出来
wchar_t exeDir[MAX_PATH] = L"";
wcsncpy(exeDir, exeName, wcsrchr(exeName, L'\\') -exeName);
printf("%ls\n",exeDir);
//设置当前目录为exe目录
SetCurrentDirectory(exeDir);
//通过dir命令显示当前目录
system("dir");
}
MAX_PATH是一个常量,它表示windows能显示的路径名最大字符数,通过GetModuleFileName()和main()函数参数都可以获得exe文件路径名,区别是main()函数参数返回的是窄字符,需要转换为宽字符才能进行路径设置。通过GetCurrentDirectory()和system(“dir”)都可以显示当前目录,一个是调用Windows api,一个是调用系统命令。
在windows中我们经常通过bat文件执行多个命令,用C也可以,例如有两个程序Pro1和Pro2,Pro1执行完后调用Pro2,Pro2执行完后调用系统命令,可以使用文件传递数据,如下:
Pro1.c
#include
#include
int main()
{
FILE* fp;
if ((fp = fopen("abc.txt", "w")) == NULL) puts("文件打开失败");
else fputs("Pro1 is a Process.",fp);
fclose(fp);
system("Pro2.exe");
}
Pro2.c
#include
#include
int main()
{
FILE* fp;
char str[20];
if ((fp = fopen("abc.txt", "r")) == NULL) puts("文件打开失败");
else fgets(str,20, fp);
fclose(fp);
char echoStr[30] = "echo ";
system(strcat(echoStr,str));
}
进程Pro1将文本写入到abc.txt中,然后调用进程Pro2读出该文本,Pro2调用系统echo命令显示文本内容。我们也可以不在Pro1中调用Pro2,而是编写一个Demo程序同时调用它们,现在去掉Pro1中的system(“Pro2.exe”)语句,然后在Demo中编写如下代码:
#include
#include
int main(void)
{
system("Pro1.exe");
system("Pro2.exe");
}
同时调用Pro1和Pro2不会发生读写冲突,因为使用system()调用外部命令时如同在bat中编写命令,只有等待上一个进程运行结束后才会执行下一个命令,可以修改Demo代码进程测试:
#include
#include
int main(void)
{
system("notepad");
system("notepad");
}
结果是只有关闭上一个记事本,下一个记事本才会打开,因为这个原因,实际上在同一时刻保证只有一个进程运行,因此是不会产生同时读写的。我们也可以不选择文件传输数据而是通过发送main()参数传递数据,例如:
Pro1.c
#include
int main(int argc, char* argv[])
{
while (argc--) puts(*argv++);
return 0;
}
Pro2.c
#include
int main(int argc, char* argv[