进程间通信简单的说有三个问题,第一个问题是:一个进程如何把信息传递给另一个,第二个问题是:要确保两个或者更多的进程在互动中不会出现交叉(即是进程互斥问题),第三个问题是:进程间同步问题、
四种进程或者线程同步互斥的控制方法
1):临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
2):互斥量:为协调共同对一个共享资源的单独访问而设计的
3):信号量:为控制一个具有有限数量用户资源而设计
4):事件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
临界区(Critical Section)
保证在某一时刻只有一个线程能访问数据的简便办法,在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开,临界区在被释放后,其他线程可以继续抢占,并以此达到原子方式操作共享资源的目的。
临界区包含两个操作原语:
EnterCriticalSection() 进入临界区
LeaveCriticalSection() 离开临界区
虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
互斥量(Mutex)
互斥量跟临界区很相似,只有拥有互斥量对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其它线程在获得以访问资源,互斥量比临界区复杂。使用互斥量不仅仅可以在同一进程的不同线程中实现资源的共享,而且还可以在不同进程的线程之间实现对资源的安全访问。
互斥量包含的几个操作原语:
CreateMutex() 创建一个互斥量
OpenMutex() 打开一个互斥量
ReleaseMutex() 释放互斥量
WaitForMultipleObjects() 等待互斥量对象
信号量(Semaphores)
信号量对象对线程的同步方式与上面的两种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的pv操作相同,它指出了同时访问共享资源的线程最大数目,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphone()创建信号量时既要同时指出允许的最大资源计数和当前可用资源计数,一般是将挡墙可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减去1,只要当前可用资源计数是大于0,就可以发出信号量信号,但是当前可用计数减小到0则说明当前占用资源的线程数已经达到了所允许的最大数目,不能再允许其他线程的进入,此时的信号量将无法发出,线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphone()函数将当前可用资源计数加1,在任何时候当前可用资源计数决不能大于最大资源计数。
信号量S是一个整数,S大于等于零时代表可供并发进程使用的资源实体数,但s小于零时则表示正在等待使用共享资源的进程数。
p操作申请资源:
1)s减去1
2)若s减1仍大于0,则进程继续执行。
3)若s减1小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转入进程调度。
V操作释放资源
1)s加1
2)若相加结果大于零,则进程继续执行。
3)若相加结果小于等于零,则从该信号的等待队列中唤醒一个等待进程,然后再返回原进程继续执行或转入进程调度、
信号量包含的几个操作原语:
CreateSemaphore() 创建一个信号量
OpenSemaphore() 打开一个信号量
ReleaseSemaphore() 释放信号量
WaitForSingleObject() 等待信号量
事件(Event)
事件对象也可以通过通知操作的方式来保持线程的同步。并且可以实现不同进程中的线程同步操作。
信号量包含的几个操作原语:
CreateEvent() 创建一个事件
OpenEvent() 打开一个事件
SetEvent() 回置事件
WaitForSingleObject() 等待一个事件
WaitForMultipleObjects() 等待多个事件
WaitForMultipleObjects 函数原型:
WaitForMultipleObjects(
IN DWORD nCount, // 等待句柄数
IN CONST HANDLE *lpHandles, //指向句柄数组
IN BOOL bWaitAll, //是否完全等待标志
IN DWORD dwMilliseconds //等待时间
)
参 数nCount指定了要等待的内核对象的数目,存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核对象的两种等待方式进行了指定,为TRUE时当所有对象都被通知时函数才会返回,为FALSE则只要其中任何一个得到通知就可以返回。 dwMilliseconds在这里的作用与在WaitForSingleObject()中的作用是完全一致的。如果等待超时,函数将返回 WAIT_TIMEOUT
信号量是一种解决进程同步的解决方案,同时信号量也可以当做互斥量来使用,使用信号量来解决进程同步问题是编程中使用较多的方法。
使用信号量比较经典的问题时生产者消费者问题:下面举个例子说明
由于本例是在windows下写的代码,在windows下使用多线程编程,需要包含pthread.h.在ftp://sourceware.org/pub/pthreads-win32网址下、
我下载的是pthreads-w32-2-7-0-release.exe.双击pthreads-w32-2-7-0-release.exe,点击Browse选择安装到的目录,然后点击Extract解压,完成后点击Done。
之后会在安装目录看到有三个文件夹Pre-built.2、pthreads.2、QueueUserAPCEx.
将Pre-built.2文件夹下的include和lib文件夹里的文件复制到VS对应的include和lib目录,我这里是C:Program FilesMicrosoft Visual Studio 11.0VCinclude和C:Program FilesMicrosoft VisualStudio 11.0VClib.这样就可以了,如果运行的时候链接错误,把pthreadVC2.dll 拷贝到你程序的可执行目录下试试。
1 #include "stdafx.h" 2 #include <stdio.h> 3 #include <pthread.h> 4 #include <sched.h> 5 #include <semaphore.h> 6 #include <conio.h> 7 #include <ctype.h> 8 #include <signal.h> 9 #include <iostream> 10 11 #include<Windows.h> 12 using namespace std; 13 #pragma comment(lib,"pthreadVC2.lib") 14 15 #define N 5 //消费者或者生产者的数目 16 #define M 10 //缓冲数目 17 18 int productin = 0; //生产者放置产品的位置 19 int prochaseout = 0; //消费者取产品的位置 20 21 int buff[M] = {0}; //缓冲区初始化为0,开始时没有产品。 22 23 sem_t empty_sem; // 同步信号量,当满的时候阻止生产者放产品。 24 sem_t full_sem; //同步信号量,当没有产品的时候阻止消费者消费。 25 26 pthread_mutex_t mutex; //互斥信号量,一次只有一个线程访问缓冲区。 27 28 int product_id = 0; //生产者id 29 int prochase_id = 0; //消费者id 30 31 void SignalExit(int signo) 32 { 33 printf("程序退出%d ",signo); 34 return; 35 } 36 37 void PrintProduction() 38 { 39 printf("此时的产品队列为::"); 40 for(int i = 0; i < M; i++ ) 41 { 42 printf("%d ",buff[i]); 43 } 44 printf(" "); 45 } 46 47 //////////////////////生产者方法//////////////////// 48 void* Product(void* pramter) 49 { 50 int id = ++product_id; 51 while(1) 52 { 53 Sleep(5000); //毫秒 54 sem_wait(&empty_sem); //给信号量减1操作 55 pthread_mutex_lock(&mutex); 56 productin = productin % M; 57 printf("生产者%d在产品队列中放入第%d个产品 ",id,productin+1); 58 buff[productin] = 1; 59 PrintProduction(); 60 ++productin; 61 62 pthread_mutex_unlock(&mutex); //释放互斥量对象 63 sem_post(&full_sem); //给信号量的值加1操作 64 } 65 } 66 67 //////////////消费者方法/////////////////////// 68 void* Prochase( void* pramter ) 69 { 70 int id = ++prochase_id; 71 while(1) 72 { 73 Sleep(7000); 74 sem_wait(&full_sem); 75 pthread_mutex_lock(&mutex); 76 prochaseout = prochaseout % M; 77 printf("消费者%d从产品队列中取出第%d个产品 ",id,prochaseout+1); 78 79 buff[prochaseout] = 0; 80 PrintProduction(); 81 ++prochaseout; 82 83 pthread_mutex_unlock(&mutex); 84 sem_post(&empty_sem); 85 } 86 } 87 88 int main() 89 { 90 cout << "生产者和消费者数目都为5,产品缓冲区为10,生产者每2秒生产一个产品,消费者每5秒消费一个产品" << endl << endl; 91 pthread_t productid[N]; 92 pthread_t prochaseid[N]; 93 94 int ret[N]; 95 96 //初始化信号量 97 int seminit1 = sem_init(&empty_sem,0,M); 98 int seminit2 = sem_init(&full_sem,0,0); 99 if( seminit1 != 0 && seminit2 != 0 ) 100 { 101 printf("sem_init failed !!! "); 102 return 0; 103 } 104 105 //初始化互斥信号量 106 int mutexinit = pthread_mutex_init(&mutex,NULL); 107 if( mutexinit != 0 ) 108 { 109 printf("pthread_mutex_init failed !! "); 110 return 0; 111 } 112 113 //创建n个生产者线程 114 for(int i = 0; i < N; i++ ) 115 { 116 ret[i] = pthread_create( &productid[i], NULL,Product,(void*)(&i) ); 117 if( ret[i] != 0 ) 118 { 119 printf("生产者%d线程创建失败! ",i); 120 return 0; 121 } 122 } 123 124 //创建n个消费者线程 125 for(int j = 0; j < N; j++ ) 126 { 127 ret[j] = pthread_create(&prochaseid[j],NULL,Prochase,NULL); 128 if( ret[j] != 0 ) 129 { 130 printf("消费者%d线程创建失败 ",j); 131 return 0; 132 } 133 } 134 135 ///////////////////////等待线程被销毁/////////////////////////////////////////////// 136 for( int k = 0; k < N; k++ ) 137 { 138 printf("销毁线程 "); 139 pthread_join(productid[k],NULL); 140 pthread_join(prochaseid[k],NULL); 141 } 142 return 0; 143 }