【原文:http://blog.csdn.net/u011000290/article/details/48108557】
在一個大型的應用系統中,往往需要多個程式相互協作,程式間通訊(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();
-
}
IPC
程式間可以共享記憶體,程式建立具有共享屬性的記憶體區域後,另一個程式可以開啟此記憶體區域,並將其對映到自己的地址空間。共享記憶體可以使用檔案對映函式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]);
-
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);
-
-
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);
-
-
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)、執行緒的優先順序等。