进程间通信(IPC)以及同步

来源:互联网 发布:酷炫爆炸js网站效果 编辑:程序博客网 时间:2024/06/10 12:03

临界区的概念: 把涉及对共享内存访问的程序片段称为临界区。临界区的提出是为了防止多个进程同时处于其中,避免竞争条件。


http://www.cnblogs.com/skyofbitbit/p/3651750.html

http://www.cnblogs.com/applebunny/archive/2012/07/11/2586483.html

http://jingyan.baidu.com/article/3a2f7c2e17e12b26afd611cb.html

1.共享内存

      共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式

2. 信号量 

         信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。一般说来,为了获得共享资源,进程需要执行下列操作: 
   (1) 测试控制该资源的信号量。 
   (2) 若此信号量的值为正,则允许进程使用该资源。进程将信号量减1。 
   (3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。 
   (4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。 

互斥量是信号量的简化版本,没有信号量的计数功能,只能为0/1,是一个二元信号灯,本质上说就是一把锁,提供对资源的独占访问,指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。

互斥量用于线程的互斥,信号量用于线程的同步,限制访问者对资源的访问顺序。

2.1 管程

   管程是一个由过程,变量以及数据结构组成的集合,结构上相当于一个类。它是一种进程同步的机制。管程的互斥机制是由编译器负责,实现临界区互斥自动化,无需程序员考虑,我们只考虑如何将临界区转换成管程即可。任一时刻只能有一个进程进入管程中。管程中引入条件变量以及wait和signal两个操作,实现自身进程的阻塞以及将等在管程之外的另一个进程调入其中。

3.管道

      管道是进程间通信中最古老的方式,一种半双工的通信方式,数据只能单向流动,它包括无名管道有名管道两种,前者用于父进程和子进程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信。 

无名管道由pipe()函数创建:    #include <unistd.h>    int pipe(int filedis[2]);    参数filedis返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。下面的例子示范了如何在父进程和子进程间实现通信。 #define INPUT 0 #define OUTPUT 1 void main() { int file_descriptors[2]; /*定义子进程号 */ pid_t pid; char buf[256]; int returned_count; /*创建无名管道*/ pipe(file_descriptors); /*创建子进程*/ if((pid = fork()) == -1) { printf("Error in fork/n"); exit(1); } /*执行子进程*/ if(pid == 0) { printf("in the spawned (child) process.../n"); /*子进程向父进程写数据,关闭管道的读端*/ close(file_descriptors[INPUT]); write(file_descriptors[OUTPUT], "test data", strlen("test data")); exit(0); } else { /*执行父进程*/ printf("in the spawning (parent) process.../n"); /*父进程从管道读取子进程写的数据,关闭管道的写端*/ close(file_descriptors[OUTPUT]); returned_count = read(file_descriptors[INPUT], buf, sizeof(buf)); printf("%d bytes of data received from spawned process: %s/n", returned_count, buf); } }    在Linux系统下,有名管道可由两种方式创建:命令行方式mknod系统调用和函数mkfifo。下面的两种途径都在当前目录下生成了一个名为myfifo的有名管道:      方式一:mkfifo("myfifo","rw");      方式二:mknod myfifo p    生成了有名管道后,就可以使用一般的文件I/O函数如open、close、read、write等来对它进行操作。下面即是一个简单的例子,假设我们已经创建了一个名为myfifo的有名管道。   /* 进程一:读有名管道*/ #include <stdio.h> #include <unistd.h> void main() { FILE * in_file; int count = 1; char buf[80]; in_file = fopen("mypipe", "r"); if (in_file == NULL) { printf("Error in fdopen./n"); exit(1); } while ((count = fread(buf, 1, 80, in_file)) > 0) printf("received from pipe: %s/n", buf); fclose(in_file); }   /* 进程二:写有名管道*/ #include <stdio.h> #include <unistd.h> void main() { FILE * out_file; int count = 1; char buf[80]; out_file = fopen("mypipe", "w"); if (out_file == NULL) { printf("Error opening pipe."); exit(1); } sprintf(buf,"this is test data for the named pipe example/n"); fwrite(buf, 1, 80, out_file); fclose(out_file); } 

4.套接字( socket ) 

    除了在异地的计算机进程间以外,套接口同样适用于本地同一台计算机内部的进程间通信。

5.消息传递系统

6.消息队列

7.消息/信号

总结:

剪切板和匿名管道只能实现同一机器两个进程间的通信,而信号量、消息、消息队列和共享内存也只能在同一台机器上的多个进程间通信;但是命名管道、邮件槽和套接字不仅可以实现同一机器上的两个进程的通信,还可以实现跨网络的进程间通信;另外,油槽和套接字可以实现一对多的通信,而命名管道只能是点对点的单一通信,但油槽的缺点是数据量较小,通常都是在424字节以下,如果数据量较大,则可以采用命名管道和套接字的方式来完成。综上:在跨网络通信中套接字无疑是最优秀的通信方式。


经典ipc问题:http://3y.uu456.com/bp-6f4c0d07eff9aef8941e0679-1.html

1.哲学家就餐问题

描述:

五位哲学家围坐一圆桌,各有一盘特别滑溜的意大利面条,面条是那样地滑溜,以致于哲学家不得不用两把叉子才能吃着,在盘与盘之间仅配备一把叉。哲学家的生活由吃与想交织而成的。当一位哲学家饥饿时,他要抓住他的左右叉,一次一把,不管先后。如果他抓到两把叉,那就吃一会儿,然后放回原处,继续想。问题的焦点集中在:你能否为每一哲学家编制一道程序,去做理当做的事情而不卡壳。

思路:一个哲学家只有在左右两个邻居都没有进餐时才能进入就餐状态。 需用一个数组记录跟踪每个哲学家的状态,是在进餐,思考,还是饥饿状态(处于正在试图拿叉子的状态)。每个哲学家状态的变化是处于临界区中的。当放下叉子时,哲学家需检查左右邻居,是否处于饥饿状态,合适条件下解除其阻塞。

#define N 5#define LEFT (i-1)%N#define RIGHT (i+1)%N#define THINKING 0#define HUNGRY 1#define EATING 2int state[N];//记录每个人状态的数组semaphore mutex=1;//临界区互斥semaphore s[N]={0};//每个哲学家一个信号量,表示能否得到两只叉子void philosopher(int i){while(1){think();take_forks(i);eat();put_forks(i);}}void take_forks(int i){donw(&mutex);state[i]=HUNGRY;test(i);//试图得到两只叉子up(&mutex);down(&s[i]);//如果得不到叉子就阻塞}void put_forks(int i){down(&mutex);stata[i]=THINKING;test(LEFT);//看下左邻居现在是否能进餐test(RIGHT);//看下右邻居现在是否能进餐up(&mutex);}void test(int i){if(state[i]==HUNGRY && state[LEFT]!=EATING && state[RIGHT]!=EATING){ state[i]=EATING;up(&s[i]);}}


2.读者-写者问题

描述:读者和写者问题模拟对数据库的访问。设想一个像飞机订票系统那样的一个大数据库,有许多希望读写它的进程在竟争。让多个进程同时读数据库是可接受的;然而,如果一个进程在写(即改变)数据库,那么,任何别的进程都不得访问该数据库,即使读也不行。你如何对读者和写者进行程序设计?

思路:解法分为读者优先、写者优先、公平竞争三种。

读者优先:只要有一个读者在使用数据库,则允许后续更大的读者进入数据库。这时若有写者来了,只能挂起,直到没有读者为止。缺点:若有一个稳定的读者流,则写者就没有进入数据库的机会。

semaphore mutex=1;//控制对rc的访问semaphore db=1;//控制对数据库的访问int rc=1;//正在读或想要读的进程数void reader(void){while(1){down(&mutex);rc++;if(rc==1)down(&db);up(&mutex);read_database();down(&mutex);rc--;if(rc==0)up(&db);up(&mutex);use_data_read();}}void writer(void){while(1){think_up_data();down(&db);write_database();up(&db);}}


写者优先:当有一个写者被挂起时,这时有一个读者来了,读者不被立刻允许进入。这样,当一个写者到达时,不用等待后续到达的读者,只要当当前的读者完成时就可进入数据库。一旦有写者来,则后续读者必须等待,唤醒时优先考虑写者

semaphore read=1;//用于阻塞读者semaphore rmutex=1,wmutex=1;//用于控制对rcount和wcount的访问semaphore db=1;//控制对数据库的访问int rcount=wcount=0;void reader(void){p(read); //只要有写者等待,读者就会阻塞p(rmutex);rcount++;if(rcount==1)p(db);v(rmutex);v(read);//及时释放,多个读者可以同时读read_database();p(rmutex);rcount--;if(rcount==0)v(db);v(rmutex);}void writer(void){p(wmutex);wcount++;if(wcount==1)p(read);v(wmutex);p(db);write_database();v(db);p(wmutex);wcount--;if(wcount==0)v(read);v(wmutex);}






0 0