您的位置:首页 > 理论基础 > 计算机网络

网络接收数据缓存机制的分析和改进

2017-08-24 20:01 423 查看

在异步网络接收数据等场合,剥离数据接收和数据缓存的对于提高处理能力具有重要影响。相比之下,接收即处理的做法不能很好的适应高速数据吞吐。因为后者的处理如果稍有延迟,就会导致严重的数据堵塞或数据丢失。

下面以libuv接收UDP数据包加入缓存、应用程序从缓存提取数据的应用为例(来于公司项目开发),总结如何实现一个有效的缓存机制。

1. 分析失败的缓存案例

最初的缓存数据提取是从缓存队列每次提取队首的单元后将其删除,libuv在另一个线程向缓存队列随机的向队列增加新的单元,两个线程使用互斥锁保护缓存队列这个共享资源。从原理上看,这没有问题。在linux运行效果很流畅,但在Windows出现了严重的数据积压,缓存队列快速增长,很短的时间就扩展到数十万个单元。如果长期运行于Windows,进程必然会因为耗尽内存而崩溃。

直观的结论是std::queue在Windows系统的出队速度赶不上入队速度。但深入分析会发现原因比这个复杂得多。在运行下面的用例之后足够说明问题:

#include <thread>

#include <mutex>

using namespace std;

mutex m_buff_mutex;

bool bSend = true;

int n = 0;

void mysend()

{

printlog("start %s.", __FUNCTION__);

char *pch;

while (bSend)

{

lock_guard<std::mutex> locker(m_buff_mutex);

//pch = new char[1024];

////qData.push(pch);

//qData.push_back(pch);

//this_thread::sleep_for(chrono::microseconds(1000000));

n++;

printlog("snd n=%d.", n);

}

printlog("quit %s.", __FUNCTION__);

}

void myrecv()

{

printlog("start %s.", __FUNCTION__);

char szRcv[1024];

int len=0,size;

while (true)

{

lock_guard<std::mutex> locker(m_buff_mutex);

//len = getdata(szRcv,size);

//printlog("len=%dB, remain %d packages ,n=%d.",len, size,n);

n--;

printlog("rcv n=%d.", n);

}

}

int main(int argc, char **argv)

{

std::thread thrdrcv(myrecv);

std::thread thrdsnd(mysend);

getchar();

bSend = false;

thrdsnd.join();

getchar();

return 0;

}

注释的痕迹说明最初这个用例也是用于分析队列的进出速度为什么不匹配。后来开始怀疑原因是不是thrdrcv和thrdsnd运行机会不均所致,就进一步将用例的两个线程简化为向全局变量n分别进行增减操作。可以肯定,此时两个线程的复杂程度已经非常接近了,如果调度机会均等,那么在大的时间范围内,n的数值应该在0附近。

结果则是:在Windows系统,基本上每次运行都能观察到n值在逐步增大;接下来在Linux系统测试,得出了相同的结论。结合原有机制在Linux没有积压数据的事实,再次证明了Linux的事实性能的确要远高于Windows

其实,无论n是越来越大还是越来越小,都能说明同样的问题,两个执行函数几乎相同的std::thread实际的调度机会非常不均,呈一边倒的趋势,这个现象在不同的操作系统都存在。网上查阅std的资料得知,其thread是系统级的,使用者很难干预优先级——而现在的这个用例也并不适合干预两个线程的优先级。后来调整了main函数前两行的次序,结果不变。

可见,导致出入队速率不等的原因是出队线程的执行机会小于libuv入队线程(如果二者反过来麻烦会大一点,但也能克服:入队线程每次更新队列长度,这个变量不需要互斥保护,其他线程只能读不能写;出队线程先检查队列长度,如果非零或大于某个值才锁定缓存队列提取数据。本质上是减少出队线程和入队线程的竞争,提高后者的调度机会)。

2. 改进之后的解决方法

查明问题的原因之后,可以做出如下改进:

既然从缓存队列提取数据的机会相对较少,不如集中起来一次把队列中现有的数据都提取出来。在Windows测试,发现这时缓存队列不再无限增长,只要处理速度跟得上,基本上控制在10个左右,即便偶有增长,也会逐渐下降。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  网络数据缓存