在一個大型的應用系統中,每每須要多個進程相互協做,進程間通訊(IPC,Inter Process Communication)就顯得比較重要了。在Linux系統中,有不少種IPC機制,好比說,信號(signal)、管道(pipe)、消息隊列(message queue)、信號量(semaphore)和共享內存(shared memory)、套接字(socket)等,其實Windows操做系統也支持這些東西。在IBM的Developerworks發現了一篇關於Windows與Linux 之間IPC機制API比較的文章,寫得很不錯,連接html
http://www.ibm.com/developerworks/cn/linux/l-ipc2lin1.htmllinux
下面大部份內容是關於這些機制的API的實現。ios
進程的建立能夠調用CreateProcess函數,CreateProcess有三個重要的參數,運行進程的名稱、指向STARTUPINFO結構的指針、指向PROCESS_INFORMATION結構的指針。其原型以下:windows
[cpp] view plaincopyprint?瀏覽器
BOOL CreateProcess 安全
( 服務器
LPCTSTRlpApplicationName, app
LPTSTR lpCommandLine, socket
LPSECURITY_ATTRIBUTES lpProcessAttributes。 函數
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFOlpStartupInfo,
LPPROCESS_INFORMATIONlpProcessInformation
);
給個例子,若是啓動時應用程序帶有命令行參數,進程將輸出命令行參數,並建立一個不帶任何參數的子線程;若是不帶有任何參數,則會輸出一條提示消息。
[cpp] view plaincopyprint?
#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後再將其關閉。
[cpp] view plaincopyprint?
#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返回。若是傳遞參數給新的進程,第一個命令行參數必須重複應用程序名稱,整個命令行會被傳遞給子進程。
傳遞參數給新進程。
[cpp] view plaincopyprint?
#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\開頭)
[cpp] view plaincopyprint?
#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;
}
從結果能夠看出,子進程鏈接到共享內存,並能輸出父進程存儲在那裏的字符串。子進程輸出字符串之後,就取消內存映射,關閉文件句柄,而後退出。子進程退出後,父進程就能夠取消內存映射、關閉文件句柄並退出。
子進程能夠繼承父進程全部資源的句柄,最簡單的方法是經過命令行傳遞值。
[cpp] view plaincopyprint?
#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函數來獲取互斥量的句柄。可是,只有一個進程能夠建立互斥量,其餘的進程只能打開現有的互斥量;互斥量的名稱必須惟一;互斥量的名稱必須傳遞給其餘進程。
[cpp] view plaincopyprint?
#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從管道讀取數據。
[cpp] view plaincopyprint?
#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操做系統的代碼很類似。
[cpp] view plaincopyprint?
#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就是一個互鎖函數。
[cpp] view plaincopyprint?
#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來釋放全局索引。
給個例子。
[cpp] view plaincopyprint?
#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。
[cpp] view plaincopyprint?
#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)、線程的優先級等。