多线程编程示例4(写者读者问题)

读者写者也是一个非常著名的同步问题。

读者写者问题描述非常简单,有一个写者很多读者,多个读者可以同时读文件,但写者在写文件时不允许有读者在读文件,同样有读者在读文件时写者也不去能写文件。

#pragma once

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

int currreadernum = 0;
const int readernum = 5;

//关键段和事件
CRITICAL_SECTION cs,cs_readernum;
HANDLE g_hEventWriter, g_hEventNoReader;

//设置控制台输出颜色
BOOL SetConsolecolor(WORD wAttributes)
{
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hConsole == INVALID_HANDLE_VALUE)
    {
        return false;
    }
    return SetConsoleTextAttribute(hConsole, wAttributes);
}

//读者线程输出函数
void ReaderPrintFun(char *sz, ...)
{
    va_list pArgList;
    va_start(pArgList, sz);
    EnterCriticalSection(&cs);
    SetConsolecolor(FOREGROUND_GREEN);
    vfprintf(stdout, sz, pArgList);
    LeaveCriticalSection(&cs);
    va_end(pArgList);
}

//写者线程输出函数
void WritePrintFun(char *sz)
{
    EnterCriticalSection(&cs);
    SetConsolecolor(FOREGROUND_RED);
    printf("%s\n", sz);
    LeaveCriticalSection(&cs);
}

//读者线程函数
unsigned int _stdcall ReaderThreadFun(PVOID pM)
{
    ReaderPrintFun("     编号为%d的读者进入等待中...\n", GetCurrentThreadId());
    //等待写者完成
    WaitForSingleObject(g_hEventWriter, INFINITE);

    //读者个数增加
    EnterCriticalSection(&cs_readernum);
    ++currreadernum;
    if (1==currreadernum)
    {
        ResetEvent(g_hEventNoReader);
    }
    LeaveCriticalSection(&cs_readernum);

    //读取文件
    ReaderPrintFun("编号为%d的读者开始读取文件...\n", GetCurrentThreadId());

    Sleep(rand() % 100);

    //结束阅读,读者个数减小
    ReaderPrintFun("编号为%d的读者结束读取文件...\n", GetCurrentThreadId());

    //读者个数减少
    EnterCriticalSection(&cs_readernum);
    --currreadernum;
    if (0==currreadernum)
    {
        SetEvent(g_hEventNoReader);
    }
    LeaveCriticalSection(&cs_readernum);

    return 0;
}

//写者线程函数
unsigned int _stdcall WriteThreadFun(PVOID pM)
{
    WritePrintFun("写者线程进入等待中...");
    
    //等待读文件的读者为零
    WaitForSingleObject(g_hEventNoReader, INFINITE);
    //标记写者正在写文件
    ResetEvent(g_hEventWriter);

    WritePrintFun("写者开始写文件...");
    Sleep(rand() % 100);
    WritePrintFun("写者结束写文件");

    //标记写者结束写文件
    SetEvent(g_hEventWriter);
    return 0;
}

int main()
{
    printf("读者写者问题\n");

    //初始化事件和信号量
    InitializeCriticalSection(&cs);
    InitializeCriticalSection(&cs_readernum);

    //手动置位,已经触发
    currreadernum = 0;
    g_hEventWriter = CreateEvent(NULL, TRUE, TRUE, NULL);
    g_hEventNoReader = CreateEvent(NULL, FALSE, TRUE, NULL);

    size_t i = 0;
    HANDLE handle[readernum + 1];

    for ( i = 1; i < 3; i++)
    {
        handle[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL);
    }
    //写者线程(置于此处是为了避免写者写完文件,然后就是顺序读取。。。)
    handle[0] = (HANDLE)_beginthreadex(NULL, 0, WriteThreadFun, NULL, 0, NULL);
    Sleep(50);

    //启动其余读者线程
    for (; i < readernum + 1; i++)
    {
        handle[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL);
    }
    WaitForMultipleObjects(readernum + 1, handle, TRUE, INFINITE);

    for (i = 0; i < readernum + 1; i++)
    {
        CloseHandle(handle[i]);
    }

    //销毁事件和信号量
    CloseHandle(g_hEventWriter);
    CloseHandle(g_hEventNoReader);
    DeleteCriticalSection(&cs);
    DeleteCriticalSection(&cs_readernum);

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

我原本想着是否使用信号量,后来看http://blog.csdn.net/morewindows/article/details/7596034

觉得使用事件确实也蛮不错的,关键是要对多线程的具体情景进行分析。

这里使用信号量的话,处理起来会比较麻烦。

运行结果:

 

此题亦可以通过读写锁SRWLock解决:

在写者线程调用AcquireSRWLockExclusive和ReleaseSRWLockExclusive;

在读者线程调用AcquireSRWLockShared和ReleaseSRWLockShared,代码量能够大大简化。

 

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