Web 并发压力测试工具Pylot的安装以及使用

ApacheBench 一把的泪,不说了,下面介绍一个python的并发测试工具Pylot

以下为mac下得安装方法,window的自行Google

Pylot介绍

一个简单易用,跨平台的开源网站压力测试工具,可以从命令行运行也可以开启图形界面运行,可以设置cookie,可以将测试结果制作成图表方便查看。

Pylot配置

第1步:下载Pylot (必须)

第2步:安装Python 2.5 + (必须)

第3步:安装wxPython(可选 - 用于GUI模式)

从这里获取安装程序:http://www.wxpython.org/download.php

第4步:安装numpy的(可选 - 用于报告以图表)

从这里获取安装程序:http://sourceforge.net/projects/numpy

第5步:安装matplotlib的(可选 - 用于报告以图表)

从这里获取安装程序:http://sourceforge.net/projects/matplotlib

wxPython,numpy,matplotlib 手动安装比较麻烦,建议使用easy_install安装

easy_install安装

官网的安装方式:https://pypi.python.org/pypi/setuptools#unix-wget

我们直接这样安装:

wget https://bootstrap.pypa.io/ez_setup.py -O - | sudo python

中间停住的时候便输入你的root密码,回车

Pylot安装

下载地址 :http://pylt.googlecode.com/files/pylot_1.26.zip

下载后直接解压即可

安装依赖包

easy_install wxpython numpy matplotlib

Pylot使用

  进入Pylot解压的目录

  首先需要配置一下testcases.xml这个文件。这个文件包含了需要测试的目标url以及具体的测试内容。

  大家打开testcases.xml之后可以看到两个……,因为我们只是要一个简单的实例,所以我们把第二个….删掉,然后把第一个….里面的改成你需要测试的url。我的就是http://3wmao.com。改好之后整个文章内容是这样的   

1
2
3
4
5
6
7
<testcases>
<!-- SAMPLE TEST CASE -->
<case>
<url>http://3wmao.com</url>
</case>
-->
</testcases>

好了,保存关闭。运行以下命令

python run.py -a 50

输出的html信息在results目录下

参数详细说明

-a, —agents=NUM_AGENTS

设置同时访问用户数量

-d, —duration=DURATION

设置总测试时间(秒)

-r, —rampup=RAMPUP

设置提升量(秒),我也没太搞懂这个是什么意思

-i, —interval=INTERVAL

设置访问间隔(毫秒)

-x, —xmlfile=TEST_CASE_XML

设置要使用的xml文件,默认testcase.xml

-o, —output_dir=PATH

设置输出文件路径

-n, —name=TESTNAME

设置测试名称

-l, —log_msgs

设置是否需要日志信息

-b, —blocking

设置是否开启锁定模式,如果开启会锁定输出直到测试结束

-g, —gui

设置是否使用图形界面

-p, —port=PORT

设置xml-rpc监听的端口

使用-g选项的话就可以开启图形界面,图形界面我就不详细介绍了,虽然有几个英文单词吧,但是大家应该一眼就能看明白怎么用了,还是挺方便的。

参考来源:http://www.cnblogs.com/numbbbbb/archive/2013/04/19/3029959.html

./configure,make,make install

今天没事折腾apache bench,发现在压力数值调大的时候发生异常,网上各种方法试过都无解。。。

正题:脚本开发人员一般很少接触源代码编译的工作,即使用到,很多也不清楚具体命令的含义,今天就详细解释一下

这几个命令都是典型的使用GNU的AUTOCONF和AUTOMAKE产生的程序的安装步骤

./configure

是用来检测你的安装平台的目标特征的。比如它会检测你是不是有CC或GCC,并不是需要CC或GCC,它是个shell脚本

configure,这一步一般用来生成 Makefile,为下一步的编译做准备,

你可以通过在 configure 后加上参数来对安装进行控制,

比如代码:./configure –prefix=/usr

意思是将该软件安装在 /usr 下面,

执行文件就会安装在 /usr/bin (而不是默认的 /usr/local/bin),

资源文件就会安装在 /usr/share(而不是默认的/usr/local/share)。

同时一些软件的配置文件你可以通过指定 –sys-config= 参数进行设定。

有一些软件还可以加上 –with、–enable、–without、–disable 等等参数对编译加以控制,

你可以通过允许 ./configure –help 察看详细的说明帮助。

configure是一个shell脚本,它可以自动设定源程序以符合各种不同平台上Unix系统的特性,并且根据系统叁数及环境产生合适的Makefile文件或是C的头文件(header file),让源程序可以很方便地在这些不同的平台上被编译连接。

make

是用来编译的,它从Makefile中读取指令,然后编译。

make,这一步就是编译,大多数的源代码包都经过这一步进行编译(当然有些perl或python编写的软件需要调用perl或python来进行编译)。

如果 在 make 过程中出现 error ,你就要记下错误代码(注意不仅仅是最后一行),然后你可以向开发者提交 bugreport(一般在 INSTALL 里有提交地址),或者你的系统少了一些依赖库等,这些需要自己仔细研究错误代码

make install

是用来安装的,它也从Makefile中读取指令,安装到指定的位置。

make insatll,这条命令来进行安装(当然有些软件需要先运行 make check 或 make test 来进行一些测试),这一步一般需要你有 root 权限(因为要向系统写入文件)。

AUTOMAKE和AUTOCONF是非常有用的用来发布C程序的东西。

CGContextRef绘制各种基本图形

Graphics Context是图形上下文,可以将其理解为一块画布,我们可以在上面进行绘画操作,绘制完成后,将画布放到我们的view中显示即可,view看作是一个画框.

效果图:

draw

自定义CustomView类,CustomView.h:

1
2
3
4
5
6
7
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#define PI 3.14159265358979323846
@interface CustomView : UIView
@end

实现类CustomView.m:

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
#import "CustomView.h"
@implementation CustomView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
// 覆盖drawRect方法,你可以在此自定义绘画和动画
- (void)drawRect:(CGRect)rect
{
//An opaque type that represents a Quartz 2D drawing environment.
//一个不透明类型的Quartz 2D绘画环境,相当于一个画布,你可以在上面任意绘画
CGContextRef context = UIGraphicsGetCurrentContext();
/*写文字*/
CGContextSetRGBFillColor (context, 1, 0, 0, 1.0);//设置填充颜色
UIFont *font = [UIFont boldSystemFontOfSize:15.0];//设置
[@"画圆:" drawInRect:CGRectMake(10, 20, 80, 20) withFont:font];
[@"画线及孤线:" drawInRect:CGRectMake(10, 80, 100, 20) withFont:font];
[@"画矩形:" drawInRect:CGRectMake(10, 120, 80, 20) withFont:font];
[@"画扇形和椭圆:" drawInRect:CGRectMake(10, 160, 110, 20) withFont:font];
[@"画三角形:" drawInRect:CGRectMake(10, 220, 80, 20) withFont:font];
[@"画圆角矩形:" drawInRect:CGRectMake(10, 260, 100, 20) withFont:font];
[@"画贝塞尔曲线:" drawInRect:CGRectMake(10, 300, 100, 20) withFont:font];
[@"图片:" drawInRect:CGRectMake(10, 340, 80, 20) withFont:font];
/*画圆*/
//边框圆
CGContextSetRGBStrokeColor(context,1,1,1,1.0);//画笔线的颜色
CGContextSetLineWidth(context, 1.0);//线的宽度
//void CGContextAddArc(CGContextRef c,CGFloat x, CGFloat y,CGFloat radius,CGFloat startAngle,CGFloat endAngle, int clockwise)1弧度=180°/π (≈57.3°) 度=弧度×180°/π 360°=360×π/180 =2π 弧度
// x,y为圆点坐标,radius半径,startAngle为开始的弧度,endAngle为 结束的弧度,clockwise 0为顺时针,1为逆时针。
CGContextAddArc(context, 100, 20, 15, 0, 2*PI, 0); //添加一个圆
CGContextDrawPath(context, kCGPathStroke); //绘制路径
//填充圆,无边框
CGContextAddArc(context, 150, 30, 30, 0, 2*PI, 0); //添加一个圆
CGContextDrawPath(context, kCGPathFill);//绘制填充
//画大圆并填充颜
UIColor*aColor = [UIColor colorWithRed:1 green:0.0 blue:0 alpha:1];
CGContextSetFillColorWithColor(context, aColor.CGColor);//填充颜色
CGContextSetLineWidth(context, 3.0);//线的宽度
CGContextAddArc(context, 250, 40, 40, 0, 2*PI, 0); //添加一个圆
//kCGPathFill填充非零绕数规则,kCGPathEOFill表示用奇偶规则,kCGPathStroke路径,kCGPathFillStroke路径填充,kCGPathEOFillStroke表示描线,不是填充
CGContextDrawPath(context, kCGPathFillStroke); //绘制路径加填充
/*画线及孤线*/
//画线
CGPoint aPoints[2];//坐标点
aPoints[0] =CGPointMake(100, 80);//坐标1
aPoints[1] =CGPointMake(130, 80);//坐标2
//CGContextAddLines(CGContextRef c, const CGPoint points[],size_t count)
//points[]坐标数组,和count大小
CGContextAddLines(context, aPoints, 2);//添加线
CGContextDrawPath(context, kCGPathStroke); //根据坐标绘制路径
//画笑脸弧线
//左
CGContextSetRGBStrokeColor(context, 0, 0, 1, 1);//改变画笔颜色
CGContextMoveToPoint(context, 140, 80);//开始坐标p1
//CGContextAddArcToPoint(CGContextRef c, CGFloat x1, CGFloat y1,CGFloat x2, CGFloat y2, CGFloat radius)
//x1,y1跟p1形成一条线的坐标p2,x2,y2结束坐标跟p3形成一条线的p3,radius半径,注意, 需要算好半径的长度,
CGContextAddArcToPoint(context, 148, 68, 156, 80, 10);
CGContextStrokePath(context);//绘画路径
//右
CGContextMoveToPoint(context, 160, 80);//开始坐标p1
//CGContextAddArcToPoint(CGContextRef c, CGFloat x1, CGFloat y1,CGFloat x2, CGFloat y2, CGFloat radius)
//x1,y1跟p1形成一条线的坐标p2,x2,y2结束坐标跟p3形成一条线的p3,radius半径,注意, 需要算好半径的长度,
CGContextAddArcToPoint(context, 168, 68, 176, 80, 10);
CGContextStrokePath(context);//绘画路径
//右
CGContextMoveToPoint(context, 150, 90);//开始坐标p1
//CGContextAddArcToPoint(CGContextRef c, CGFloat x1, CGFloat y1,CGFloat x2, CGFloat y2, CGFloat radius)
//x1,y1跟p1形成一条线的坐标p2,x2,y2结束坐标跟p3形成一条线的p3,radius半径,注意, 需要算好半径的长度,
CGContextAddArcToPoint(context, 158, 102, 166, 90, 10);
CGContextStrokePath(context);//绘画路径
//注,如果还是没弄明白怎么回事,请参考:http://donbe.blog.163.com/blog/static/138048021201052093633776/
/*画矩形*/
CGContextStrokeRect(context,CGRectMake(100, 120, 10, 10));//画方框
CGContextFillRect(context,CGRectMake(120, 120, 10, 10));//填充框
//矩形,并填弃颜色
CGContextSetLineWidth(context, 2.0);//线的宽度
aColor = [UIColor blueColor];//blue蓝色
CGContextSetFillColorWithColor(context, aColor.CGColor);//填充颜色
aColor = [UIColor yellowColor];
CGContextSetStrokeColorWithColor(context, aColor.CGColor);//线框颜色
CGContextAddRect(context,CGRectMake(140, 120, 60, 30));//画方框
CGContextDrawPath(context, kCGPathFillStroke);//绘画路径
//矩形,并填弃渐变颜色
//关于颜色参考http://blog.sina.com.cn/s/blog_6ec3c9ce01015v3c.html
//http://blog.csdn.net/reylen/article/details/8622932
//第一种填充方式,第一种方式必须导入类库quartcore并#import <QuartzCore/QuartzCore.h>,这个就不属于在context上画,而是将层插入到view层上面。那么这里就设计到Quartz Core 图层编程了。
CAGradientLayer *gradient1 = [CAGradientLayer layer];
gradient1.frame = CGRectMake(240, 120, 60, 30);
gradient1.colors = [NSArray arrayWithObjects:(id)[UIColor whiteColor].CGColor,
(id)[UIColor grayColor].CGColor,
(id)[UIColor blackColor].CGColor,
(id)[UIColor yellowColor].CGColor,
(id)[UIColor blueColor].CGColor,
(id)[UIColor redColor].CGColor,
(id)[UIColor greenColor].CGColor,
(id)[UIColor orangeColor].CGColor,
(id)[UIColor brownColor].CGColor,nil];
[self.layer insertSublayer:gradient1 atIndex:0];
//第二种填充方式
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
CGFloat colors[] =
{
1,1,1, 1.00,
1,1,0, 1.00,
1,0,0, 1.00,
1,0,1, 1.00,
0,1,1, 1.00,
0,1,0, 1.00,
0,0,1, 1.00,
0,0,0, 1.00,
};
CGGradientRef gradient = CGGradientCreateWithColorComponents
(rgb, colors, NULL, sizeof(colors)/(sizeof(colors[0])*4));//形成梯形,渐变的效果
CGColorSpaceRelease(rgb);
//画线形成一个矩形
//CGContextSaveGState与CGContextRestoreGState的作用
/*
CGContextSaveGState函数的作用是将当前图形状态推入堆栈。之后,您对图形状态所做的修改会影响随后的描画操作,但不影响存储在堆栈中的拷贝。在修改完成后,您可以通过CGContextRestoreGState函数把堆栈顶部的状态弹出,返回到之前的图形状态。这种推入和弹出的方式是回到之前图形状态的快速方法,避免逐个撤消所有的状态修改;这也是将某些状态(比如裁剪路径)恢复到原有设置的唯一方式。
*/
CGContextSaveGState(context);
CGContextMoveToPoint(context, 220, 90);
CGContextAddLineToPoint(context, 240, 90);
CGContextAddLineToPoint(context, 240, 110);
CGContextAddLineToPoint(context, 220, 110);
CGContextClip(context);//context裁剪路径,后续操作的路径
//CGContextDrawLinearGradient(CGContextRef context,CGGradientRef gradient, CGPoint startPoint, CGPoint endPoint,CGGradientDrawingOptions options)
//gradient渐变颜色,startPoint开始渐变的起始位置,endPoint结束坐标,options开始坐标之前or开始之后开始渐变
CGContextDrawLinearGradient(context, gradient,CGPointMake
(220,90) ,CGPointMake(240,110),
kCGGradientDrawsAfterEndLocation);
CGContextRestoreGState(context);// 恢复到之前的context
//再写一个看看效果
CGContextSaveGState(context);
CGContextMoveToPoint(context, 260, 90);
CGContextAddLineToPoint(context, 280, 90);
CGContextAddLineToPoint(context, 280, 100);
CGContextAddLineToPoint(context, 260, 100);
CGContextClip(context);//裁剪路径
//说白了,开始坐标和结束坐标是控制渐变的方向和形状
CGContextDrawLinearGradient(context, gradient,CGPointMake
(260, 90) ,CGPointMake(260, 100),
kCGGradientDrawsAfterEndLocation);
CGContextRestoreGState(context);// 恢复到之前的context
//下面再看一个颜色渐变的圆
CGContextDrawRadialGradient(context, gradient, CGPointMake(300, 100), 0.0, CGPointMake(300, 100), 10, kCGGradientDrawsBeforeStartLocation);
/*画扇形和椭圆*/
//画扇形,也就画圆,只不过是设置角度的大小,形成一个扇形
aColor = [UIColor colorWithRed:0 green:1 blue:1 alpha:1];
CGContextSetFillColorWithColor(context, aColor.CGColor);//填充颜色
//以10为半径围绕圆心画指定角度扇形
CGContextMoveToPoint(context, 160, 180);
CGContextAddArc(context, 160, 180, 30, -60 * PI / 180, -120 * PI / 180, 1);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke); //绘制路径
//画椭圆
CGContextAddEllipseInRect(context, CGRectMake(160, 180, 20, 8)); //椭圆
CGContextDrawPath(context, kCGPathFillStroke);
/*画三角形*/
//只要三个点就行跟画一条线方式一样,把三点连接起来
CGPoint sPoints[3];//坐标点
sPoints[0] =CGPointMake(100, 220);//坐标1
sPoints[1] =CGPointMake(130, 220);//坐标2
sPoints[2] =CGPointMake(130, 160);//坐标3
CGContextAddLines(context, sPoints, 3);//添加线
CGContextClosePath(context);//封起来
CGContextDrawPath(context, kCGPathFillStroke); //根据坐标绘制路径
/*画圆角矩形*/
float fw = 180;
float fh = 280;
CGContextMoveToPoint(context, fw, fh-20); // 开始坐标右边开始
CGContextAddArcToPoint(context, fw, fh, fw-20, fh, 10); // 右下角角度
CGContextAddArcToPoint(context, 120, fh, 120, fh-20, 10); // 左下角角度
CGContextAddArcToPoint(context, 120, 250, fw-20, 250, 10); // 左上角
CGContextAddArcToPoint(context, fw, 250, fw, fh-20, 10); // 右上角
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke); //根据坐标绘制路径
/*画贝塞尔曲线*/
//二次曲线
CGContextMoveToPoint(context, 120, 300);//设置Path的起点
CGContextAddQuadCurveToPoint(context,190, 310, 120, 390);//设置贝塞尔曲线的控制点坐标和终点坐标
CGContextStrokePath(context);
//三次曲线函数
CGContextMoveToPoint(context, 200, 300);//设置Path的起点
CGContextAddCurveToPoint(context,250, 280, 250, 400, 280, 300);//设置贝塞尔曲线的控制点坐标和控制点坐标终点坐标
CGContextStrokePath(context);
/*图片*/
UIImage *image = [UIImage imageNamed:@"apple.jpg"];
[image drawInRect:CGRectMake(60, 340, 20, 20)];//在坐标中画出图片
// [image drawAtPoint:CGPointMake(100, 340)];//保持图片大小在point点开始画图片,可以把注释去掉看看
CGContextDrawImage(context, CGRectMake(100, 340, 20, 20), image.CGImage);//使用这个使图片上下颠倒了,参考http://blog.csdn.net/koupoo/article/details/8670024
// CGContextDrawTiledImage(context, CGRectMake(0, 0, 20, 20), image.CGImage);//平铺图
}
@end

