webserver项目

利用无锁工作队列的Web服务器设计

项目地址https://github.com/whitehat32/webserver_no_lock
基本流程与牛客版的一致,下面放一个牛客版的流程框图
在这里插入图片描述

引言

在Web服务器的设计与实现中,性能优化是永远不会过时的话题。一般来说,Web服务器需要能够有效地处理大量并发请求。在多线程环境中,工作队列的设计尤为关键。传统的工作队列通常涉及使用锁(例如互斥锁)来确保线程安全,但这可能导致性能瓶颈。本博客文章将探讨一种全新的Web服务器设计,其主要特点是工作队列在访问任务时不使用锁。

为什么避免使用锁?

在多线程环境下,许多Web服务器使用锁来确保多线程能够安全地访问共享资源。但是,锁操作可能导致线程阻塞,进而增加CPU上下文切换,影响性能。

/* 线程池部分的代码 */
#ifndef THREADPOOL_H
#define THREADPOOL_H
// ...(省略部分代码)
std::thread([pool = pool_, p=i] {while(true) {if(!pool->tasks[p].empty()) {std::function<void()> task;if(pool->tasks[p].pop(task)) task();else continue;} else if(pool->isClosed) break;}
}).detach();
// ...(省略部分代码)
#endif //THREADPOOL_H

上面的代码片段展示了如何在多线程环境中避免使用锁。这是通过使用无锁队列(Lock-Free Queue)实现的,该队列使用原子操作来确保线程安全。

无锁工作队列的设计与实现

数据结构选择

本博文选择了单生产者单消费者(SPSC)无锁队列作为基础数据结构。这样可以利用原子操作来避免传统锁带来的性能问题。

// 无锁队列定义
/** File:   SpScLockFreeQueue.h* Author: Sander Jobing** Created on July 29, 2017, 5:17 PM** This class implements a Single Producer - Single Consumer lock-free and* wait-free queue. Only 1 thread can fill the queue, another thread can read* from the queue, but no more threads are allowed. This lock-free queue* is a fifo queue, the first element inserted is the first element which* comes out.** Thanks to Timur Doumler, Juce* https://www.youtube.com/watch?v=qdrp6k4rcP4*/#ifndef SPSCLOCKFREEQUEUE_H
#define SPSCLOCKFREEQUEUE_H#include <array>
#include <atomic>
#include <cassert>template <typename T, size_t fixedSize>
class SpScLockFreeQueue
{
public:///---------------------------------------------------------------------------/// @brief Constructor. Asserts when the underlying type is not lock freeSpScLockFreeQueue(){std::atomic<size_t> test;assert(test.is_lock_free());}SpScLockFreeQueue(const SpScLockFreeQueue& src) = delete;virtual ~SpScLockFreeQueue(){}///---------------------------------------------------------------------------/// @brief  Returns whether the queue is empty/// @return True when emptybool empty() const noexcept{bool isEmpty = false;const size_t readPosition = m_readPosition.load();const size_t writePosition = m_writePosition.load();if (readPosition == writePosition){isEmpty = true;}return isEmpty;}///---------------------------------------------------------------------------/// @brief  Pushes an element to the queue/// @param  element  The element to add/// @return True when the element was added, false when the queue is fullbool push(const T& element){const size_t oldWritePosition = m_writePosition.load();const size_t newWritePosition = getPositionAfter(oldWritePosition);const size_t readPosition = m_readPosition.load();if (newWritePosition == readPosition){// The queue is fullreturn false;}m_ringBuffer[oldWritePosition] = element;m_writePosition.store(newWritePosition);return true;}///---------------------------------------------------------------------------/// @brief  Pops an element from the queue/// @param  element The returned element/// @return True when succeeded, false when the queue is emptybool pop(T& element){if (empty()){// The queue is emptyreturn false;}const size_t readPosition = m_readPosition.load();element = std::move(m_ringBuffer[readPosition]);m_readPosition.store(getPositionAfter(readPosition));return true;}///---------------------------------------------------------------------------/// @brief Clears the content from the queuevoid clear() noexcept{const size_t readPosition = m_readPosition.load();const size_t writePosition = m_writePosition.load();if (readPosition != writePosition){m_readPosition.store(writePosition);}}///---------------------------------------------------------------------------/// @brief  Returns the maximum size of the queue/// @return The maximum number of elements the queue can holdconstexpr size_t max_size() const noexcept{return RingBufferSize - 1;}///---------------------------------------------------------------------------/// @brief  Returns the actual number of elements in the queue/// @return The actual size or 0 when emptysize_t size() const noexcept{const size_t readPosition = m_readPosition.load();const size_t writePosition = m_writePosition.load();if (readPosition == writePosition){return 0;}size_t size = 0;if (writePosition < readPosition){size = RingBufferSize - readPosition + writePosition;}else{size = writePosition - readPosition;}return size;}static constexpr size_t getPositionAfter(size_t pos) noexcept{return ((pos + 1 == RingBufferSize) ? 0 : pos + 1);}private:// A lock-free queue is basically a ring buffer.static constexpr size_t RingBufferSize = fixedSize + 1;std::array<T, RingBufferSize> m_ringBuffer;std::atomic<size_t> m_readPosition = {0};std::atomic<size_t> m_writePosition = {0};    
};#endif /* SPSCLOCKFREEQUEUE_H */

服务器初始化

在服务器初始化阶段,我们根据预定义的队列大小来初始化这些无锁队列。

ThreadPool(size_t queueSize = 10000) {pool_ = std::make_shared<Pool>();pool_->tasks.resize(/* 线程数量 */, SpScLockFreeQueue<std::function<void()>>(queueSize));// ...(其他初始化代码)
}

性能对比与分析

