• 多线程编程示例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
    ", sz);
        LeaveCriticalSection(&cs);
    }
    
    //读者线程函数
    unsigned int _stdcall ReaderThreadFun(PVOID pM)
    {
        ReaderPrintFun("     编号为%d的读者进入等待中...
    ", GetCurrentThreadId());
        //等待写者完成
        WaitForSingleObject(g_hEventWriter, INFINITE);
    
        //读者个数增加
        EnterCriticalSection(&cs_readernum);
        ++currreadernum;
        if (1==currreadernum)
        {
            ResetEvent(g_hEventNoReader);
        }
        LeaveCriticalSection(&cs_readernum);
    
        //读取文件
        ReaderPrintFun("编号为%d的读者开始读取文件...
    ", GetCurrentThreadId());
    
        Sleep(rand() % 100);
    
        //结束阅读,读者个数减小
        ReaderPrintFun("编号为%d的读者结束读取文件...
    ", 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("读者写者问题
    ");
    
        //初始化事件和信号量
        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,代码量能够大大简化。

  • 相关阅读:
    二叉树(链表形式)
    判断一个非空单链表是否是递增有序的
    指针的异或运算可用于交换两个变量的值
    JavaScript导论
    JavaScript语言的历史
    分享一个分页控件的实现思路
    MVC还是MVVM?或许VMVC更适合WinForm客户端
    基于NPOI的Excel数据导入
    一段用于地址清洗的代码
    模块3之手机号码格式的校验
  • 原文地址:https://www.cnblogs.com/jason1990/p/4719169.html
Copyright © 2020-2023  润新知