消费者生产者主要是就是PV操作的问题,但是其中还掺杂了线程问题。
1、首先要先明确一些知识点:
当一个进程启动的时候随即会创建一个主线程,那么在程序中还可以用CreateThread这个函数来创建进程内的新的线程为进程的运行提高效率。
这个函数的定义原形如下:
HANDLECreateThread( LPSECURITY_ATTRIBUTESlpThreadAttributes,//SD SIZE_TdwStackSize,//initialstacksize LPTHREAD_START_ROUTINElpStartAddress,//threadfunction LPVOIDlpParameter,//threadargument DWORDdwCreationFlags,//creationoption LPDWORDlpThreadId//threadidentifier );
dwStackSize,为这个线程设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。程序在此处也用了主线程的栈大小 lpStartAddress指定了线程函数。但是必须以DWORD WINAPI 函数名 (LPVOID lpParam)这种形式来定义 DWORDdwCreationFlags表示是线程的标志,如果为0那么创建后立刻激活这个线程 LPDWORDlpThreadId 在这个变量里面保存新线程ID
在创建这个线程激活之后,这个线程的开始就是函数的第三个参数,这个参数应该是一个函数名,线程就开始于这个函数。函数名后面的参数便是调用这个函数的参数。
前面的一些字符常量的定义我已经在源码中给出了详细的注释
HANDLE Mutex; //用于互斥
HANDLE FullSemaphore; //当缓冲区满的时候迫使生产者等待
HANDLE EmptySemaphore; //当缓冲区空的时候迫使消费者等待
int buffer[SIZE_OF_BUFFER]; //缓冲区是个循环队列
三个信号量的定义以及缓冲区的定义尤为重要,在之后的程序中创建互斥和同步信号量的时候,方法不同但是都返回一个句柄。句柄这个概念我也已经说过多次,就是创建 了一个对象之后返回这个对象的标识,以便于之后能够找到这个对象。
消费和生产的过程和我们上课时候讲的流程是一模一样的,唯一要注意的就是缓冲区的问题。
缓冲区设两个指针,第一个是in指针,第二个是out指针,两个指针分别对应于生产和消费。要注意的是, 程序已经规定了缓冲区的大小只有两个,并且采用循环数组实现缓冲区。
在生产者生产了资源之后,会把相应的资源放到缓冲区中,此时缓冲区存的是生产者此次生产的ID号码,在消费者从缓冲区取出资源的时候,会把数组中相应的数值重置为0代表已经取走,并且指针前移。
2、程序的主要流程:
先是进入主函数,然后创建用于保护临界区的互斥体,之后利用CreateSemaphore函数创建同步信号量,用于生产者和消费者在对缓冲区进行操作的时候进行协调。同步信号量包括两个,第一个是表示缓冲区是否还有空位,因为缓冲区在初始状态有两个空位,故此这个信号量的初始值也是2.另外一个信号量是表示缓冲区有几个资源,因为缓冲区初始化的时候 是没有资源的,所以赋初值为0.
之后设定生产者和消费者的个数,并且累计了生产者和消费者的总的线程数。
将每一个线程的句柄最后都存于一个数组内,便于管理。
之后根据生产者线程个数创建生产者线程,每一次创建一个线程,线程都立即激活,因为缓冲区个数为2,生产者个数为3,那么在每一次生产者线程创建的时候,激活生产函数,将 EmptySemaphore信号量在P操作的时候消耗减去1,等到为0的时候,此时缓冲区已满不能再继续生产。当再生产第三个生产线程的时候,信号量变为-1,此时第三个线程已经阻塞不能执行。再次期间,不但两个生产线程在执行,并且两个生产线程是霸占了现在的临界区的,也就是缓冲区,但是主进程中的消费者线程也在建立并且激活。但是在生产者没生产完之前,所有的消费者都被阻塞在了FullSemaphore这个同步信号量上,即使一个生产者生产完资源通过ReleaseSemaphore(FullSemaphore, 1, NULL);激活一个消费者线程,那么被激活的这个也不能继续执行,因为还有一个生产者尚在临界区,消费者不能获得临界区的互斥体,得等所有的生产者生产完毕,并且退出临界区,这个时候消费者才能够进入临界区进行消费资源。
在这里要提醒大家的是,只要进程不被杀死,那么在进程中的线程也会一直在运行。所以在消费者线程运行的时候自然也会阻塞一些生产者进程,当 消费者消费完之后,退出临界区,ReleaseSemaphore(EmptySemaphore, 1, NULL);就会使用这个函数激活生产者进程,发出信号告诉他此时缓冲区已经有空位。当然,生产者也必须在消费者完成所有工作的前提下在进行工作,只要你手动停止整个进程为止。因为临界区在这里是互斥的,任何时候都只有消费者或者生产者其中一个进入这个区域,即使同为生产者,两个以上也只有其中的一个能够进入缓冲区。
3、一些改动
(1)EmptySemaphore = CreateSemaphore(NULL, 0, SIZE_OF_BUFFER, NULL),把这个信号量的初值改变,也就是改变了缓冲区在初始化的时候默认里面没有空位,那样生产者生产了资源之后无法放入,消费者要取出资源还无法取出,生产者和消费者互相依赖彼此来唤醒,所以造成了死锁的情况。
(2)完全可以用 CreateSemaphore代替CreateMutex。一个是修改创建信号量的语句:Mutex = CreateSemaphore(NULL, 1, SIZE_OF_BUFFER, NULL),还有一处修改就是改变出临界区的时候增加信号量的语句:ReleaseSemaphore(Mutex, 1, NULL)。这是课堂上讲的东西,我不愿在废话。
最后源码如下:
#include<windows.h> #include<iostream> #include<cstring> using namespace std; const unsigned short SIZE_OF_BUFFER = 2; //缓冲区长度 unsigned short ProductID = 0; //产品号 unsigned short ConsumeID = 0; //将被消耗的产品号 unsigned short in = 0; //产品进入缓冲区时的缓冲区下标 unsigned short out = 0; //产品出缓冲区的时候缓冲区的下标 int buffer[SIZE_OF_BUFFER]; //缓冲区是个循环队列 bool p_ccontinue = true; //控制程序结束 HANDLE Mutex; //用于互斥 HANDLE FullSemaphore; //当缓冲区满的时候迫使生产者等待 HANDLE EmptySemaphore; //当缓冲区空的时候迫使消费者等待 DWORD WINAPI Producer(LPVOID); //生产者进程 DWORD WINAPI Consumer(LPVOID); //消费者进程 int main() { Mutex = CreateMutex(NULL, FALSE, NULL); //创建互斥信号量 /* 找出当前系统是否已经存在指定进程的实例。如果没有则创建一个互斥体。CreateMutex()函数可用来创建一个有名或无名的互斥量对象 HANDLE CreateMutex( LPSECURITY_ATTRIBUTESlpMutexAttributes, // 指向安全属性的指针 BOOLbInitialOwner, // 初始化互斥对象的所有者 LPCTSTRlpName // 指向互斥对象名的指针 ); */ EmptySemaphore = CreateSemaphore(NULL, SIZE_OF_BUFFER, SIZE_OF_BUFFER, NULL); //创建空不取同步信号量 /* HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTESlpSemaphoreAttributes, // SD LONGlInitialCount, // initial count LONGlMaximumCount, // maximum count LPCTSTRlpName// object name ); 创建一个新的信号量,如执行成功,返回信号量对象的句柄;零表示出错 lInitialCount Long,设置信号量的初始计数。可设置零到lMaximumCount之间的一个值 lMaximumCount Long,设置信号量的最大计数 lpName String,指定信号量对象的名称。 它是提供一个计数值,它允许在这个计数值之内, 任何执行到WaitForSingleObject的Thread都不会停下来, 而且每执行WaitForSingleObject一次,计数值就减一, 当计数值变成0时,该Semaphore才会处於UnSignal的状态, 而某个Thread ReleaseSemaphore时,便会将计数值增加, 以便其他的Thread或本身可得Signal的讯号,而使WaitForSingleObject停止等待。 */ FullSemaphore = CreateSemaphore(NULL, 0, SIZE_OF_BUFFER, NULL); //创建满不放信号量 const unsigned short PRODUCERS_COUNT = 3; //生产者个数 const unsigned short CONSUMERS_COUNT = 1; //消费者个数 const unsigned short THREADS_COUNT = PRODUCERS_COUNT + CONSUMERS_COUNT; //总的线程数 HANDLE hThreads[THREADS_COUNT]; //各路线程的handle DWORD producerID[PRODUCERS_COUNT]; //生产者和消费者的标识符 DWORD consumerID[CONSUMERS_COUNT]; //创建生产者线程 for(int i = 0; i < PRODUCERS_COUNT; i++) { hThreads[i] = CreateThread(NULL, 0, Producer, NULL, 0, &producerID[i]); if(hThreads[i] == NULL) return -1; } //创建消费者线程 for(int i = 0; i < CONSUMERS_COUNT; i++) { hThreads[i + PRODUCERS_COUNT] = CreateThread(NULL, 0, Consumer, NULL, 0, &consumerID[i]); if(hThreads[i + PRODUCERS_COUNT] == NULL) return -1; } while(p_ccontinue) { if(getchar()) { p_ccontinue = FALSE; } } return 0; } //生产函数 void Produce() { cout << endl << "Producing" << ++ ProductID << "......."; cout << "succeed"<< endl; } //生产结果放入缓冲区 void Append() { cerr << "Appending a product ... "; buffer[in] = ProductID; in = (in + 1) % SIZE_OF_BUFFER; cerr << "Succeed" << endl; //输出缓冲区当前状态 for(int i = 0; i < SIZE_OF_BUFFER; i++) { cout << i << ":" << buffer[i]; if(i == in) cout << "<----生产"; if(i == out) cout << "<-----消费"; cout << endl; } } //从缓冲区取出 void Take() { cerr << "Taking a product . . ."; ConsumeID = buffer[out]; buffer[out] = 0; out = (out + 1) % SIZE_OF_BUFFER; cerr << "SUcceed" << endl; //输出缓冲区当前状态 for(int i = 0; i < SIZE_OF_BUFFER; i++) { cout << i << ":" << buffer[i]; if(i == in) cout << "<----生产"; if(i == out) cout << "<-----消费"; cout << endl; } } void Consume() { cout << "Consuming " << ConsumeID << "..."; cout << "Succeed" << endl; } DWORD WINAPI Producer(LPVOID lpPara) { while(p_ccontinue) { //两个P操作 WaitForSingleObject(EmptySemaphore, INFINITE); //判断缓冲区此时是否有空间 WaitForSingleObject(Mutex, INFINITE); //保护临界区,互斥 Produce(); Append(); Sleep(500); //两个V操作 ReleaseMutex(Mutex); ReleaseSemaphore(FullSemaphore, 1, NULL); } return 0; } DWORD WINAPI Consumer(LPVOID lpPara) { while(p_ccontinue) { //两个P操作 WaitForSingleObject(FullSemaphore, INFINITE); WaitForSingleObject(Mutex, INFINITE); Take(); Consume(); Sleep(500); ReleaseMutex(Mutex); ReleaseSemaphore(EmptySemaphore, 1, NULL); } return 0; }