返回介绍

总结

发布于 2024-10-04 12:28:35 字数 13467 浏览 0 评论 0 收藏 0

总结

  • 首先前面提到了一个思路:给队列模型添加初步的线程保护,在使用它的时候,可以不考虑会保护其免受资源争夺的问题

    • 实际上就是将CRITICAL_SECTION放在 Queue.c 的实现当中。
    • 让两个基本操作 PushBack PopFront 能够自己实现保护自己
    • 具体应该怎么做呢?之前的我们对队列模型中的 empty 实现了单独保护,现在反过来,将其保护范围扩大一些就行了。
  • 具体方法 Queue.c

    • 首先是取消使用 empty_sec 这个关键段/临界区
    • 使用新的 static CRITICAL_SECTION io_section;
    • 修改 newQueuedel_queue 里的初始化和销毁关键段代码。
    • 以及重点的 push_backpop_front的代码修改,前者变化多一些

        static int push_back(queue * __restrict object, const char * __restrict src, const char * __restrict dst)
        {
            char*    loc_src = NULL; /* 本地变量,尽量利用寄存器以及缓存 */
            char*    loc_dst = NULL;
            combine* loc_com = NULL;
            queue*   loc_que = object;
      
            size_t   len_src = strlen(src); /* 获取路径长度 */
            size_t   len_dst = strlen(dst);
            size_t   rear = 0; /* 队列的队尾 */
            size_t   front = 0; /* 队列的队首 */
      
            loc_src = Malloc_s(len_src + 1); /* 分配空间 */
            loc_dst = Malloc_s(len_dst + 1);
            loc_com = Malloc_s(sizeof(combine));
            if (loc_src == NULL || loc_dst == NULL || loc_com == NULL)
            {
                Free_s(loc_src); /* 特殊处理过的释放函数 */
                Free_s(loc_dst);
                Free_s(loc_src);
                return 1;
            }
            strcpy(loc_src, src); /* 构造路径模型 */
            strcpy(loc_dst, dst);
            loc_com->dst_to_path = loc_dst;
            loc_com->src_from_path = loc_src;
            /* 进入保护 */
            EnterCriticalSection(&io_section); 
            rear = loc_que->rear;   /*获取队尾*/
            front = loc_que->front; /*获取队首*/
      
            loc_que->path_contain[rear++] = loc_com; /* 将本地路径加入实体 */
            loc_que->rear = (rear % CAPCITY);     /* 用数组实现循环队列的步骤 */
            /* 取消原先的保护 */
            if (loc_que->rear == loc_que->front)  
            {
                loc_que->empty = 0;
            }
            LeaveCriticalSection(&io_section);
            return 0;
        }
      

      注释里写了很多信息,主要教之前的版本改变了一下串行代码的顺序,功能并没有太大变化,变化的两处地方,一个是内存分配错误判断由三个if变成一个if,另一个是为了使临界区内的代码尽可能少,所以将一些操作移动了。

      pop_front代码基本没改变只是将临界区扩大了保护范围。

        static combine * pop_front(queue* object)
        {
            EnterCriticalSection(&io_section);
            size_t   loc_front = object->front;                   /*获取当前队首*/
            ...    
        //    EnterCriticalSection(&empty_sec); /* 原先的临界区起始 */
            if (object->front == object->rear)
                object->empty = 1;
            else
                object->empty = 0;
        //    LeaveCriticalSection(&empty_sec);
            LeaveCriticalSection(&io_section);
            return loc_com;
        }    
      
  • 如此修改以后,该队列模型就具备了初步的线程安全功能。

  • 在主代码中,可以删除 PopFrontPushBack 附近的保护操作。
  • 前方提到了,CRITICAL_SECTION 相对于 Mutex 不太安全,这里简单说一下,具体请查询相关资料
    • 前者只对当前代码段负责,也就是其他操作这个资源的途径是不被保护的。
    • 通俗的来说,假设有多个线程,CRITICAL_SECTION只保证在同一个"时间"内,只有一个线程能够运行这段代码,假设我在其他代码还有对这段代码中的资源进行访问,那关键段就不能保证什么了。
    • 速度快开销小是因为它和内核关系不大,是进程内的操作。
  • 发现问题了吗?
    • 在代码中,对于empty 的操作存在着问题,我们必须对它进行类似Mutex的保护,而不是使用CRITICAL_SECTION
    • 我们这里应该使用Mutex吗?其实有一个更好的选择,那就是在 Windows Vista之后引入的一个读写锁SRWLOCK,允许多个线程读取数据或者单个线程写入数据
      • 为什么选择它?道理还是一样,因为它不使用内核资源。
      • 将代码中对empty关键段保护修改或添加上SRWLOCK读写锁的保护
      • 操作并没有什么区别,就是进入保护区请求(AcquireSRWLock(Exclusive/Shared)),离开保护区释放(ReleaseSRWLock(Exclusive/Shared))。
      • 本来有一个更好的可以减小开销的 TryAcquire... 操作,但是确在Windows 7以后才引入,故不在此实现。

结后语

  • 所谓读写锁,并不是所谓的银弹,意思就是不要盲目的使用读写锁,这里使用读写锁只是因为想要更加全面的覆盖知识点!
  • 要知道读锁的加持,并不一定就比一个互斥锁(这是Linux平台下对Windows临界区的称呼)要廉价,特别是在库设计实现者手里,他必须考虑到种种因素,例如 写锁饿死 现象,想要解决这个问题,就不可避免的要牺牲读锁的性能,有可能将互斥锁替换成读写锁以后,性能反而降低了。

写锁饿死,是个挺广泛的概念,只要有读写锁的身影就必定有它,可以查阅相关资料

  • 这一切都是需要测试,测试,再测试,之所以选择在 Windows 平台上开发,原因之一就是Visual Studio有一套强大到爆表的性能分析工具,你可以轻易找出代码性能问题。善用它来研究不同锁之间的差别,性能。

  • 对于多线程程序而言,同步原语实际上还是要善用 条件变量/互斥锁(临界区,关键段)这两个概念,能满足90% 的需求,最多再使用一些平台关键字就行了,不要参杂着各种各样的同步魔法,要不就换一种思维,除了 多线程,并发世界还有许多“很美好”的东西

很美好。。。是我自己说的

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文