我正在尝试构建一个负载测试器,方法是创建一个发出快速异步 http 请求的线程,忽略服务器响应,另一个线程通过发送请求和测量响应时间每 500 毫秒对服务器进行一次采样。伪代码:
for (int i = 0; i < input.Length; i++)
{
Console.WriteLine("Load: {0} requests/second", input[i]);
var trd = LunchSamlper();
var callsPerSecond = input[i];
for (j = 0; j < callsPerSecond * BATCH_TIME; j++)
{
tasks.Add(Task.Factory.StartNew(async () =>
{
await SendRequestAsyncIgnoreResponse(address, payload);
}));
Thread.Sleep(1000 / callsPerSecond);
}
Task.WaitAll(tasks.ToArray());
tasks.Clear();
trd.Abort();
}
private static async Task SendRequestAsyncIgnoreResponse(Uri url, string data)
{
WebClient client = new WebClient();
client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
await Task.Factory.StartNew(() => client.UploadStringAsync(url, data));
}
private static Thread LunchSamlper()
{
var t = new Thread(() =>
{
Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;
Stopwatch sw = new Stopwatch();
while (true)
{
sw.Restart();
SendRequestAndProcessResponse();
sw.Stop();
Console.WriteLine("Time:{0}, Threads in process: {1}, Response Time: {2}", DateTime.Now.TimeOfDay, Process.GetCurrentProcess().Threads.Count, sw.ElapsedMilliseconds);
Thread.Sleep(500);
}
});
t.Priority = ThreadPriority.Highest;
t.Start();
return t;
}
理想情况下,采样器线程每 500 毫秒唤醒一次,发出请求并测量响应时间。 实际上,线程每 10+ 秒唤醒一次,这使得测试无关紧要。我不太熟悉 CLR 内部结构,但我猜发生了什么事是异步 responses 快速排队,需要越来越多的线程来处理它们,从而插入采样器线程下调度程序。 我想做的(但不知道怎么做)是强制采样器线程保持在队列的顶部,有点欺骗微软声明他们使用的循环法。我尝试将其设置为“最高”优先级,但没有帮助。
编辑: 请看上面的新代码,这是我实际执行的代码,输出如下。您的所有评论都是正确的 - 采样器不受其余线程的影响(服务器响应太长)
但是,你可以清楚地看到负载越大,进程中呈现的线程就越多。我相信这强化了我的主张,即异步任务越多,线程池就需要更多的线程来处理它们当任务准备就绪时(即,当异步部分完成时)。当队列中准备好的任务太多时,线程池只能创建更多的线程。如果我错了,请向我解释幕后到底发生了什么。 输出:
Threads in process: 5
Load: 2 requests/second
Time :10:38:46.2552818, Threads in process: 19, Response Time : 454
Time :10:38:47.1143283, Threads in process: 22, Response Time : 357
Time :10:38:47.9765673, Threads in process: 22, Response Time : 359
Time :10:38:48.8408045, Threads in process: 22, Response Time : 360
.
. (same stats for 30 seconds)
.
Time :10:39:15.6071230, Threads in process: 23, Response Time : 351
Load: 20 requests/second
Time :10:39:16.2120124, Threads in process: 23, Response Time : 369
Time :10:39:17.0811746, Threads in process: 23, Response Time : 365
Time :10:39:17.9329613, Threads in process: 24, Response Time : 348
Time :10:39:18.7825313, Threads in process: 24, Response Time : 346
Time :10:39:19.6345169, Threads in process: 24, Response Time : 345
Time :10:39:20.4919706, Threads in process: 24, Response Time : 354
.
.
.
.
Time :10:39:41.9171558, Threads in process: 26, Response Time : 359
Time :10:39:42.7701623, Threads in process: 26, Response Time : 351
Time :10:39:43.6266869, Threads in process: 26, Response Time : 353
Time :10:39:44.4826907, Threads in process: 26, Response Time : 352
Time :10:39:45.3433820, Threads in process: 26, Response Time : 358
Load: 50 requests/second
Time :10:39:46.5745915, Threads in process: 26, Response Time : 358
Time :10:39:47.9899573, Threads in process: 28, Response Time : 912
Time :10:39:50.5339321, Threads in process: 30, Response Time : 2039
Time :10:39:54.8185811, Threads in process: 28, Response Time : 3780
Time :10:40:02.5677990, Threads in process: 28, Response Time : 7244
Time :10:40:16.3460440, Threads in process: 30, Response Time : 13273
边注:
起初,我没有使用采样线程。代码看起来像这样:
for (int i = 0; i < numOfRequests; i++)
{
Stopwatch sw = new Stopwatch();
Task.Factory.StartNew(async () => await SendRequestIgnoreResponse(sw))
.ContinueWith(p=> MeasureServerResponse(sw));
Thread.Sleep(1000 / requestsPerSecond);
}
但我意识到测量的时间不正确,因为秒表还在计算响应准备好时任务在队列中花费的时间。这就是我想出采样器线程的想法的方式,它使它同步调用。任何新想法都会被认真考虑!
最后的注释:
值得一提的是,负载下的服务器是本地的,在生成负载的同一台机器上。寻址 url 背后的方法调用外部资源,这需要大约 300 毫秒。
最佳答案
您必须记住,当您的 IO 正确异步时,没有 spoon 线程。您创建新线程只是为了花费极少的时间开始一个异步操作,以及消耗线程池时间来处理您从未使用过的结果。
只是不要那样做:
for (int i = 0; i < numOfRequests; i++)
{
SendRequestIgnoreResponse();
Thread.Sleep(1000 / requestsPerSecond);
}
另请记住,Thread.Sleep
没有特别高的精度。它的精度最多可达几十毫秒,在压力下的系统中甚至可能达到数百毫秒。
关于c# - 如何创建一个立即启动的线程(即使有很多线程就绪)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29106253/