C语言玩转多进程


多进程

在一个项目中并发执行任务时多数情况下都会选择多线程,但有时候也会选择多进程,例如可以同时运行n个记事本编辑不同文本,由一个命令跳转到另外一个命令,或者使用不同进程进行协作。在学习多进程之前我们必须了解一个进程如何获取进程,进程如何结束,如何调用外部进程等操作。

退出程序

自ANSI C开始就支持退出程序和析构,无论在任何时候调用exit()都会直接退出程序,exit()可以向主机环境返回退出结果,exit(0)表示正常退出,其他值表示退出异常,EXIT_SUCCESS和EXIT_FAILURE分别代表0和1,这两个常量是在stdlib.h中定义的。在main()中使用return等同于调用exit(),exit()在程序结束时会关闭所有打开的流,清空栈和堆上的内存,然后将控制权返回主机环境并报告状态。

析构函数

程序结束可能还会输出某些信息到控制台,或者执行某些操作系统命令,此时有机会执行析构函数。atexit()可以接受一个函数指针作为析构函数,当程序结束时会先调用析构函数,然后再执行exit()。例如:

#include <stdio.h>
#include<stdlib.h>

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 <stdio.h>
#include<windows.h>
#include<time.h>

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 <stdio.h>
#include<Windows.h>

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 <stdio.h>
#include<windows.h>
#include<wchar.h>

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<stdio.h>
#include<Windows.h>

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<stdio.h>
#include<Windows.h>

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 <stdio.h>
#include<windows.h>

int main(void)
{
   
	system("Pro1.exe");
	system("Pro2.exe");
}

同时调用Pro1和Pro2不会发生读写冲突,因为使用system()调用外部命令时如同在bat中编写命令,只有等待上一个进程运行结束后才会执行下一个命令,可以修改Demo代码进程测试:

#include <stdio.h>
#include<windows.h>

int main(void)
{
   
	system("notepad");
	system("notepad");
}

结果是只有关闭上一个记事本,下一个记事本才会打开,因为这个原因,实际上在同一时刻保证只有一个进程运行,因此是不会产生同时读写的。我们也可以不选择文件传输数据而是通过发送main()参数传递数据,例如:

Pro1.c
#include<stdio.h>

int main(int argc, char* argv[])
{
   
	while (argc--) puts(*argv++);
	return 0;
}

Pro2.c
#include<stdio.h>

int main(int argc
  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值