秒杀多线程第十一篇---读者写者问题

本文通过具体代码示例,详细解析了读者写者问题的同步机制。利用关键段、事件等手段实现了读者与写者之间的互斥与同步,确保了在多线程环境下对共享资源的安全访问。

与上一篇《秒杀多线程第十篇 生产者消费者问题》的生产者消费者问题一样,读者写者也是一个非常著名的同步问题。读者写者问题描述非常简单,有一个写者很多读者,多个读者可以同时读文件,但写者在写文件时不允许有读者在读文件,同样有读者在读文件时写者也不去能写文件。

这里写图片描述

上面是读者写者问题示意图,类似于生产者消费者问题的分析过程,首先来找找哪些是属于“等待”情况。

第一.写者要等到没有读者时才能去写文件。

第二.所有读者要等待写者完成写文件后才能去读文件。

找完“等待”情况后,再看看有没有要互斥访问的资源。由于只有一个写者而读者们是可以共享的读文件,所以按题目要求并没有需要互斥访问的资源。类似于上一篇中美观的彩色输出,我们对生产者输出代码进行了颜色设置(在控制台输出颜色设置参见《VC 控制台颜色设置》)。因此在这里要加个互斥访问,不然很有可能在写者线程将控制台颜色设置还原之前,读者线程就已经有输出了。所以要对输出语句作个互斥访问处理,修改后的读者及写者的输出函数如下所示:

//读者线程输出函数
void ReaderPrintf(char *pszFormat, ...)
{
    va_list   pArgList;
    va_start(pArgList, pszFormat);
    EnterCriticalSection(&g_cs);
    vfprintf(stdout, pszFormat, pArgList);
    LeaveCriticalSection(&g_cs);
    va_end(pArgList);
}
//写者线程输出函数
void WriterPrintf(char *pszStr)
{
    EnterCriticalSection(&g_cs);
    SetConsoleColor(FOREGROUND_GREEN);
    printf("     %s\n", pszStr);
    SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
    LeaveCriticalSection(&g_cs);
}

读者线程输出函数所使用的可变参数详见《C,C++中使用可变参数》。

解决了互斥输出问题,接下来再考虑如何实现同步问题可以设置一个变量来记录正在读文件的读者个数第一个开始读文件的读者要负责将关闭允许写者进入的标志,最后一个结束读文件的读者要负责打开允许写者进入的标志!!!!!。这样第一种“等待”情况就解决了。第二种“等待”情况是有写者进入时所以读者不能进入,使用一个事件就可以完成这个任务了——所有读者都要等待这个事件而写者负责触发事件和设置事件为未触发!!!!。详细见代码中注释:

//读者与写者问题
#include <stdio.h>
#include <process.h>
#include <windows.h>
//设置控制台输出颜色
BOOL SetConsoleColor(WORD wAttributes)
{
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hConsole == INVALID_HANDLE_VALUE)
        return FALSE;

    return SetConsoleTextAttribute(hConsole, wAttributes);
}
const int READER_NUM = 5;  //读者个数
//关键段和事件
CRITICAL_SECTION g_cs, g_cs_writer_count;
HANDLE g_hEventWriter, g_hEventNoReader;
int g_nReaderCount;
//读者线程输出函数(变参函数的实现)
void ReaderPrintf(char *pszFormat, ...)
{
    va_list   pArgList;

    va_start(pArgList, pszFormat);
    EnterCriticalSection(&g_cs);
    vfprintf(stdout, pszFormat, pArgList);
    LeaveCriticalSection(&g_cs);
    va_end(pArgList);
}
//读者线程函数
unsigned int __stdcall ReaderThreadFun(PVOID pM)
{
    ReaderPrintf("     编号为%d的读者进入等待中...\n", GetCurrentThreadId());
    //等待写者完成
    WaitForSingleObject(g_hEventWriter, INFINITE);

    //读者个数增加
    EnterCriticalSection(&g_cs_writer_count);
    g_nReaderCount++;
    if (g_nReaderCount == 1)
        ResetEvent(g_hEventNoReader);
    LeaveCriticalSection(&g_cs_writer_count);

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

    Sleep(rand() % 100);

    //结束阅读,读者个数减小,空位增加
    ReaderPrintf(" 编号为%d的读者结束读取文件\n", GetCurrentThreadId());

    //读者个数减少
    EnterCriticalSection(&g_cs_writer_count);
    g_nReaderCount--;
    if (g_nReaderCount == 0)
        SetEvent(g_hEventNoReader);
    LeaveCriticalSection(&g_cs_writer_count);

    return 0;
}
//写者线程输出函数
void WriterPrintf(char *pszStr)
{
    EnterCriticalSection(&g_cs);
    SetConsoleColor(FOREGROUND_GREEN);
    printf("     %s\n", pszStr);
    SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
    LeaveCriticalSection(&g_cs);
}
//写者线程函数
unsigned int __stdcall WriterThreadFun(PVOID pM)
{
    WriterPrintf("写者线程进入等待中...");
    //等待读文件的读者为零
    WaitForSingleObject(g_hEventNoReader, INFINITE);
    //标记写者正在写文件
    ResetEvent(g_hEventWriter);

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

    //标记写者结束写文件
    SetEvent(g_hEventWriter);
    return 0;
}
int main()
{
    printf("  读者写者问题\n");
    printf(" -- by MoreWindows( http://blog.youkuaiyun.com/MoreWindows ) --\n\n");

    //初始化事件和信号量
    InitializeCriticalSection(&g_cs);
    InitializeCriticalSection(&g_cs_writer_count);

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

    int i;
    HANDLE hThread[READER_NUM + 1];
    //先启动二个读者线程
    for (i = 1; i <= 2; i++)
        hThread[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL);
    //启动写者线程
    hThread[0] = (HANDLE)_beginthreadex(NULL, 0, WriterThreadFun, NULL, 0, NULL);
    Sleep(50);
    //最后启动其它读者结程
    for ( ; i <= READER_NUM; i++)
        hThread[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL);
    WaitForMultipleObjects(READER_NUM + 1, hThread, TRUE, INFINITE);
    for (i = 0; i < READER_NUM + 1; i++)
        CloseHandle(hThread[i]);

    //销毁事件和信号量
    CloseHandle(g_hEventWriter);
    CloseHandle(g_hEventNoReader);
    DeleteCriticalSection(&g_cs);
    DeleteCriticalSection(&g_cs_writer_count);
    return 0;
}

