一:三个问题
进程间通信简单的说有三个问题。第一个问题是一个进程如何把信息传递给另一个。第二个要处理的问题是是,要确保两个或更多的的进程在关键互动中不会出现交叉(即是进程互斥的问题),第三个问题是与正确的顺序有关(即是进程之间的同步问题)。
二:信号量及PV操作
解决互斥进入临界区分为两种解决方案,软件解决方案和硬件解决方案,软件解决方案有Peterson解法,硬件解决方案有TSL指令, XCHG指令等。上述的解决方案都有忙等待的缺点,并且主要是用来解决互斥问题。
信号量是一种解决进程同步的解决方案,同时信号量也可以当作互斥量来使用。使用信号量来解决进程同步问题也是编程中使用较多的方法。使用信号量解决进程同步比较经典的问题是生产者消费者问题。Linux代码演示如下。
#include#include #include #include #include #include #define N 5 // 消费者或者生产者的数目 #define M 10 // 缓冲数目 //int M=10; int in = 0; // 生产者放置产品的位置 int out = 0; // 消费者取产品的位置 int buff[M] = { 0 }; // 缓冲初始化为0,开始时没有产品 sem_t empty_sem; // 同步信号量,当满了时阻止生产者放产品 sem_t full_sem; // 同步信号量,当没产品时阻止消费者消费 pthread_mutex_t mutex; // 互斥信号量,一次只有一个线程访问缓冲 int product_id = 0; //生产者id int prochase_id = 0; //消费者id //信号处理函数 void Handlesignal(int signo){ printf("程序退出%d\n",signo); exit(0); } /* 打印缓冲情况 */ void print() { int i; printf("产品队列为"); for(i = 0; i < M; i++) printf("%d", buff[i]); printf("\n"); } /* 生产者方法 */ void *product() { int id = ++product_id; while(1) {//重复进行 //用sleep的数量可以调节生产和消费的速度,便于观察 sleep(2); sem_wait(&empty_sem); pthread_mutex_lock(&mutex); in= in % M; printf("生产者%d在产品队列中放入第%d个产品\t",id, in); buff[in]= 1; print(); ++in; pthread_mutex_unlock(&mutex); sem_post(&full_sem); } } /* 消费者方法 */ void *prochase() { int id = ++prochase_id; while(1) {//重复进行 //用sleep的数量可以调节生产和消费的速度,便于观察 sleep(5); sem_wait(&full_sem); pthread_mutex_lock(&mutex); out= out % M; printf("消费者%d从产品队列中取出第%d个产品\t",id, out); buff[out]= 0; print(); ++out; pthread_mutex_unlock(&mutex); sem_post(&empty_sem); } } int main() { printf("生产者和消费者数目都为5,产品缓冲为10,生产者每2秒生产一个产品,消费者每5秒消费一个产品,Ctrl+退出程序\n"); pthread_t id1[N]; pthread_t id2[N]; int i; int ret[N]; //结束程序 if(signal(SIGINT,Handlesignal)==SIG_ERR){//按ctrl+C产生SIGINT信号 printf("信号安装出错\n"); } // 初始化同步信号量 int ini1 = sem_init(&empty_sem, 0, M);//产品队列缓冲同步 int ini2 = sem_init(&full_sem, 0, 0);//线程运行同步 if(ini1 && ini2 != 0) { printf("信号量初始化失败!\n"); exit(1); } //初始化互斥信号量 int ini3 = pthread_mutex_init(&mutex, NULL); if(ini3 != 0) { printf("线程同步初始化失败!\n"); exit(1); } // 创建N个生产者线程 for(i = 0; i < N; i++) { ret[i]= pthread_create(&id1[i], NULL, product, (void *) (&i)); if(ret[i] != 0) { printf("生产者%d线程创建失败!\n", i); exit(1); } } //创建N个消费者线程 for(i = 0; i < N; i++) { ret[i]= pthread_create(&id2[i], NULL, prochase, NULL); if(ret[i] != 0) { printf("消费者%d线程创建失败!\n", i); exit(1); } } //等待线程销毁 for(i = 0; i < N; i++) { pthread_join(id1[i], NULL); pthread_join(id2[i],NULL); } exit(0); }
在线程中也可以使用条件量解决生产者消费者问题,具体代码演示参考我的另一篇博客。
三:管程
为了更易于编写正确的程序,Brinch Hansen (1973)和Hoare(1974)提出了一种高级同步原语,称为管程(monitor)。在下面的介绍中我们会发现,他们两人提出的方案略有不同。一个管程是一个由过程、变量及数据结构等组成的一个集合,它们组成一个特殊的模块或软件包。进程可在任何需要的时候调用管程中的过程,但它们不能在管程之外声明的过程中直接访问管程内的数据结构。C、C++不支持管程,Java语言支持管程。
四:其他通信方式
与管程和信号量有关的另一个问题是是,这些机制否是设计用来解决访问公共内存的一个或多个CPU上的互斥问题的。通过将信号量放在共享内存并用TSL或XCHG指令来保护他们,可以避免竞争。如果一个分布式系统具有多个CPU,并且每个CPU拥有自己的私有内存,他们通过一个局域网相连,那么这些源于将失效。这里的结论是信号量太低级,而管程在少数几个编程语言之外又无法使用,并且,这些源于均未提供机器见信息的交换方法。所以还需要其他办法。
1.消息传递
2.屏障