多线程编程示例3(生产者消费者)

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

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

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

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

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

    以上这段分析见http://blog.csdn.net/morewindows/article/details/7577591,不能再赞了。

 

以下为自己实现的代码:(多生产者未实现,不过可以参照多消费者,将生产者的数量放在临界区进行操作)

#pragma once

#define _CRTDBG_MAP_ALLOC
#include<cstdio>
#include<Windows.h>
#include<crtdbg.h>
#include<process.h>

//设置控制台输出颜色
BOOL SetConsolecolor(WORD wAttributes)
{
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hConsole==INVALID_HANDLE_VALUE)
    {
        return false;
    }
    return SetConsoleTextAttribute(hConsole, wAttributes);
}
//1生产者 1消费者
int buffer = 10; //缓冲区

//1生产者 2消费者 4产品缓冲池
bool BoolEnd=false;
int buffer_ni = 0, buffer_nj = 0;
const int buffer_size = 4;
int buffer_multi[buffer_size]{0};

const int produce_num = 10; //生产产品个数
CRITICAL_SECTION cs;
HANDLE g_hEventBufferEmpty, g_hEventBufferFull;
HANDLE g_hSemaphoreBufferEmpty, g_hSemaphoreBufferFull;

//生产者线程函数
unsigned int _stdcall ProducerThreadFun(PVOID pM)
{
    for (size_t i = 0; i < produce_num; i++)
    {
        //等待缓冲区为空
        WaitForSingleObject(g_hEventBufferEmpty, INFINITE);

        //互斥的访问缓冲区
        EnterCriticalSection(&cs);
        SetConsolecolor(FOREGROUND_RED);
        buffer = i;
        printf("生产者将第%2d件产品放入缓冲区\n", i);
        LeaveCriticalSection(&cs);

        //通知缓冲区有数据
        SetEvent(g_hEventBufferFull);
    }
    return 0;
}

//消费者线程函数
unsigned int _stdcall ConsumerThreadFun(PVOID pM)
{
    volatile bool flag = true;
    while (flag)
    {
        //等待缓冲区有数据
        WaitForSingleObject(g_hEventBufferFull, INFINITE);

        //互斥的访问缓冲区
        EnterCriticalSection(&cs);
        SetConsolecolor(FOREGROUND_GREEN);
        printf("消费者从缓冲区提取第%d件产品\n", buffer);
        if (produce_num == buffer + 1)
        {
            flag = false;
        }
        LeaveCriticalSection(&cs);

        //通知缓冲区为空
        SetEvent(g_hEventBufferEmpty);

        Sleep(10); //产品区为空,停止提取产品(避免空转),让生产者生产产品
    }
    return 0;
}

unsigned int _stdcall ProduceThreadFun_Multi(PVOID pM)
{
    for (size_t i = 0; i < produce_num; i++)
    {
        //等待有空的缓冲区出现
        WaitForSingleObject(g_hSemaphoreBufferEmpty, INFINITE);

        //互斥的访问缓冲区
        EnterCriticalSection(&cs);
        SetConsolecolor(FOREGROUND_RED);
        buffer_multi[buffer_ni] = i;
        printf("生产者在缓冲池第%d个缓冲区投放数据%2d\n", buffer_ni, i);
        buffer_ni = (buffer_ni + 1) % buffer_size;
        LeaveCriticalSection(&cs);

        //通知消费者有新数据
        ReleaseSemaphore(g_hSemaphoreBufferFull, 1, NULL);
    }
    printf("生产者完成任务,线程结束运行!\n");
    return 0;
}

unsigned int _stdcall ConsumerThreadFun_Multi(PVOID pM)
{
    while (true)
    {
        //等待非空的缓冲区出现
        WaitForSingleObject(g_hSemaphoreBufferFull, INFINITE);

        //互斥的访问缓冲区
        EnterCriticalSection(&cs);
        if (BoolEnd) //另外一个消费者线程已经读取最后一件产品
        {
            LeaveCriticalSection(&cs); break;
        }
        SetConsolecolor(FOREGROUND_GREEN);
        printf("  编号为%d的消费者从缓冲区中第%d缓冲区提取产品%2d\n", GetCurrentThreadId(), buffer_nj, buffer_multi[buffer_nj]);
        if (buffer_multi[buffer_nj]+1==produce_num)
        {
            BoolEnd = true; //读取结束标识符
            LeaveCriticalSection(&cs);
            //读取最后一个产品,因为另外一个线程在等待,故添加信号量
            //这样会导致重复读取最后一件产品,可添加特殊标记,此处添加BoolEnd
            ReleaseSemaphore(g_hSemaphoreBufferFull, 1, NULL);
            break;
        }
        buffer_nj = (buffer_nj + 1) % buffer_size;
        LeaveCriticalSection(&cs);

        Sleep(50); //避免空转,让生产区填充产品
        ReleaseSemaphore(g_hSemaphoreBufferEmpty, 1, NULL);
    }
    SetConsolecolor(FOREGROUND_RED);
    printf("\n编号为%d的消费者收到通知,线程结束运行\n", GetCurrentThreadId());
    return 0;
}

void fun1()
{
    printf("生产者消费问题 1生产者 1消费者 \n");

    //初始化临界段
    InitializeCriticalSection(&cs);
    //创建两个自动复位事件,注意参数区别
    g_hEventBufferEmpty = CreateEvent(NULL, FALSE, TRUE, NULL);
    g_hEventBufferFull = CreateEvent(NULL, FALSE, FALSE, NULL);

    const int threadnum = 2;
    HANDLE handle[threadnum];

    handle[0] = (HANDLE)_beginthreadex(NULL, 0, ProducerThreadFun, NULL, 0, NULL);
    handle[1] = (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun, NULL, 0, NULL);
    WaitForMultipleObjects(threadnum, handle, TRUE, INFINITE);

    //销毁事件和关键段
    CloseHandle(handle[0]);
    CloseHandle(handle[1]);
    DeleteCriticalSection(&cs);
    CloseHandle(g_hEventBufferEmpty);
    CloseHandle(g_hEventBufferFull);
}

void fun2()
{
    InitializeCriticalSection(&cs);
    g_hSemaphoreBufferEmpty = CreateSemaphore(NULL, 4, 4, NULL);
    g_hSemaphoreBufferFull = CreateSemaphore(NULL, 0, 4, NULL);

    const int thread_num = 3;
    HANDLE handle[thread_num];

    //初始化变量
    buffer_ni = 0;
    buffer_nj = 0;
    BoolEnd = false;
    memset(buffer_multi, 0, sizeof(buffer_multi));

    //生产者线程
    handle[0] = (HANDLE)_beginthreadex(NULL, 0, ProduceThreadFun_Multi, NULL, 0, NULL);
    //消费者线程
    handle[1] = (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun_Multi, NULL, 0, NULL);
    handle[2] = (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun_Multi, NULL, 0, NULL);
    WaitForMultipleObjects(thread_num, handle, TRUE, INFINITE);
    
    for (size_t i = 0; i < thread_num; i++)
    {
        CloseHandle(handle[i]);
    }
    CloseHandle(g_hSemaphoreBufferEmpty);
    CloseHandle(g_hSemaphoreBufferFull);
    DeleteCriticalSection(&cs);
}

int main()
{
    //1生产者 1消费者
    //fun1();

    //1生产者 2消费者
    fun2();

    //检测内存泄漏
    _CrtDumpMemoryLeaks();
    return 0;
}

 

 

此题后续解决了双线程读取队列数据问题。

详见:http://blog.csdn.net/morewindows/article/details/8646902

posted @ 2015-08-10 16:06  从此寂静无声  阅读(718)  评论(0编辑  收藏  举报