最近在重构之前写的网络底层时,从各个方面认真考虑了每一个细节实现。其中,在提交I/O(WSASend/WSARecv)和I/O完成(GetQueuedCompletionStatus)时,难免出现一个缓冲区需要两个线程公用的问题。
假设主线程不断发送该消息,这些消息被堆叠在一个缓冲区里,定时使用WSASend提交发送I/O请求,在GetQueuedCompletionStatus返回后,才能按照已发送的字节数去删掉该缓冲区里相应字节数的数据。不明白?好吧我说的简单一些。
WSASend调用后,你传递的参数只是说明:我希望发送这么多数据。但请求提交后,你的要求未必能够被全部满足,也就是说也许你想发送1024字节的东西,但也许GetQueuedCompletionStatus返回,操作完成后,本次只成功发送了1000个字节,也就是说剩余的24字节的数据,你还需要再调用WSASend,直到都发送成功为止。所以在这种情况下,一定要等待GetQueuedCompletionStatus返回,才知道究竟发送成功了多少,也才能从之前的发送缓冲里删掉数据。否则,如果你在提交WSASend时就把数据删掉了,而GetQueuedCompletionStatus返回后却告诉你只发了1000字节,那就杯具了-----那24字节的数据永远地离开了我们。
注:上面两个自然段锁阐述地情况我没有测试出来过,也没有专门对此测试。但因为文档上并未说明WSASend一定会发完全部数据,所以我一直认为要等操作完成后才知道。但也有不同观点。但即使如此,相信按照我下述方法处理也不会损失效率(至少我的应用是满足了)
而在这种情况下,我们发送消息,也就是向这个缓冲区后面堆放要发送的数据,是主线程中执行的,而GetQueuedCompletionStatus完成后,从缓冲区内弹出数据,却是IOCP的工作线程做的。当然,最简单的办法就是------加个锁呗。但是,在I/O频繁的情况下,可以想象会出现多少线程争用的情况。于是,就有了本文要说的东西:环形缓冲。
环形缓冲的原理并不难理解,只适用于一个线程写,一个线程读的情况。环形缓冲的原理我就不再赘述,可以自行搜索。
废话不多说。下面给出我在这次优化中写的一个环形缓冲类,该环形缓冲完美地在基于IOCP的网络框架中工作了起来,实实在在地解决了线程争用引发地效率低下。
2012.9.1 0:57 重贴代码
修改了一个可能出现的误置Full标志的BUG,之前的代码中,是先增加写指针,再判断是否等于读指针,等于则置Full标志,但若在该判断之前,读线程将数据读空,此时写线程继续工作,进行该判断时,就会发现写指针 = 读指针(但是是由于读空造成的),于是错误地将状态置为Full。
2014.8.4 2:08 重贴代码
重写代码,解决掉xiaolizi提出的可能会引发数据被覆盖的BUG,详细请见回复:(十分感谢您提出这个问题)
XRingBuffer.h
#pragma once #include "XBaseDefine.h" const BYTE XRING_BUFFER_READ_POS_AND_WRITE_POS_SIZE = 2; class XRingBuffer { public: XRingBuffer(const DWORD size); ~XRingBuffer(); bool pushData(const void* data, const DWORD size); bool copyData(void* dest, const DWORD destSize, const DWORD copySize); bool popData(void* dest, const DWORD destSize, const DWORD popSize); bool popData(const DWORD popSize); const DWORD getUsedSize() const; const DWORD getFreeSize() const; private: bool copyDataWithAddReadPosOption(void* dest, const DWORD destSize, const DWORD popSize, bool addReadPos); private: char* _buffer; const DWORD _size; volatile DWORD _write_pos; volatile DWORD _read_pos; };
#include "XRingBuffer.h" #include "XDebug.h" XRingBuffer::XRingBuffer(const DWORD size) : _size(size + XRING_BUFFER_READ_POS_AND_WRITE_POS_SIZE), _write_pos(1), _read_pos(0), _buffer(NULL) { _buffer = new char[_size]; } XRingBuffer::~XRingBuffer() { delete [] _buffer; } bool XRingBuffer::pushData(const void* data, const DWORD size) { const DWORD freeSize = getFreeSize(); if(freeSize < size) { return false; } const DWORD readPos =_read_pos; const DWORD writePos = _write_pos; if(writePos > readPos) { const DWORD lenFromWritePosToBufferEnd = _size - writePos; if(size <= lenFromWritePosToBufferEnd) { memcpy(_buffer + _write_pos, data, size); _write_pos += size; if(_write_pos == _size) { _write_pos = 0; } else if(_write_pos > _size) { assert_fail("wirtepos cannot bigger than size"); return false; } return true; } else { // 先拷贝前一部分到缓冲区尾部 memcpy(_buffer + _write_pos, data, lenFromWritePosToBufferEnd); const DWORD secondPartLen = size - lenFromWritePosToBufferEnd; // 拷贝后一部分到缓冲区前部 memcpy(_buffer, ((char*)data) + lenFromWritePosToBufferEnd, secondPartLen); _write_pos = secondPartLen; return true; } } else if(writePos < readPos) { memcpy(_buffer + writePos, data, size); _write_pos += size; return true; } else { assert_fail("write pos equal read pos, it's an error"); return false; } } bool XRingBuffer::copyData(void* dest, const DWORD destSize, const DWORD copySize) { return copyDataWithAddReadPosOption(dest, destSize, copySize, false); } bool XRingBuffer::popData(void* dest, const DWORD destSize, const DWORD popSize) { return copyDataWithAddReadPosOption(dest, destSize, popSize, true); } bool XRingBuffer::popData(const DWORD popSize) { return copyDataWithAddReadPosOption(NULL, 0, popSize, true); } const DWORD XRingBuffer::getUsedSize() const { const DWORD writePos = _write_pos; const DWORD readPos = _read_pos; if(writePos > readPos) { return writePos - readPos - 1; } else if(writePos < readPos) { return (_size - readPos - 1) + _write_pos; } else { assert_fail("write pos equal read pos, it's an error"); return 0; } } const DWORD XRingBuffer::getFreeSize() const { const DWORD usedSize = getUsedSize(); return _size - (usedSize + XRING_BUFFER_READ_POS_AND_WRITE_POS_SIZE); } bool XRingBuffer::copyDataWithAddReadPosOption(void* dest, const DWORD destSize, const DWORD copySize, bool addReadPos) { const DWORD usedSize = getUsedSize(); if(usedSize < copySize) { assert_fail("data is not enought to copy"); return false; } if(dest != NULL) { if(destSize < copySize) { assert_fail("dest buffer size is smaller than copy size"); return false; } } const DWORD writePos = _write_pos; const DWORD readPos = _read_pos; if(writePos > readPos) { if(dest != NULL) { memcpy(dest, _buffer + readPos + 1, copySize); } if(addReadPos) { _read_pos += copySize; } return true; } else if(writePos < readPos) { const DWORD lenFromReadPosToBufferEnd = _size - readPos - 1; if(copySize <= lenFromReadPosToBufferEnd) { if(dest != NULL) { memcpy(dest, _buffer + readPos + 1, copySize); } if(addReadPos) { _read_pos += copySize; assert(_read_pos < _size); } return true; } else { const DWORD secondPartLen = copySize - lenFromReadPosToBufferEnd; if(dest != NULL) { memcpy(dest, _buffer + readPos + 1, lenFromReadPosToBufferEnd); memcpy(((char*)dest) + lenFromReadPosToBufferEnd, _buffer, secondPartLen); } if(addReadPos) { _read_pos = secondPartLen - 1; } return true; } } else { assert_fail("write pos equal read pos, it's an error"); return false; } }