`
444878909
  • 浏览: 639822 次
文章分类
社区版块
存档分类
最新评论

生产者-消费者问题

 
阅读更多

学习MoreWindows的《秒杀多线程系列》至第十篇生产者消费者问题,对于多线程也可以说是有了一知半解了,特作一篇博文进行一点总结。在此,非常感谢MoreWindows为我们大家提供这么好的学习资料。

生产者消费者问题描述:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个具有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经放入产品的缓冲区中再次投放产品,同时在本文中也保证消费者和生产者对缓冲区的操作是互斥的(其实是没必要的)。

在本文中,利用一个互斥量来保证保证消费者和生产者对缓冲区的操作是互斥的,用proOK和conOK信号量来保证生产者和消费者之间的同步。

第一个版本的程序:

#include <stdio.h>
#include <process.h>
#include <windows.h>

const int produceNum = 8;
const int bufferSize = 4;
char buffer[bufferSize];

CRITICAL_SECTION cs1;
HANDLE proOK,conOK;
int g_i,g_j;
unsigned int __stdcall ProFun(PVOID pPM)
{
    for(int i=1;i<=produceNum;i++)
    {
        WaitForSingleObject(proOK,INFINITE);
        EnterCriticalSection(&cs1);
        buffer[g_i]=i;
        printf("生产者在第%d个缓冲区中投入了数据%d\n",g_i,i);
        g_i = (g_i+1)%bufferSize;

        LeaveCriticalSection(&cs1);
        ReleaseSemaphore(conOK,1,NULL);
    }
     printf("   生产者完成任务,线程结束运行\n");
     return 0;
}

unsigned int __stdcall ConFun(PVOID pPM)
{
     while(TRUE)
     {
         WaitForSingleObject(conOK,INFINITE);

         EnterCriticalSection(&cs1);
         printf("       编号为%d的消费者从第%d个缓冲区取走数据%d\n",(int)GetCurrentThreadId(),g_j,buffer[g_j]);

         if(buffer[g_j] == produceNum)
         {

             LeaveCriticalSection(&cs1);
             ReleaseSemaphore(conOK,1,NULL);//通知另一个消费者也退出
             break;
         }

         g_j=(g_j+1)%bufferSize;

         LeaveCriticalSection(&cs1);

         Sleep(50);
         ReleaseSemaphore(proOK,1,NULL);
    }
     printf("           编号为%d的线程收到结束信号,退出。。。。。\n",(int)GetCurrentThreadId());
     return 0;
}
int main()
{

    InitializeCriticalSection(&cs1);
    proOK=CreateSemaphore(NULL,4,4,NULL);
    conOK=CreateSemaphore(NULL,0,4,NULL);
    g_i=0;
    g_j=0;
    const int THREAD_NUM =3;
    HANDLE handle[THREAD_NUM];

    handle[0]=(HANDLE)_beginthreadex(NULL,0,ProFun,NULL,0,NULL);

    handle[1]=(HANDLE)_beginthreadex(NULL,0,ConFun,NULL,0,NULL);
    handle[2]=(HANDLE)_beginthreadex(NULL,0,ConFun,NULL,0,NULL);
    WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);

    for(int i=0;i<3;i++)
        CloseHandle(handle[i]);
    CloseHandle(proOK);
    CloseHandle(conOK);
    DeleteCriticalSection(&cs1);
    return 0;
}


运行结果截图:

从截图可以看到:产品8被两个消费者取了两次,原因在哪里呢:

if(buffer[g_j] == produceNum)
         {

             LeaveCriticalSection(&cs1);
             ReleaseSemaphore(conOK,1,NULL);
             //isover = TRUE;
             break;
         }


这段程序的作用是:当某一个消费者取到最后一个产品时,就退出,但为了确保另外一个消费者也能够正常退出,所以通过这句话ReleaseSemaphore(conOK,1,NULL);通知另外一个程序也及时退出,这样就造成了第二个消费者本来不能消费了的,但是又可以消费一次了(conOK信号量+1),这样两个消费者就都取了一次产品8.好吧,那这样的话,最后,如果某一个线程取到最后一个产品后就退出,而不管另一个线程是否会退出,即去掉这句ReleaseSemaphore(conOK,1,NULL);另外一个线程是否会正常退出呢。来看一下运行结果:

根据截图,我们看到,程序阻塞在第二个线程退出之前了,这是因为在第一个消费者退出后,conOK信号量已为0,然而第二个消费者已经开始准备去消费了,但又得不到消费的允许,就只有一直在等了。

那我们怎样才能让两个消费者线程对产品8只消费一次的同时,又能够保证两个线程都可以同时退出呢。在第一个程序中,两个线程是无条件的就可以准备消费(while(TRUE)),所以就出现了那种已经准备消费了,但是又得不到消费的允许,进一步就无法获得消费结束的信号,所以程序一直在等待。这样,我们就想到是不是可以给消费者设置一个可以准备消费的条件,所以,就加一个bool变量就可以了,当这个变量为真时,可以准备进行消费,否则准备消费都不允许,修改后的程序如下:

#include <stdio.h>
#include <process.h>
#include <windows.h>

const int produceNum = 8;
const int bufferSize = 4;
char buffer[bufferSize];

CRITICAL_SECTION cs1;
HANDLE proOK,conOK;//生产者消费者允许生产和消费的信号
int g_i,g_j;
bool isover;
unsigned int __stdcall ProFun(PVOID pPM)
{
    for(int i=1;i<=produceNum;i++)
    {
        WaitForSingleObject(proOK,INFINITE);
        EnterCriticalSection(&cs1);
        buffer[g_i]=i;
        printf("生产者在第%d个缓冲区中投入了数据%d\n",g_i,i);
        g_i = (g_i+1)%bufferSize;

        LeaveCriticalSection(&cs1);
        ReleaseSemaphore(conOK,1,NULL);
    }
     printf("   生产者完成任务,线程结束运行\n");
     return 0;
}

unsigned int __stdcall ConFun(PVOID pPM)
{
     while(!isover)
     {
         WaitForSingleObject(conOK,INFINITE);

         EnterCriticalSection(&cs1);
         printf("       编号为%d的消费者从第%d个缓冲区取走数据%d\n",(int)GetCurrentThreadId(),g_j,buffer[g_j]);

         if(buffer[g_j] == produceNum)
         {

             LeaveCriticalSection(&cs1);
             //ReleaseSemaphore(conOK,1,NULL);
             isover = TRUE;
             break;
         }

         g_j=(g_j+1)%bufferSize;

         LeaveCriticalSection(&cs1);

         Sleep(50);
         ReleaseSemaphore(proOK,1,NULL);
    }
     printf("           编号为%d的线程收到结束信号,退出。。。。。\n",(int)GetCurrentThreadId());
     return 0;
}
int main()
{

    InitializeCriticalSection(&cs1);
    proOK=CreateSemaphore(NULL,4,4,NULL);
    conOK=CreateSemaphore(NULL,0,4,NULL);
    g_i=0;
    g_j=0;
    isover = FALSE;
    const int THREAD_NUM =3;
    HANDLE handle[THREAD_NUM];

    handle[0]=(HANDLE)_beginthreadex(NULL,0,ProFun,NULL,0,NULL);

    handle[1]=(HANDLE)_beginthreadex(NULL,0,ConFun,NULL,0,NULL);
    handle[2]=(HANDLE)_beginthreadex(NULL,0,ConFun,NULL,0,NULL);
    WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);

    for(int i=0;i<3;i++)
        CloseHandle(handle[i]);
    CloseHandle(proOK);
    CloseHandle(conOK);
    DeleteCriticalSection(&cs1);
    return 0;
}

运行结果:

从结果看到,两个消费者对8号产品消费了一次,并且都正常的退出了线程。

最后,再一次感谢MoreWinsows给我们提供了这么好的学习资料。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics