WEB项目中除了单元测试,还经常需要多线程测试一个方法是否存在并发问题,或者是否有性能问题。每次都要写测试代码总是一件很累的事情。于是写了这一个多线程测试的类库,用来进行快速的多线程并发测试。
多线程并发测试时,需要等所有线程测试结束后通知主线程,主线程才能进行下一步动作,这里主要用到了ManualResetEvent。ManualResetEvent 类表示一个本地等待处理事件,在已发事件信号后必须手动重置该事件。通常,此通信涉及一个线程在其他线程进行之前必须完成的任务。当一个线程开始一个活动(此活动必须完成后,其他线程才能开始)时,它调用 Reset 以将 ManualResetEvent 置于非终止状态。此线程可被视为控制 ManualResetEvent。调用 ManualResetEvent 上的 WaitOne 的线程将阻止,并等待信号。当控制线程完成活动时,它调用 Set 以发出等待线程可以继续进行的信号。并释放所有等待线程。一旦它被终止,ManualResetEvent 将保持终止状态,直到它被手动重置。即对 WaitOne 的调用将立即返回。可以通过将布尔值传递给构造函数来控制 ManualResetEvent 的初始状态,如果初始状态处于终止状态,为 true;否则为 false。
多线程并发测试由以下步骤完成:
- 创建并发测试的线程数,先创建的线程等待最后一个线程创建完成。
- 所有线程执行待测试的方法,返回测试的结果。
- 等所有线程执行完成后,进入思考时间等待。
- 继续进行循环测试。
我们来看这个多线程并发测试的代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
|
using
System;
using
System.Collections.Generic;
using
System.Diagnostics;
using
System.Threading;
/// <summary>
/// 并发测试
/// </summary>
public
class
ConcurrentTest : IDisposable
{
#region 私有方法
/// <summary>
/// 测试方法所在的接口
/// </summary>
private
Func<
bool
> func;
/// <summary>
/// 主线程控制信号
/// </summary>
private
ManualResetEvent manualResetEvent;
/// <summary>
/// 测试线程控制信号
/// </summary>
private
ManualResetEvent threadResetEvent;
/// <summary>
/// 待执行的线程数
/// </summary>
private
List<
int
> threads;
/// <summary>
/// 测试结果
/// </summary>
private
List<ConcurrentTestResult> results;
/// <summary>
/// 执行测试的成功数
/// </summary>
private
int
successCount;
/// <summary>
/// 执行测试的失败数
/// </summary>
private
int
failureCount;
/// <summary>
/// 测试耗时
/// </summary>
private
long
elapsedMilliseconds;
/// <summary>
/// 当前线程
/// </summary>
private
int
currentIndex;
/// <summary>
/// 当前测试的总线程数
/// </summary>
private
int
currentCount;
/// <summary>
/// 思考时间
/// </summary>
private
int
thinkTime;
/// <summary>
/// 重复次数
/// </summary>
private
int
repeatCount;
/// <summary>
/// 测试计时器
/// </summary>
private
Stopwatch stopwatch;
#endregion
#region 构造函数
/// <summary>
/// 构造函数
/// </summary>
public
ConcurrentTest()
{
manualResetEvent =
new
ManualResetEvent(
true
);
threadResetEvent =
new
ManualResetEvent(
true
);
stopwatch =
new
Stopwatch();
}
#endregion
#region 执行测试
/// <summary>
/// 执行多线程测试
/// </summary>
/// <param name="threadCount">需要测试的线程数</param>
/// <param name="func">待执行方法</param>
/// <returns></returns>
public
List<ConcurrentTestResult> Execute(
int
threadCount, Func<
bool
> func)
{
return
Execute(threadCount, 1, func);
}
/// <summary>
/// 执行多线程测试
/// </summary>
/// <param name="threadCount">需要测试的线程数</param>
/// <param name="repeatCount">重复次数</param>
/// <param name="func">待执行方法</param>
/// <returns></returns>
public
List<ConcurrentTestResult> Execute(
int
threadCount,
int
repeatCount, Func<
bool
> func)
{
return
Execute(threadCount, 0, repeatCount, func);
}
/// <summary>
/// 执行多线程测试
/// </summary>
/// <param name="threadCount">需要测试的线程数</param>
/// <param name="thinkTime">思考时间,单位耗秒</param>
/// <param name="repeatCount">重复次数</param>
/// <param name="func">待执行方法</param>
/// <returns></returns>
public
List<ConcurrentTestResult> Execute(
int
threadCount,
int
thinkTime,
int
repeatCount, Func<
bool
> func)
{
return
Execute(
new
List<
int
>() { threadCount }, thinkTime, repeatCount, func);
}
/// <summary>
/// 执行多线程测试
/// </summary>
/// <param name="threads">分别需要测试的线程数</param>
/// <param name="thinkTime">思考时间,单位耗秒</param>
/// <param name="repeatCount">重复次数</param>
/// <param name="func">待执行方法</param>
/// <returns></returns>
public
List<ConcurrentTestResult> Execute(List<
int
> threads,
int
thinkTime,
int
repeatCount, Func<
bool
> func)
{
this
.func = func;
this
.threads = threads;
this
.thinkTime = thinkTime;
this
.repeatCount = repeatCount;
CheckParameters();
CreateMultiThread();
return
this
.results;
}
#endregion
#region 验证参数
/// <summary>
/// 验证参数
/// </summary>
private
void
CheckParameters()
{
if
(func ==
null
)
throw
new
ArgumentNullException(
"func不能为空"
);
if
(threads ==
null
|| threads.Count == 0)
throw
new
ArgumentNullException(
"threads不能为空或者长度不能为0"
);
if
(thinkTime < 0)
throw
new
Exception(
"thinkTime不能小于0"
);
if
(repeatCount <= 0)
throw
new
Exception(
"repeatCount不能小于等于0"
);
}
#endregion
#region 创建多线程并执行测试
/// <summary>
/// 创建多线程进行测试
/// </summary>
private
void
CreateMultiThread()
{
results =
new
List<ConcurrentTestResult>(threads.Count);
foreach
(
int
threadCount
in
threads)
{
for
(
int
repeat = 0; repeat < repeatCount; repeat++)
{
//主线程进入阻止状态
manualResetEvent.Reset();
//测试线程进入阻止状态
threadResetEvent.Reset();
stopwatch.Reset();
currentCount = threadCount;
currentIndex = 0;
successCount = 0;
failureCount = 0;
elapsedMilliseconds = 0;
for
(
int
i = 0; i < currentCount; i++)
{
Thread t =
new
Thread(
new
ThreadStart(DoWork));
t.Start();
}
//阻止主线程,等待测试线程完成测试
manualResetEvent.WaitOne();
results.Add(
new
ConcurrentTestResult()
{
FailureCount = failureCount,
SuccessCount = successCount,
ElapsedMilliseconds = elapsedMilliseconds
});
Thread.Sleep(thinkTime);
}
}
}
/// <summary>
/// 执行测试方法
/// </summary>
private
void
DoWork()
{
bool
executeResult;
Interlocked.Increment(
ref
currentIndex);
if
(currentIndex < currentCount)
{
//等待所有线程创建完毕后同时执行测试
threadResetEvent.WaitOne();
}
else
{
//最后一个线程创建完成,通知所有线程,开始执行测试
threadResetEvent.Set();
//开始计时
stopwatch.Start();
}
//执行测试
executeResult = func();
Interlocked.Decrement(
ref
currentIndex);
if
(currentIndex == 0)
{
//最后一个线程执行的测试结束,结束计时
stopwatch.Stop();
elapsedMilliseconds = stopwatch.ElapsedMilliseconds;
//保存测试结果
if
(executeResult)
Interlocked.Increment(
ref
successCount);
else
Interlocked.Increment(
ref
failureCount);
//通知主线程继续
manualResetEvent.Set();
}
else
{
//保存测试结果
if
(executeResult)
Interlocked.Increment(
ref
successCount);
else
Interlocked.Increment(
ref
failureCount);
}
}
#endregion
#region 释放资源
/// <summary>
/// 释放资源
/// </summary>
public
void
Dispose()
{
manualResetEvent.Close();
threadResetEvent.Close();
}
#endregion
}
/// <summary>
/// 并发测试结果
/// </summary>
public
class
ConcurrentTestResult
{
/// <summary>
/// 当前执行线程总数
/// </summary>
public
int
ThreadCount
{
get
{
return
SuccessCount + FailureCount; }
}
/// <summary>
/// 测试成功数
/// </summary>
public
int
SuccessCount {
get
;
set
; }
/// <summary>
/// 测试失败数
/// </summary>
public
int
FailureCount {
get
;
set
; }
/// <summary>
/// 总耗时
/// </summary>
public
long
ElapsedMilliseconds {
get
;
set
; }
}
|
使用起来就非常简单了,我们看测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
class
Program
{
static
void
Main(
string
[] args)
{
using
(ConcurrentTest concurrentTest =
new
ConcurrentTest())
{
var result = concurrentTest.Execute(5, -1, 10,
new
TestClass().Execute);
foreach
(var item
in
result)
{
Console.WriteLine(
"线程数:{0}\t成功:{1}\t失败:{2}\t耗时:{3}"
,
item.ThreadCount, item.SuccessCount, item.FailureCount, item.ElapsedMilliseconds);
}
}
Console.ReadKey(
true
);
}
}
public
class
TestClass
{
public
bool
Execute()
{
int
tempValue = GetRandom();
System.Threading.Thread.Sleep(tempValue);
return
tempValue % 2 == 0;
}
private
int
GetRandom()
{
return
new
Random().Next(990, 1000);
}
}
|
测试类库提供了4个Execute方法的重载,一般情况下能满足我们的多线程并发测试场景了。
另一地址:http://blog.moozi.net/archives/multi-threaded-concurrent-test-library.html