zoukankan      html  css  js  c++  java
  • 生产者消费者问题

     继经典线程同步问题之后,我们来看看生产者消费者问题及读者写者问题。生产者消费者问题是一个著名的线程同步问题,该问题描述如下:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个具有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经放入产品的缓冲区中再次投放产品。

        这个生产者消费者题目不仅常用于操作系统的课程设计,也常常在程序员和软件设计师考试中出现。并且在计算机考研的专业课考试中也是一个非常热门的问题。因此现在就针对这个问题进行详细深入的解答。

        首先来简化问题,先假设生产者和消费者都只有一个,且缓冲区也只有一个。这样情况就简便多了。

        第一.从缓冲区取出产品和向缓冲区投放产品必须是互斥进行的。可以用关键段互斥量来完成。

        第二.生产者要等待缓冲区为空,这样才可以投放产品,消费者要等待缓冲区不为空,这样才可以取出产品进行消费。并且由于有二个等待过程,所以要用二个事件信号量来控制。

        考虑这二点后,代码很容易写出来。另外为了美观起见,将消费者的输出颜色设置为彩色,有关如何在控制台下设置彩色输出请参阅《VC 控制台颜色设置》。

    [cpp] view plain copy
     
    1. //1生产者 1消费者 1缓冲区  
    2. //使用二个事件,一个表示缓冲区空,一个表示缓冲区满。  
    3. //再使用一个关键段来控制缓冲区的访问  
    4. #include <stdio.h>  
    5. #include <process.h>  
    6. #include <windows.h>  
    7. //设置控制台输出颜色  
    8. BOOL SetConsoleColor(WORD wAttributes)  
    9. {  
    10.     HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);  
    11.     if (hConsole == INVALID_HANDLE_VALUE)  
    12.         return FALSE;     
    13.     return SetConsoleTextAttribute(hConsole, wAttributes);  
    14. }  
    15. const int END_PRODUCE_NUMBER = 10;   //生产产品个数  
    16. int g_Buffer;                        //缓冲区  
    17. //事件与关键段  
    18. CRITICAL_SECTION g_cs;  
    19. HANDLE g_hEventBufferEmpty, g_hEventBufferFull;  
    20. //生产者线程函数  
    21. unsigned int __stdcall ProducerThreadFun(PVOID pM)  
    22. {  
    23.     for (int i = 1; i <= END_PRODUCE_NUMBER; i++)  
    24.     {  
    25.         //等待缓冲区为空  
    26.         WaitForSingleObject(g_hEventBufferEmpty, INFINITE);  
    27.   
    28.         //互斥的访问缓冲区  
    29.         EnterCriticalSection(&g_cs);  
    30.         g_Buffer = i;  
    31.         printf("生产者将数据%d放入缓冲区 ", i);  
    32.         LeaveCriticalSection(&g_cs);  
    33.           
    34.         //通知缓冲区有新数据了  
    35.         SetEvent(g_hEventBufferFull);  
    36.     }  
    37.     return 0;  
    38. }  
    39. //消费者线程函数  
    40. unsigned int __stdcall ConsumerThreadFun(PVOID pM)  
    41. {  
    42.     volatile bool flag = true;  
    43.     while (flag)  
    44.     {  
    45.         //等待缓冲区中有数据  
    46.         WaitForSingleObject(g_hEventBufferFull, INFINITE);  
    47.           
    48.         //互斥的访问缓冲区  
    49.         EnterCriticalSection(&g_cs);  
    50.         SetConsoleColor(FOREGROUND_GREEN);  
    51.         printf("  消费者从缓冲区中取数据%d ", g_Buffer);  
    52.         SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);  
    53.         if (g_Buffer == END_PRODUCE_NUMBER)  
    54.             flag = false;  
    55.         LeaveCriticalSection(&g_cs);  
    56.           
    57.         //通知缓冲区已为空  
    58.         SetEvent(g_hEventBufferEmpty);  
    59.   
    60.         Sleep(10); //some other work should to do  
    61.     }  
    62.     return 0;  
    63. }  
    64. int main()  
    65. {  
    66.     printf("  生产者消费者问题   1生产者 1消费者 1缓冲区 ");  
    67.     printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) -- ");  
    68.   
    69.     InitializeCriticalSection(&g_cs);  
    70.     //创建二个自动复位事件,一个表示缓冲区是否为空,另一个表示缓冲区是否已经处理  
    71.     g_hEventBufferEmpty = CreateEvent(NULL, FALSE, TRUE, NULL);  
    72.     g_hEventBufferFull = CreateEvent(NULL, FALSE, FALSE, NULL);  
    73.       
    74.     const int THREADNUM = 2;  
    75.     HANDLE hThread[THREADNUM];  
    76.       
    77.     hThread[0] = (HANDLE)_beginthreadex(NULL, 0, ProducerThreadFun, NULL, 0, NULL);  
    78.     hThread[1] = (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun, NULL, 0, NULL);  
    79.     WaitForMultipleObjects(THREADNUM, hThread, TRUE, INFINITE);  
    80.     CloseHandle(hThread[0]);  
    81.     CloseHandle(hThread[1]);  
    82.       
    83.     //销毁事件和关键段  
    84.     CloseHandle(g_hEventBufferEmpty);  
    85.     CloseHandle(g_hEventBufferFull);  
    86.     DeleteCriticalSection(&g_cs);  
    87.     return 0;  
    88. }  

    运行结果如下所示:

    可以看出生产者与消费者已经是有序的工作了。

        然后再对这个简单生产者消费者问题加大难度。将消费者改成2个,缓冲池改成拥有4个缓冲区的大缓冲池。

        如何来思考了这个问题了?首先根据上面分析的二点,可以知道生产者和消费者由一个变成多个的影响不大,唯一要注意的是缓冲池变大了,回顾一下《秒杀多线程第八篇 经典线程同步 信号量Semaphore》中的信号量,不难得出用二个信号量就可以解决这种缓冲池有多个缓冲区的情况——用一个信号量A来记录为空的缓冲区个数,另一个信号量B记录非空的缓冲区个数,然后生产者等待信号量A,消费者等待信号量B就可以了。因此可以仿照上面的代码来实现复杂生产者消费者问题,示例代码如下:

    [cpp] view plain copy
     

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

    const int Products_Num = 8;
    const int BUFFER_SIZE = 4;
    int Buffer[BUFFER_SIZE];
    bool g_bProductorOver = false;
    CRITICAL_SECTION g_cs;
    HANDLE g_hThreadSemaforphoreEmpty, g_hThreadSemaforphoreFull;

    int g_i, g_j;
    //控制台颜色调配
    BOOL SetConsoleColor(WORD wAttributes)
    {
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hConsole == INVALID_HANDLE_VALUE)
    return FALSE;
    return SetConsoleTextAttribute(hConsole, wAttributes);
    }

    unsigned int _stdcall ProducerThreadFun(PVOID pM)
    {
    for (int i = 1; i <=Products_Num; i++)
    {
    WaitForSingleObject(g_hThreadSemaforphoreEmpty, INFINITE);

    EnterCriticalSection(&g_cs);
    Buffer[g_i] = i;
    printf("生产者放入%d缓冲区的数据为%d ", g_i, Buffer[g_i]);
    g_i = (g_i + 1) % BUFFER_SIZE;
    LeaveCriticalSection(&g_cs);
    ReleaseSemaphore(g_hThreadSemaforphoreFull, 1, NULL);
    }
    printf("生产者放入数据完成 ");
    return 0;
    }
    unsigned int _stdcall CustomerThreadFun(PVOID pM)
    {
    // volatile bool flag = true;
    while (true)
    {
    WaitForSingleObject(g_hThreadSemaforphoreFull, INFINITE);
    EnterCriticalSection(&g_cs);
    if (g_bProductorOver==true)
    {
    // flag = false;
    break; //跳出到哪里去了
    }
    SetConsoleColor(FOREGROUND_RED);
    printf("编号为%d的消费者从缓冲池中第%d个缓冲区取出数据%d ", GetCurrentThreadId(), g_j, Buffer[g_j]);
    SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
    if (Buffer[g_j] == Products_Num)
    /*当缓冲池里就剩一个资源的时候,两个消费线程都会去消费这个资源。
    可是当A线程消费完这个资源后, 为了不让B陷入无尽的等待(因为生产线程已经结束),
    所以在A消费之后凭空释放了一个资源,导致同一个资源被消费两次*/
    {
    ReleaseSemaphore(g_hThreadSemaforphoreEmpty, 1, NULL);
    g_bProductorOver = true;
    // flag = false;
    LeaveCriticalSection(&g_cs);

    break;
    }
    g_j = (g_j + 1) % BUFFER_SIZE;
    LeaveCriticalSection(&g_cs);
    Sleep(50);
    ReleaseSemaphore(g_hThreadSemaforphoreEmpty, 1, NULL);


    }
    SetConsoleColor(FOREGROUND_GREEN);
    printf("消费者取出数据完成 ");
    SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);

    return 0;
    }

    int main()
    {
    InitializeCriticalSection(&g_cs);
    g_hThreadSemaforphoreEmpty = CreateSemaphore(NULL, 4, 4, NULL);
    g_hThreadSemaforphoreFull = CreateSemaphore(NULL, 0, 4, NULL);
    g_i = 0;
    g_j = 0;
    memset(Buffer, 0, sizeof(Buffer));

    const int THREADNUM = 3;
    HANDLE hThread[THREADNUM];
    hThread[0] = (HANDLE)_beginthreadex(NULL, 0, ProducerThreadFun, NULL, 0, NULL);
    hThread[1] = (HANDLE)_beginthreadex(NULL, 0, CustomerThreadFun, NULL, 0, NULL);
    hThread[2] = (HANDLE)_beginthreadex(NULL, 0, CustomerThreadFun, NULL, 0, NULL);
    //hThread[3] = (HANDLE)_beginthreadex(NULL, 0, CustomerThreadFun, NULL, 0, NULL);
    //hThread[4] = (HANDLE)_beginthreadex(NULL, 0, CustomerThreadFun, NULL, 0, NULL);
    WaitForMultipleObjects(THREADNUM, hThread, TRUE, 1000);

    for (int i = 0; i < THREADNUM; i++)
    CloseHandle(hThread[i]);

    //销毁信号量和关键段
    CloseHandle(g_hThreadSemaforphoreEmpty);
    CloseHandle(g_hThreadSemaforphoreFull);
    DeleteCriticalSection(&g_cs);
    system("pause");
    return 0;
    }

    运行结果如下图所示:

    输出结果证明各线程的同步和互斥已经完成了。

    至此,生产者消费者问题已经圆满的解决了,下面作个总结:

    1.首先要考虑生产者与消费者对缓冲区操作时的互斥。

    2.不管生产者与消费者有多少个,缓冲池有多少个缓冲区。都只有二个同步过程——分别是生产者要等待有空缓冲区才能投放产品,消费者要等待有非空缓冲区才能去取产品。

    下一篇《秒杀多线程第十一篇读者写者问题》将介绍另一个著名的同步问题——读者写者问题,欢迎大家再来参阅。

    转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/7577591

    如果觉得本文对您有帮助,请点击‘顶’支持一下,您的支持是我写作最大的动力,谢谢。

  • 相关阅读:
    Python使用SMTP模块、email模块发送邮件
    harbor搭建及使用
    ELK搭建-windows
    ELK技术栈之-Logstash详解
    【leetcode】1078. Occurrences After Bigram
    【leetcode】1073. Adding Two Negabinary Numbers
    【leetcode】1071. Greatest Common Divisor of Strings
    【leetcode】449. Serialize and Deserialize BST
    【leetcode】1039. Minimum Score Triangulation of Polygon
    【leetcode】486. Predict the Winner
  • 原文地址:https://www.cnblogs.com/zsq1993/p/5998220.html
Copyright © 2011-2022 走看看