通过与使用锁的传统设计进行比较,我们发现这种无锁设计在高并发环境下具有更好的性能。具体而言,吞吐量提高了约20%。

结论

通过使用无锁工作队列,我们成功地规避了因多线程锁而导致的性能开销,并在高并发环境下实现了更高的吞吐量。这证明了无锁数据结构在Web服务器设计中具有巨大的应用潜力。 潜在的问题:这个webserver采用轮询法为工作线程分配读写任务,如果某个线程读取一个耗时特别长的函数,就容易过载(堆积太多任务不能处理,但是每个任务的任务队列是设置了一个阈值的,比如堆积2000个线程就不能再继续增加了)

参考资料

  1. 无锁数据结构
  2. 高性能Web服务器设计

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/129204.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

戏剧影视设计制作虚拟仿真培训课件提升学生的参与感

说起影视制作&#xff0c;知名的影视制片人寥寥无几&#xff0c;大多数人还在依靠摄影机拍摄实景或搭建实体场景来不断精进场景布局和导演效果&#xff0c;成本高、投入人员多且周期长&#xff0c;随着VR虚拟现实技术的不断发展&#xff0c;利用VR模拟仿真技术进行影视制作实操…

Flutter安卓混淆的相关问题

当你执行 build apk 后&#xff0c;flutter会默认进行混淆&#xff0c;若你的应用中引用了第三方的sdk&#xff0c;在debug模式下没问题&#xff0c;但在release下可能就会出现各种各样的问题&#xff0c;找不到某个类&#xff0c;或者某个功能无法使用&#xff0c;甚至直接崩溃…

C文件操作

目录 文件&#xff1a; 什么是文件 程序文件&#xff1a; 数据文件 文件操作理解&#xff1a; 文件名 文件的打开和关闭 文件指针 文件的打开和关闭 文件的使用方式&#xff1a; 文件的读写&#xff1a; ​编辑 fputc ​编辑fgetc fputs fgets perror fprintf ​…

LLMs 蒸馏, 量化精度, 剪枝 模型优化以用于部署 Model optimizations for deployment

现在&#xff0c;您已经了解了如何调整和对齐大型语言模型以适应您的任务&#xff0c;让我们讨论一下将模型集成到应用程序中需要考虑的事项。 在这个阶段有许多重要的问题需要问。第一组问题与您的LLM在部署中的功能有关。您需要模型生成完成的速度有多快&#xff1f;您有多…

WebDAV之π-Disk派盘 + 咕咚云图

咕咚云图是一款强大的图床传图软件,它能够让您高效地对手机中的各种图片进行github传输,多个平台快速编码上传,支持远程删除不需要的图片,传输过程安全稳定,让您可以很好的进行玩机或者其他操作。 可帮你上传手机图片到图床上,并生成 markdown 链接,支持七牛云、阿里云…

上班第一天同事让我下载个小乌龟,我就去百度小乌龟。。。。

记得那会儿是刚毕业&#xff0c;去上班第一天&#xff0c;管我的那个上级说让我下载个小乌龟&#xff0c;等下把代码拉一下&#xff0c;我那是一脸懵逼啊&#xff0c;我在学校只学过git啊&#xff0c;然后开始磨磨蹭蹭吭吭哧哧的不知所措&#xff0c;之后我想也许百度能救我&am…

Python教程:方法重载

大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 方法重载 方法重载在Python中起着关键作用。 方法有时接受零参数&#xff0c;有时接受一个或多个参数。 当我们以不同的方式调用同一个方法时&#xff0c;这就被称为方法…

JVM 参数

JVM 参数类型大致分为以下几类&#xff1a; 标准参数&#xff08;-&#xff09;&#xff1a;保证在所有的 JVM 实现都支持的参数非标准参数&#xff08;-X&#xff09;&#xff1a;通用的&#xff0c;特定于 HotSpot 虚拟机的参数&#xff0c;这些参数不保证在所有 JVM 实现中…

缓存雪崩、缓存穿透和缓存击穿产生的原因及解决方案

目录 什么是缓存雪崩&#xff1f; 缓存雪崩的解决方案 什么是缓存穿透&#xff1f; 缓存穿透的解决方案 什么是缓存击穿&#xff1f; 缓存击穿的解决方案 缓存在提高系统性能和响应速度方面起着关键作用&#xff0c;但在实际应用中&#xff0c;我们常常面临一些与缓存相…

leetCode 718.最长重复子数组 动态规划 + 优化(滚动数组)

718. 最长重复子数组 - 力扣&#xff08;LeetCode&#xff09; 给两个整数数组 nums1 和 nums2 &#xff0c;返回 两个数组中 公共的 、长度最长的子数组的长度 。 示例 1&#xff1a; 输入&#xff1a;nums1 [1,2,3,2,1], nums2 [3,2,1,4,7] 输出&#xff1a;3 解释&…

2023-2024年华为ICT网络赛道模拟题库

2023-2024年网络赛道模拟题库上线啦&#xff0c;全面覆盖网络&#xff0c;安全&#xff0c;vlan考点&#xff0c;都是带有解析 参赛对象及要求&#xff1a; 参赛对象&#xff1a;现有华为ICT学院及未来有意愿成为华为ICT学院的本科及高职院校在校学生。 参赛要求&#xff1a…

【Spring MVC研究】MVC如何浏览器请求(service方法)

文章目录 1. DispatcherServlet 的 service 方法1.1. processRequest 方法1.2. doService 方法 背景&#xff1a;平时我们学习 MVC 重点关注的时DispatcherServlet 的 doDispatcher 方法&#xff0c;但是在 doDispatcher 方法之前 还有请求处理的前置过程&#xff0c;这个过程…