在一个大型的应用系统中,往往需要多个进程相互协作,进程间通信(IPC,Inter Process Communication)就显得比较重要了。在Linux系统中,有很多种IPC机制,比如说,信号(signal)、管道(pipe)、消息队列(message queue)、信号量(semaphore)和共享内存(shared memory)、套接字(socket)等,其实Windows操作系统也支持这些东西。在IBM的Developerworks发现了一篇关于Windows与Linux 之间IPC机制API比较的文章,写得很不错,链接
http://www.ibm.com/developerworks/cn/linux/l-ipc2lin1.html
下面大部分内容是关于这些机制的API的实现。
进程的创建可以调用CreateProcess函数,CreateProcess有三个重要的参数,运行进程的名称、指向STARTUPINFO结构的指针、指向PROCESS_INFORMATION结构的指针。其原型如下:
BOOL CreateProcess ( LPCTSTRlpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes。 LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFOlpStartupInfo, LPPROCESS_INFORMATIONlpProcessInformation );
给个例子,如果启动时应用程序带有命令行参数,进程将输出命令行参数,并创建一个不带任何参数的子线程;如果不带有任何参数,则会输出一条提示消息。
#include <Windows.h> #include <tchar.h> #include <iostream> using namespace std; int _tmain(int argc, _TCHAR* argv[]){ STARTUPINFO startup_info; PROCESS_INFORMATION process_info; if (argc>1) { cout<<"Argument"<<argv[1]<<endl; cout<<"开启子线程"<<endl; ZeroMemory(&process_info,sizeof(process_info)); ZeroMemory(&startup_info,sizeof(startup_info)); startup_info.cb=sizeof(startup_info); if (CreateProcess(argv[0],0,0,0,0,0,0,0,&startup_info,&process_info)==0) { cout<<"Error"<<endl; } WaitForSingleObject(process_info.hProcess,INFINITE); } else{ cout<<"No arguments"<<endl; } getchar(); }
再给个例子,利用CreateProcess开启一个新线程,启动IE浏览器,打开百度的主页,5s后再将其关闭。
#include <Windows.h> #include <tchar.h> #include <iostream> using namespace std; #define IE L"C:\\Program Files\\Internet Explorer\\iexplore.exe" #define CMD L"open http://www.baidu.com/" int _tmain(int argc, _TCHAR* argv[]){ STARTUPINFO startup_info; GetStartupInfo(&startup_info); PROCESS_INFORMATION process_info; startup_info.dwFlags=STARTF_USESHOWWINDOW; startup_info.wShowWindow=SW_HIDE; if (!CreateProcess(IE,CMD,NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL,&startup_info,&process_info)) { cout<<"Create Process Error:"<<GetLastError()<<endl; return 0; } Sleep(5000); TerminateProcess(process_info.hProcess,0); return 0; }
被创建的句柄通过process_info.hProcess返回。如果传递参数给新的进程,第一个命令行参数必须重复应用程序名称,整个命令行会被传递给子进程。
传递参数给新进程。
#include <Windows.h> #include <tchar.h> #include <iostream> using namespace std; int _tmain(int argc, _TCHAR* argv[]){ STARTUPINFO startup_info; PROCESS_INFORMATION process_info; if (argc==1) { cout<<"No arguments given starting child process"<<endl; wchar_t argument[256]; wsprintf(argument,L"\"%s\" Hello",argv[0]); ZeroMemory(&process_info,sizeof(process_info)); ZeroMemory(&startup_info,sizeof(startup_info)); startup_info.cb=sizeof(startup_info); if (CreateProcess(argv[0],argument,0,0,0,0,0,0,&startup_info,&process_info)==0) { cout<<"Error "<<GetLastError()<<endl; } WaitForSingleObject(process_info.hProcess,INFINITE); } else{ cout<<"Argument "<<argv[1]<<endl; } getchar(); }
进程间可以共享内存,进程建立具有共享属性的内存区域后,另一个进程可以打开此内存区域,并将其映射到自己的地址空间。共享内存可以使用文件映射函数CreateFileMapping,创建共享内存区域的句柄,通过MapViewOfFile()把这个区域映射到进程,然后再连接到现有的共享内存区域,可以通过OpenFileMapping获得句柄。在进程使用完共享内存后,需要调用UnmapViewOfFile()取消映射,再调用CloseHandle()关闭相应的句柄,避免内存泄露。
给个例子,如果启动时应用程序不带任何参数,应用程序会创建一个子进程。父进程也将建立一个共享内存区域,并将一个字符串保存到共享内存。共享内存取名为sharedmemory,在Local\命名空间中创建,即该共享内存对该用户所有的全部进程可见。(进程的命名空间分为两种,全局命名空间以Global\标识符开头,本地命名空间以Local\开头)
#include <Windows.h> #include <tchar.h> #include <iostream> using namespace std; int _tmain(int argc, _TCHAR* argv[]){ STARTUPINFO startup_info; PROCESS_INFORMATION process_info; HANDLE filehandle; TCHAR ID[]=TEXT("Local\\sharedmemory"); char* memory; if (argc==1) { filehandle=CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,1024,ID); memory=(char*)MapViewOfFile(filehandle,FILE_MAP_ALL_ACCESS,0,0,0); sprintf_s(memory,1024,"%s","Data from first process"); cout<<"First process:"<<memory<<endl; ZeroMemory(&process_info,sizeof(process_info)); ZeroMemory(&startup_info,sizeof(startup_info)); startup_info.cb=sizeof(startup_info); wchar_t cmdline[256]; wsprintf(cmdline,L"\"%s\" Child\n",argv[0]); CreateProcessW(argv[0],cmdline,0,0,0,0,0,0,&startup_info,&process_info); WaitForSingleObject(process_info.hProcess,INFINITE); UnmapViewOfFile(memory); CloseHandle(filehandle); } else{ filehandle=OpenFileMapping(FILE_MAP_ALL_ACCESS,0,ID); memory=(char*)MapViewOfFile(filehandle,FILE_MAP_ALL_ACCESS,0,0,0); cout<<"Second process: "<<memory; UnmapViewOfFile(memory); CloseHandle(filehandle); } getchar(); return 0; }
从结果可以看出,子进程连接到共享内存,并能输出父进程存储在那里的字符串。子进程输出字符串以后,就取消内存映射,关闭文件句柄,然后退出。子进程退出后,父进程就可以取消内存映射、关闭文件句柄并退出。
子进程可以继承父进程所有资源的句柄,最简单的方法是通过命令行传递值。
#include <Windows.h> #include <tchar.h> #include <iostream> using namespace std; int _tmain(int argc, _TCHAR* argv[]){ STARTUPINFO startup_info; PROCESS_INFORMATION process_info; SECURITY_ATTRIBUTES sa; HANDLE filehandle; TCHAR ID[]=TEXT("Local\\sharedmemory"); wchar_t* memory; if (argc==1) { //父进程 sa.nLength=sizeof(sa);//设置安全属性 sa.bInheritHandle=TRUE;//使句柄可以被继承 sa.lpSecurityDescriptor=NULL; filehandle=CreateFileMapping(INVALID_HANDLE_VALUE,&sa,PAGE_READWRITE,0,1024,ID); memory=(wchar_t*)MapViewOfFile(filehandle,FILE_MAP_ALL_ACCESS,0,0,0); //用共享内存设置命令行 swprintf(memory,1024,L"\"%s\" %i",argv[0],filehandle); cout<<"First process memory:"<<memory<<" handle: "<<filehandle<<endl; ZeroMemory(&process_info,sizeof(process_info)); ZeroMemory(&startup_info,sizeof(startup_info)); startup_info.cb=sizeof(startup_info); //启动子进程 CreateProcess(NULL,memory,0,0,true,0,0,0,&startup_info,&process_info); WaitForSingleObject(process_info.hProcess,INFINITE); UnmapViewOfFile(memory); CloseHandle(filehandle); } else{ filehandle=(HANDLE)_wtoi(argv[1]);//从argv[1]获得句柄 memory=(wchar_t*)MapViewOfFile(filehandle,FILE_MAP_ALL_ACCESS,0,0,0); cout<<"Second process memory : "<<memory<<" handle: "<<filehandle<<endl; UnmapViewOfFile(memory); CloseHandle(filehandle); } getchar(); return 0; }
进程间共享互斥量,可以通过调用CreateMutex或者OpenMutex函数来获取互斥量的句柄。但是,只有一个进程可以创建互斥量,其他的进程只能打开现有的互斥量;互斥量的名称必须唯一;互斥量的名称必须传递给其他进程。
#include <Windows.h> #include <tchar.h> #include <iostream> using namespace std; int _tmain(int argc, _TCHAR* argv[]){ HANDLE sharedmutex; STARTUPINFO startup_info; PROCESS_INFORMATION process_info; ZeroMemory(&process_info,sizeof(process_info)); ZeroMemory(&startup_info,sizeof(startup_info)); startup_info.cb=sizeof(startup_info); sharedmutex=CreateMutex(0,0,L"mymutex"); if (GetLastError()!=ERROR_ALIAS_EXISTS) { if (CreateProcess(argv[0],0,0,0,0,0,0,0,&startup_info,&process_info)==0) { cout<<"Error : "<<GetLastError()<<endl; } WaitForSingleObject(process_info.hProcess,INFINITE); } WaitForSingleObject(sharedmutex,INFINITE); for (int i=0;i<100;i++) { cout<<"Process "<<GetCurrentProcessId()<<" count"<<i<<endl; } ReleaseMutex(sharedmutex); CloseHandle(sharedmutex); getchar(); return 0; }
使用共享互斥量来确保两个进程中一次只有一个能计数从0数到19,如果没有互斥量的话,那么两个进程可能同时在跑,则控制台的输出将是混合的输出,使用互斥量以后,一次只有一个进程在输出。
也可以用管道进行通信,管道是流式通信的一种方式,管道有两种命名管道和匿名管道。匿名管道的创建可以调用CreatePipe(),创建命名管道可以调用CreateNamedPipe(),调用WriteFile通过管道发送数据,ReadFile从管道读取数据。
#include <Windows.h> #include <tchar.h> #include <process.h> #include <iostream> #include <stdio.h> using namespace std; HANDLE readpipe,writepipe; unsigned int __stdcall stage1(void * param) { char buf[200]; DWORD len; for (int i=0;i<10;i++) { sprintf(buf,"Text %i",i); WriteFile(writepipe,buf,strlen(buf)+1,&len,0); } CloseHandle(writepipe); return 0; } unsigned int __stdcall stage2(void * param) { char buf[200]; DWORD len; while(ReadFile(readpipe,buf,200,&len,0)) { DWORD offset=0; while(offset<len) { cout<<&buf[offset]<<endl; offset+=strlen(&buf[offset])+1; } } CloseHandle(readpipe); return 0; } int _tmain(int argc, _TCHAR* argv[]){ HANDLE thread1,thread2; CreatePipe(&readpipe,&writepipe,0,0); thread1=(HANDLE)_beginthreadex(0,0,&stage1,0,0,0); thread2=(HANDLE)_beginthreadex(0,0,&stage2,0,0,0); WaitForSingleObject(thread1,INFINITE); WaitForSingleObject(thread2,INFINITE); getchar(); return 0; }
第一个线程将文本信息放入管道,第二个线程接收并输出这些信息。
还可以用套接字进行通信。WindowsSockets API以BSD Sockets API为基础,与类UNIX操作系统的代码很相似。
#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include <Windows.h> #include <tchar.h> #include <process.h> #include <WinSock2.h> #include <iostream> #include <stdio.h> #pragma comment(lib,"ws2_32.lib") using namespace std; HANDLE hevent; //响应线程 void handleecho(void *data) { char buf[1024]; int count; ZeroMemory(buf,sizeof(buf)); int socket=(int)data; while((count=recv(socket,buf,1023,0))>0) { cout<<"received "<<buf<<"from client"<<endl; int ret=send(socket,buf,count,0); } cout<<"close echo thread"<<endl; shutdown(socket,SD_BOTH); closesocket(socket); } //客户端线程 unsigned int __stdcall client(void *data) { SOCKET ConnectSockket=socket(AF_INET,SOCK_STREAM,0); WaitForSingleObject(hevent,INFINITE); struct sockaddr_in server; ZeroMemory(&server,sizeof(server)); server.sin_family=AF_INET; server.sin_addr.s_addr=inet_addr("192.168.1.107"); server.sin_port=7780; connect(ConnectSockket,(struct sockaddr*)&server,sizeof(server)); cout<<"send 'abcd' to server"<<endl; char buf[1024]; ZeroMemory(buf,sizeof(buf)); strncpy_s(buf,1024,"abcd",5); send(ConnectSockket,buf,strlen(buf)+1,0); ZeroMemory(buf,sizeof(buf)); recv(ConnectSockket,buf,1024,0); //cout<<"get "<<buf<<"from server"<<endl; printf("get '%s' from server\n",buf); cout<<"close client"<<endl; shutdown(ConnectSockket,SD_BOTH); closesocket(ConnectSockket); return 0; } //服务器线程 unsigned int __stdcall server(void *data) { SOCKET newsocket; SOCKET ServerSocket=socket(AF_INET,SOCK_STREAM,0); struct sockaddr_in server; ZeroMemory(&server,sizeof(server)); server.sin_family=AF_INET; server.sin_addr.s_addr=INADDR_ANY; server.sin_port=7780; bind(ServerSocket,(struct sockaddr*)&server,sizeof(server)); listen(ServerSocket,SOMAXCONN); SetEvent(hevent); while((newsocket=accept(ServerSocket,0,0))!=INVALID_SOCKET) { HANDLE newthread; newthread=(HANDLE)_beginthread(&handleecho,0,(void *)newsocket); } cout<<"close server"<<endl; shutdown(ServerSocket,SD_BOTH); closesocket(ServerSocket); return 0; } //主线程启动客户端线程和服务端线程 int _tmain(int argc, _TCHAR* argv[]){ HANDLE serverthread,clienthread; WSADATA wsaData; WSAStartup(MAKEWORD(2,2),&wsaData); hevent=CreateEvent(0,true,0,0); serverthread=(HANDLE)_beginthreadex(0,0,&server,0,0,0); clienthread=(HANDLE)_beginthreadex(0,0,&client,0,0,0); WaitForSingleObject(clienthread,INFINITE); CloseHandle(clienthread); CloseHandle(hevent); getchar(); WSACleanup(); return 0; }
服务器线程的第一个操作是打开一个套接字,接着绑定连接。套接字置于监听状态,值SOMAXCONN包含排队等待接受的连接的最大值。然后服务器发信号给事件,事件继而使客户端线程尝试连接。接着,主线程循环等待接受连接,直到收到INVALID_SOCKET的连接。Windows套接字关闭时会发生这种情况。服务器线程在其他线程退出后清理退出。服务器每次接受一个连接时都会创建一个新线程,且新连接的标识会传递给新创建的线程。当循环收到INVALID_SOCKET时,服务器线程关闭,然后关闭套接字。
Windows API也提供了很多原子操作,互锁函数。InterlockedIncrement就是一个互锁函数。
#include <Windows.h> #include <tchar.h> #include <process.h> #include <iostream> using namespace std; int isPrime(int num) { int i; for (i=2;i<(int)(sqrt((float)num)+1.0);i++) { if (num%i==0) return 0; } return 1; } volatile long counter=2; unsigned int __stdcall test(void *) { while (counter<20) { int num=InterlockedIncrement(&counter); //int num=counter++; printf("Thread ID : %i; value = %i, is prime = %i\n",GetCurrentThreadId(),num,isPrime(num)); } return 0; } int _tmain(int argc,_TCHAR* argv[]) { HANDLE h1,h2; h1=(HANDLE)_beginthreadex(0,0,&test,(void *)0,0,0); h2=(HANDLE)_beginthreadex(0,0,&test,(void *)0,0,0); WaitForSingleObject(h1,INFINITE); WaitForSingleObject(h2,INFINITE); CloseHandle(h1); CloseHandle(h2); getchar(); return 0; }
还有一个问题就是线程本地存储(TLS, ThreadLocal Storage),TLS 是一个机制,利用该机制,程序可以拥有全局变量,但处于“每一线程各不相同”的状态。也就是说,进程中的所有线程都可以拥有全局变量,但这些变量其实是特定对某个线程才有意义,各个线程拥有全局变量的一个副本,各自之间不相影响。每个线程访问数据的方式相同,但看不到其他线程持有的值。比如说,定义一个全局变量int a=10,那么在线程1中对a进行操作a=a-1,如果没用TLS,那么线程2开始获得的a就是9。但是,如果采取了TLS,不管线程1中对a的值进行了如何的修改操作,其他的线程一开始获得的a还是10,不会被修改。这个全局的变量a是没有存储在线程堆栈中的,是在全局的堆栈中,但是却被各个线程“共享”且互不影响。可以认为线程本地存储的本质是“全局”数据的作用域受到了执行线程的限制。
线程本地分配可以调用__declspec、TlsAlloc()等函数。TlsAlloc可以分配全局索引,该索引由所有线程共享,但是每个线程存储在索引中的数据为调用的线程私有,也就是说其他线程看不到持有的值。当不再需要全局索引提供线程本地存储时,可以调用TlsFree来释放全局索引。
给个例子。
#include <Windows.h> #include <tchar.h> #include <process.h> #include <iostream> using namespace std; DWORD TLSIndex; void setdata(int value) { cout<<"Thread "<<GetCurrentThreadId()<<": set value = "<<value<<endl; TlsSetValue(TLSIndex,(void*)value); } void getdata() { int value; value=(int)TlsGetValue(TLSIndex); cout<<"Thread "<<GetCurrentThreadId()<<": has value = "<<value<<endl; } unsigned int __stdcall workthread(void *data) { int value=(int)data; cout<<"Thread "<<GetCurrentThreadId()<<": got value = "<<value<<endl; setdata(value); Sleep(1000); getdata(); return 0; } int _tmain(int argc,_TCHAR* argv[]) { HANDLE h[8]; TLSIndex=TlsAlloc(); for (int i=0;i<8;i++) { h[i]=(HANDLE)_beginthreadex(0,0,&workthread,(void*)i,0,0); } for (int i=0;i<8;i++) { WaitForSingleObject(h[i],INFINITE); } TlsFree(TLSIndex); getchar(); return 0; }
线程本地存储用于保存传给各个线程的值,每个线程在被创建的时候就被传递一个唯一的值,并通过setdata存储在线程本地存储中。getdata可以读取线程本地值,每个线程调用setdata方法,接着休眠1s让其他线程运行,然后调用getdata读取数据。
还有个问题,就是优先级的问题。线程的优先级越高,获得的CPU资源(时间)就越多。在有些情况下,调整一个应用程序中不同线程的优先级会非常有用。比如说,当某个应用执行一个长时间的后台任务时,为了保证机器的高响应性,这个后台任务最好以低优先级运行。
Windows操作系统中提供了相关的API。
#include <Windows.h> #include <tchar.h> #include <process.h> #include <iostream> #include <time.h> using namespace std; unsigned int __stdcall fastthread(void *data) { double d=1.0; cout<<"fast thread started"<<endl; SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_ABOVE_NORMAL); clock_t start=clock(); for (int i=0;i<1000000000;i++) { d+=i; } clock_t end=clock(); cout<<"fast thread finished, it takes "<<(double)(end-start)/CLOCKS_PER_SEC<<"s to finish the task"<<endl; return 0; } unsigned int __stdcall slowthread(void *data) { double d=0.0; cout<<"slow thread started"<<endl; SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_BELOW_NORMAL); clock_t start=clock(); for (int i=0;i<1000000000;i++) { d+=i; } clock_t end=clock(); cout<<"slow thread finished, it takes "<<(double)(end-start)/CLOCKS_PER_SEC<<"s to finnish the task"<<endl; return 0; } int _tmain(int argc,_TCHAR* argv[]) { HANDLE fast,slow; slow=(HANDLE)_beginthreadex(0,0,&slowthread,0,0,0); fast=(HANDLE)_beginthreadex(0,0,&fastthread,0,0,0); WaitForSingleObject(fast,INFINITE); WaitForSingleObject(slow,INFINITE); getchar(); return 0; }
有时候调整线程的优先级会带来优先级反转的问题。
主要实现了windows操作系统中IPC的API,主要有进程之间共享内存、子进程中继承句柄、互斥量、管道、套接字等。此外,还有Windows中的互锁函数。线程本地化存储(TLS)、线程的优先级等。