C++ 如何在C+中使用无锁循环缓冲区实现零拷贝tcp+;

C++ 如何在C+中使用无锁循环缓冲区实现零拷贝tcp+;,c++,tcp,circular-buffer,lockless,zero-copy,C++,Tcp,Circular Buffer,Lockless,Zero Copy,我有多个线程需要使用TCP流中的数据。我希望在共享内存中使用循环缓冲区/队列来读取TCP套接字。TCP接收将直接写入循环队列。使用者将从队列中读取数据 此设计应启用零复制和零锁定。然而,这里有两个不同的问题 仅从TCP套接字读取1条逻辑消息是否可能/有效?如果没有,并且我读了不止一条消息,我将不得不将剩余值从这个复制到这个->下一个 真的可以实现无锁队列吗?我知道有原子操作,但这些操作也会很昂贵。因为所有的CPU缓存都需要失效。这将影响我的24个内核上的所有操作 我对低级TCP有些生疏,不太清楚

我有多个线程需要使用TCP流中的数据。我希望在共享内存中使用循环缓冲区/队列来读取TCP套接字。TCP接收将直接写入循环队列。使用者将从队列中读取数据

此设计应启用零复制和零锁定。然而,这里有两个不同的问题

  • 仅从TCP套接字读取1条逻辑消息是否可能/有效?如果没有,并且我读了不止一条消息,我将不得不将剩余值从这个复制到这个->下一个

  • 真的可以实现无锁队列吗?我知道有原子操作,但这些操作也会很昂贵。因为所有的CPU缓存都需要失效。这将影响我的24个内核上的所有操作

  • 我对低级TCP有些生疏,不太清楚如何判断消息何时完成。我是查找\0还是特定于实现


    不幸的是,TCP不能传输消息,只能传输字节流。如果你想传送消息,你必须在上面应用一个协议。实现高性能的最佳协议是使用指定消息长度的健全可检查标头的协议-这允许您将正确数量的ot数据直接读取到适当的缓冲区对象中,而无需逐字节迭代数据以查找消息结尾字符。然后,可以将缓冲区指针排队到另一个线程,并为下一条消息创建/释放一个新的缓冲区对象。这避免了对大容量数据的任何复制,对于大型消息来说,这是非常有效的,因此对消息对象指针使用非阻塞队列在某种程度上是毫无意义的

    下一个可用的优化是将对象*缓冲区集中在一起,以避免持续的新建/处置,回收使用者线程中的*缓冲区,以便在网络接收线程中重复使用。这对于ConcurrentQueue来说相当容易做到,最好是阻塞,以便在池暂时清空时允许流控制,而不是数据损坏或SEGFULTS/AV


    接下来,在每个*缓冲区数据成员的开头添加一个[cacheline size]“死区”,以防止任何线程与任何其他线程错误共享数据

    结果应该是一个高带宽,完整的消息流进入消费者线程,而延迟、CPU浪费或缓存抖动非常小。您的所有24核都可以在不同的数据上完全运行

    在多线程应用程序中复制批量数据是对设计拙劣和失败的承认

    跟进

    由于协议不同,您似乎一直在重复数据:(

    错误共享可用PDU缓冲区对象,示例:

    typedef struct{
      char deadZone[256];  // anti-false-sharing
      int dataLen;
      char data[8388608]; // 8 meg of data
    } SbufferData;
    
    class TdataBuffer: public{
    private:
      TbufferPool *myPool; // reference to pool used, in case more than one
      EpduState PDUstate; // enum state variable used to decode protocol
    protected:
      SbufferData netData;
    public:
      virtual reInit(); // zeros dataLen, resets PDUstate etc. - call when depooling a buffer
      virtual int loadPDU(char *fromHere,int len);  // loads protocol unit
      release(); // pushes 'this' back onto 'myPool'
    };
    
    loadPDU获得一个指向原始网络数据的指针,长度为。它返回0-表示尚未完全组装PDU,或返回它从原始网络数据中获取的字节数以完全组装PDU,在这种情况下,将其排队,对另一个PDU进行解包并调用loadPDU()使用未使用的剩余原始数据,然后继续输入下一个原始数据

    如果需要,您可以使用不同派生缓冲区类的不同池来服务于不同的协议-一个TbufferPool[Eprotocols]数组.TbufferPool可能只是一个BlockingCollection队列。管理变得几乎微不足道-缓冲区可以发送到系统周围的队列上,发送到GUI以显示统计数据,然后可能发送到记录器,只要在队列链的末尾有调用release()的东西

    显然,“真正”的PDU对象可能会加载更多的方法、数据联合/结构、迭代器和状态引擎来操作协议,但这是最基本的想法。最主要的是易于管理和封装,因为没有两个线程可以在同一个缓冲区实例上操作,所以不需要锁/同步来解析/访问da助教


    哦,是的,而且由于队列的锁定时间不必超过推送/弹出一个指针所需的时间,因此实际争用的机会非常低-即使是传统的阻塞队列也几乎不需要使用内核锁定。

    如果您使用的是Windows 8或Windows Server 2012,则可以使用注册I/O,从而为l提供更高的带宽与常规IOCP相比,它的CPU更低;它通过减少内核转换、零拷贝等方式实现这一点

    API:

    背景资料:

    您的空间中有什么信息?您有远大的目标,但简单直接的解决方案的性能是否还不够?无锁并不意味着“不昂贵”,虽然实际上无锁通常比锁定中涉及的上下文切换快几个数量级。只要将缓冲区设置为合适的大小,就可以通过原子增量实现您想要的,这是非常快的,例如65536。总之,我同意Nim的说法。@Damon,有趣的是,我没有看到环实现完全依赖于原子增量的ion,你可能有一篇论文(链接)对此做了什么吗?这是我见过的(并且已经做过的)唯一的实现依赖于比较/交换操作。如果传递对象指针,而不是实际的大容量数据,缓冲区的大小就有点不相关了-无论如何,每个缓冲区只传递32/64位。排队100MB与排队10字节所需的时间相同。这实际上比使用do的讨厌的共享大容量数据队列更容易管理dgy没有锁定指针,缓存抖动问题层出不穷。请注意,“批量数据”一开始就在概念上是错误的。尽管很多数据可能随时都会到达线路上,但从来没有“批量”这样的东西,必须没有。如果有,您的系统将无法工作。传入的数据放在队列中,由使用者处理的速度比新数据传入的速度快。总之,您的使用者必须比可能传入的任何数据都快,并且具有良好的安全裕度。否则,您的工作队列无论如何