结果:
这里写图片描述

根据结果可以看出当有读者在读文件时,写者线程会进入等待状态中。当写者线程在写文件时,读者线程也会排队等待,说明读者和写者已经完成了同步。

本系列通过经典线程同步问题来列举线程同步手段的关键段、事件、互斥量、信号量,并作对这四种方法进行了总结。然后又通过二个著名的线程同步实例——生产者消费者问题和读者写者问题来强化对多线程同步互斥的理解与运用。希望读者们能够熟练掌握,从而在笔试面试中能够顺利的“秒杀”多线程的相关试题,获得自己满意的offer。

从《秒杀多线程第十篇生产者消费者问题》到《秒杀多线程第十一篇读者写者问题》可以得出多线程问题的关键在于找到所有“等待”情况和判断有无需要互斥访问的资源。那么如何从实际问题中更好更快更全面的找出这些了?请看《秒杀多线程第十二篇多线程同步内功心法——PV操作上》和《秒杀多线程第十三篇多线程同步内功心法——PV操作下》这二篇以加强解决多线程同步问题的“内功”。

另外,读者写者问题可以用读写锁SRWLock来解决,请看《秒杀多线程第十四篇 读者写者问题继 读写锁SRWLock》

转载请标明出处,原文地址:http://blog.youkuaiyun.com/morewindows/article/details/7596034

数据集介绍:垃圾分类检测数据集 一、基础信息 数据集名称:垃圾分类检测数据集 图片数量: 训练集:2,817张图片 验证集:621张图片 测试集:317张图片 总计:3,755张图片 分类类别: - 金属:常见的金属垃圾材料。 - 纸板:纸板类垃圾,如包装盒等。 - 塑料:塑料类垃圾,如瓶子、容器等。 标注格式: YOLO格式,包含边界框和类别标签,适用于目标检测任务。 数据格式:图片来源于实际场景,格式为常见图像格式(如JPEG/PNG)。 二、适用场景 智能垃圾回收系统开发: 数据集支持目标检测任务,帮助构建能够自动识别和分类垃圾材料的AI模型,用于自动化废物分类和回收系统。 环境监测与废物管理: 集成至监控系统或机器人中,实时检测垃圾并分类,提升废物处理效率和环保水平。 学术研究与教育: 支持计算机视觉与环保领域的交叉研究,用于教学、实验和论文发表。 三、数据集优势 类别覆盖全面: 包含三种常见垃圾材料类别,覆盖日常生活中主要的可回收物类型,具有实际应用价值。 标注精准可靠: 采用YOLO标注格式,边界框定位精确,类别标签准确,便于模型直接训练和使用。 数据量适中合理: 训练集、验证集和测试集分布均衡,提供足够样本用于模型学习和评估。 任务适配性强: 标注兼容主流深度学习框架(如YOLO等),可直接用于目标检测任务,支持垃圾检测相关应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值