转载自:(http://blog.csdn.net/rhljiayou/article/details/9919713)

Block常用用法

转载自:http://blog.csdn.net/enuola/article/details/8674063

Block简介:

Block的实际行为和Function很像,最大的差别是在可以存取同一个Scope的变量值。Block实体形式如下:

^(传入参数列){行为主体};

Block实体开头是“^”,接着是由小括号所包起来的参数列(比如 int a, int b, int c),行为主体由大括号包起来,专有名字叫做block literal。行为主体可以用return回传值,类型会被compiler自动辨别。如果没有参数列要写成:

^(void)

例如下面的一个例子:

1
^(int a){return a*a;};

这是代表Block会回传输入值的平方值(int a 就是参数列, return a*a; 就是行为主体)。记得行为主体里最后要加“;”,因为是叙述,而整个{}最后也要加“;”,因为Block是物件实体。用法如下:

1
2
int result = ^(int a){return a*a;}(5);
NSLog(@"%d", result);

很奇怪吧?后面的小括号里面的5会被当成a的输入值,然后经由Block输出 5*5 = 25指定给result这个变量。
有没有简单一点的方法嗯?不然每次都写这么长?有。接下来介绍一个叫做Block Pointer的东西来简化我们的写法。

Block Pointer是这样定义的:

回传值(^名字)(参数列);

比如下面的例子:

1
2
3
4
5
6
7
//声明一个square的Block Pointer,其所指向的Block有一个int输入和int输出
int (^square)(int);
//将Block实体指定给square
square = ^(int a){ return a*a ; };
//调用方法,感觉是是不是很像function的用法?
int result = square(5);
NSLog(@"%d", result);

是不是变的简单了?

也可以吧Block Pointer当成参数传递给一个function,比如:

1
2
3
void myFunction(int (^mySquare)(int)); //function的定义,将Block作为参数
int (^mySquare)(int) = ^(int a){return a*a;}; //定义一个mySquare的Block pointer变量
myFunction(mySquare); //把mySquare作为myFunction的参数

上面的三行代码其实等价于下面这行代码

1
myFunction( ^int(int a){return a*a;} );

当其作为Object-C method的传入值的话,需要把类型写在变量前面,然后加上小括号。比如下面这种写法:

1
-(void)objcMethod:(int(^)(int))square; //square参数的类型是int(^)(int)

存取变量

1、可以读取和Block pointer同一个Scope的变量值:

1
2
3
4
5
6
7
{
int outA = 8;
int (^myPtr)(int) = ^(int a){ return outA + a;};
//block里面可以读取同一类型的outA的值
int result = myPtr(3); // result is 11
NSLog(@"result=%d", result);
}

下面来看一段很有意思的代码:

1
2
3
4
5
6
7
{
int outA = 8;
int (^myPtr)(int) = ^(int a){ return outA + a;}; //block里面可以读取同一类型的outA的值
outA = 5; //在调用myPtr之前改变outA的值
int result = myPtr(3); // result的值仍然是11,并不是8
NSLog(@"result=%d", result);
}

为什么result 的值仍然是11?而不是8呢?

事实上,myPtr在其主体中用到的outA这个变量值的时候做了一个copy的动作,把outA的值copy下来。所以,之后outA即使换成了新的值,对于myPtr里面copy的值是没有影响的。

需要注意的是,这里copy的值是变量的值,如果它是一个记忆体的位置(地址),换句话说,就是这个变量是个指针的话,

它的值是可以在block里被改变的。如下例子:

1
2
3
4
5
{
NSMutableArray *mutableArray = [NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil];
int result = ^(int a){[mutableArray removeLastObject]; return a*a;}(5);
NSLog(@"test array :%@", mutableArray);
}

原本mutableArray的值是{@”one”,@”two”,@”three”},在block里面被更改mutableArray后,就变成{@”one”, @”two”}了。

2、直接存取static类型的变量

1
2
3
4
5
6
7
{
static int outA = 8;
int (^myPtr)(int) = ^(int a){return outA + a;};
outA = 5;
int result = myPtr(3); //result的值是8,因为outA是static类型的变量
NSLog(@"result=%d", result);
}

甚至可以直接在block里面修改outA的值,例如下面的写法:

1
2
3
4
5
6
{
static int outA = 8;
int (^myPtr)(int) = ^(int a){ outA = 5; return outA + a;};
int result = myPtr(3); //result的值是8,因为outA是static类型的变量
NSLog(@"result=%d", result);
}

3、Block Variable类型的变量

在某个变量前面如果加上修饰字“__block”的话(注意,block前面有两个下划线),这个变量就称作block variable。

那么在block里面就可以任意修改此变量的值,如下代码:

1
2
3
4
5
6
7
8
{
__block int num = 5;
int (^myPtr)(int) = ^(int a){return num++;};
int (^myPtr2)(int) = ^(int a){return num++;};
int result = myPtr(0); //result的值为5,num的值为6
result = myPtr2(0); //result的值为6,num的值为7
NSLog(@"result=%d", result);
}

因为myPtr和myPtr2都有用到num这个block variable,最终num的值为7.