進程間的通信(一)

進程間有很多的通信方式,包括:管道(pipe),命名管道(named pipe),信號(signal),消息(message)隊列,共享內存,內存映射(mapped memory),信號量以及套接口(socket)。下面就逐一的介紹着幾種通信方式。

一、 管道

管道是一種半雙工的通信方式,用於具有親緣關係的進程進行單方向的通信。linux用pipe函數創建一個管道,其函數原型爲:

#include <unistd.h>
int pipe(int fd[2]);
參數fd[2]是一個文件描述符數組,fd[1]爲寫入文件描述符,fd[0]爲讀出文件描述符。當建立一個管道的時候,可以實現父子進程間的通信,這是父子進程可以對fd的讀寫文件描述符進行操作,由於管道式一種單方向的通信,因此父子進程中要關閉讀或寫的文件描述符。下面舉個實例進行解釋:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
#define BUFS 1024

int main()
{
	int fd[2];
	pid_t pid,pr;
	char buf[BUFS];
	int len;
	int status;
	if(pipe(fd)<0)
	{
		printf("pipe initialization failed.");
		exit(1);
	}
	
	if((pid =fork())<0)
	{
		printf("fail to fork.");
		exit(1);
	}
	
	if(pid > 0) //父進程的程序段
	{
		close(fd[1]); //關閉父進程的寫文件描述符
		pr = wait(&status); // 調用wait()函數防止殭屍進程的產生
		printf("I am father!\n");
		len = read(fd[0],buf, BUFS);
		if(len<11)
		{
			printf("read failed.");
			exit(1);
		}
		printf("recv message is %s\n", buf);
		exit(0);
	}
	else //子進程的程序段
	{
		close(fd[0]);//關閉子進程的讀文件描述符
		printf("I am child!");
		write(fd[1], "I success!\n", 11);
		exit(0);
	}
	
}
上面程序的輸出結果是:

I am child!
I am father!
recv message is: I success! 
並且管道可以多次申請,最終在程序結束後由系統收回。下面就是一個父進程與它的兩個子進程之間的通信的例子。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>

#define BUFS 1024


int main()
{
	int fd[2];
	int fdsecond[2];
	pid_t pid,pr;
	char buf[BUFS], bufsecond[BUFS];
	memset(buf,0, BUFS);
	int len, lensecond;
	int status;
	pid_t pidsecond, pidsecondr;
	
	if(pipe(fd)<0)
	{
		printf("pipe initialization failed.");
		exit(1);
	}
	
	if((pid =fork())<0)
	{
		printf("fail to fork.");
		exit(1);
	}
	
	if(pid > 0)
	{
		close(fd[1]);
		
		pr = wait(&status);
		printf("I am father!\n");
		len = read(fd[0],buf, BUFS);
		if(len<11)
		{
			printf("read failed.");
			exit(1);
		}
		printf("recv from first child is: %s\n", buf);
		
		
	}
	else
	{
		close(fd[0]);
		printf("I am first child!\n");
		write(fd[1], "I success(1)!\n", 14);
		exit(0);
	}
	if(pipe(fdsecond)<0)
	{
		printf("pipe initialization failed.");
		exit(1);
	}
	if((pidsecond = fork()) < 0)
	{
		printf("fail to fork.");
		exit(1);	
	}
	if(pidsecond > 0)
	{
		close(fdsecond[1]);
		
		pidsecondr = wait(&status);
		printf("I am father!\n");
		lensecond = read(fdsecond[0],bufsecond,BUFS );
		
		printf("recv from second child is: %s\n", bufsecond);
		
	}
	else
	{
		close(fdsecond[0]);
		//sleep(2);
		//sem_wait(&sem_id);
		printf("I am second child!\n");
		write(fdsecond[1], "I success(2)!\n", 14);
		//sem_post(&sem_id);
		exit(0);
	}
	exit(0);
	
}
輸出結果爲:

I am first child!
I am father!
recv from first child is: I success(1)!

I am second child!
I am father!
recv from second child is: I success(2)!



二、信號量

信號量主要作爲不同進程間和同一進程不同線程間的通信。信號量包括無名信號量和有名信號量。首先對無名信號量做一個簡單的介紹。無名信號量可以動態啓動。其中函數sem_init()用於初始化一個信號量,他帶有一個參數N,表示可用的資源數。在初始化一個信號量之後,一個線程在使用一個資源之前必須調用函數sem_wait()或者sem_trywait()等待一個可用的信號量,並在用完資源之後,還需要調用函數sem_pos()來返還資源。下面通過舉例來解釋信號量的應用。例子是在同一進程中不同線程上實現同步和互斥的。

#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>

int num = 0; //設置全局變量
sem_t sem_id; //定義semaphore變量
void * thread_one_func(void *arg)
{
	sem_wait(&sem_id);
	printf("the thread one has the semaphore!");
	int i;
	for(i = 0; i < 5; i++)
		num++;
	printf("number = %d \n", num);
	sem_post(&sem_id);
}

void *thread_two_func(void *arg)
{
	sem_wait(&sem_id); //程序被掛起,直到等到需要的資源
	printf("the thread two has the semaphore!");
	int i;
	for(i =0; i< 5; i++)
		num --;
	printf("number = %d\n", num);
	sem_post(&sem_id); //程序結束的時候,要返還自己佔用的資源

}

int main()
{
	pthread_t pid1, pid2;
	sem_init(&sem_id, 0,1);//初始化sem_id;函數中的第二個參數表示線程間通信(如果爲1則爲進程間通信);第三個參數是可用資源的數目
	pthread_create(&pid1, NULL, thread_one_func, NULL);
	pthread_create(&pid2, NULL, thread_two_func, NULL);
	pthread_join(pid1, NULL);
	pthread_join(pid2, NULL);
	printf("main... \n");
	return 0;
}

上面例子的輸出結果爲:

the thread one has the semaphore!number = 5 
the thread two has the semaphore!number = 0
main... 
如果改變main函數中的兩個線程的創建順序,即

pthread_create(&pid2, NULL, thread_two_func, NULL);
pthread_create(&pid1, NULL, thread_one_func, NULL);

那麼輸出結果爲:

the thread two has the semaphore!number = -5
the thread one has the semaphore!number = 0 
main... 

備註:一個進程中的線程的可以設置很多的屬性,如:分離狀態,繼承性,調度策略等等。而且多線程的發生在不加鎖或者其他線程同步互斥控制的時候是隨機的。舉一個比較經典的例子:兩個線程分別對全局變量num;進行自加和自減10次,那麼該全局變量的最終結果爲:

答案應該是-10~10之間。因爲自加和自減不是原子操作;他們分別包括:

讀取內存到寄存器;

寄存器自增或自減操作;

將寄存器數據讀入內存;

其次看下編譯器是怎麼編譯的,某些編譯器比如VC在非優化版本中會編譯爲以下彙編代碼:

__asm
{
        mov eax,  dword ptr[i]
        inc eax
        mov dword ptr[i], eax
}

因此,由於兩個線程之間的發生是隨機的,而且線程具有私有的寄存器和棧內存的,他們對內存的訪問不是互斥的,由於他們訪問內存的先後順序的不同因此產生的結果不同。即使給變量num加上volatile的修飾符也沒用,因爲volatile是確保編譯器不會對被其修飾的變量做優化,即每次訪問該變量的時候,都要去內存中讀取。但是多線程仍會引起對內存寫的重複操作。(猜測,因爲我在虛擬機下的ubuntu 運行的多線程結果總是一樣的,可能是和虛擬機下的ubuntu只有一個cpu的原因有關)。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章