泛型工厂应用改进尝试(一)
实验声明
调用者的产品只要满足以下两个条件即可通过该工厂生产食物:
1.要求食物都继承自Food抽象类
2.无参构造方法公开(如果没有单独声明构造方法,默认为公开的无参构造)
专设术语
①分步制作流程:将一个方法的逻辑拆分为多个方法,并且给每一个方法都加上唯一的访问锁。
②静态存储:利用静态的存储结构来对数据进行存储。
③中间处理逻辑:对静态存储结构的处理,判断当前类型是否存在于静态存储结构中,不存在则创建,使用静态存储就必须使用中间处理逻辑,不使用静态存储则不需要使用中间处理逻辑。
测试思想
通过DateTime.Now获取方法执行的开始时间和结束时间,然后两个时间差值单位精确到毫秒。单线程数值保留4位小数,要求每执行一个线程休眠100毫秒,获取11个单线程执行的总计耗时算作一组数据,共获取20组数据,测试2次;多线程数值不保留小数,多线程共计12线程,要求多线程并发执行,不存在线程休眠,获取11个线程全部执行完成的总计耗时算作一组数据,共获取20组数据,测试2次,其它多线程并发测试类似。(之所以不统计12个线程是因为第一个执行的线程包含了C#文件的编译耗时,所以只统计第二个到最后一个,共计11个线程)
测试数据求算公式
(设20组数据分别为a1,a2,a3,…,a20,线程数11):
线程平均耗时=(a1/11+a2/11+a3/11+…+a20/11)/20(保留4位小数)
实验过程、分析及结果
这种泛型工厂存在两个问题,第一它是通过反射来调用构造方法,所以速度非常慢,第二由于存在对公共资源的读取,所以要考虑资源冲突。
第一个问题如果想要改进,可以创建两个静态存储结构用于存储已经调用过至少一次的对应类型的构造方法信息以及这种食物类型,当再生产同一类型食物时就不需要再去通过反射查找构造信息了,而是直接从静态存储结构中查找,这种方法也算是牺牲存储来换取速度吧,类似于一种缓存思想吧。
第二个问题,我们需要访问的公共资源就是为了解决第一个问题的静态存储结构,当多个线程中都调用工厂生产食品时,都会使用到这个静态存储结构,就会存在资源冲突,所以必须对资源加锁访问,但是这么做又会导致我们生产食品时每一个产品都需要等前面的产品完全生产完成后才能进入生产过程,而不能够多线程并发生产,这就违背工厂的本质了。
为了解决第二个问题,我认为可以将制作流程中的逻辑进行进一步划分,划分为几个有序的方法,而我们需要保证每一个方法都有对应的锁,例如食物的制作流程有获取原材料、生产食物、包装食物,那么我们可以把整个流程分为三个流程,然后分别加上唯一的访问锁。
现在有线程A,B,C,D,E,F六个线程分别制作奶黄面包、夹层饼干、青柠薯片、吐司面包、草莓饼干、香辣薯片,制作流程按顺序分别是M1(获取原材料),M2(生产食物),M3(包装食物)。
三种食物采用一条生产线明显不太理想,我们可以给每种食物制订一条生产线,面包为L1,饼干为L2,薯片为L3(三个生产线的工作都可以概括为M1+M2+M3)。A和D都生产面包所以使用L1生产线,B和E都生产饼干所以使用L2生产线,C和F都生产薯片所以使用L3生产线。
思路1
首先假设A进入L1生产线,获取M1的锁执行M1时,D需要等待A执行完M1+M2+M3才能开始生产;
B进入了L2生产线,获取了M1的锁执行M1,E需要等待B执行完M1+M2+M3才能开始生产;
C进入L3生产线,获取了M1的锁执行M1,F需要等待C执行完M1+M2+M3才能开始生产。
根据思路1,我进行了对比测试和简单测试,接下来我们一起看看实验结果吧。
(请查看测试表中的对比测试,对比测试比较单线程下反射和直接new对象的耗时)
要求启动12个线程,不使用分步制作流程,不使用静态存储,无中间处理逻辑,通过对比测试可知:
发现单线程执行耗时:约0.02236ms
多线程并发执行耗时:约36.2273ms
单线程直接new对象耗时:约0.0019ms
结论1:从上述结果我们可以发现单线程下反射的速率相比直接new对象慢了约10.77倍。
虽然通过反射让我们的工厂更加多态也更加接近面向对象,但是单纯使用反射在面对多线程时会带来巨大的性能开销。我们发现反射的效率比较低,参考了网上的资料发现,可以采用静态存储的方式来将类型信和构造器信息存储下来,下一次创建对象时就不需要通过反射查找而是直接根据静态存储中的生产资料创建对象即可,所以来验证一下这个方法是否有效?如果有效,它相对直接反射能提高多少速率呢?
(请查看测试表中的简单测试,简单测试比较单线程下直接使用反射和使用静态存储的耗时)
要求启动5个线程,不使用分步制作流程,使用静态存储,有中间处理逻辑,通过简单测试可知:
结论2:单线程下,使用静态存储比直接使用反射(无中间处理逻辑)的速率慢约7.3%,使用静态存储比直接使用反射(有中间处理逻辑)的速率快约26.4%。
这个结果出乎了我的意料,我的预测是使用静态存储会更快一些,但是实验结果却大相径庭。出现这个结果的原因是因为中间处理逻辑导致的,那我们应该怎么弥补中间处理逻辑所带来的时间消耗呢?我们可以尝试从对象的生产过程考虑,思路1要求我们必须按照顺序(M1-M2-M3)生产出一个食物后才能开始生产下一个食物,这就使得所有线程必须有序地一个一个进行生产,但是我们知道,一个食物大致可以分为M1-M2-M3三个生产过程,当一个食物进入M2或M3时,为了提高速率我们可以让下一个待制作的食物进入M1这个生产流程,这样可以使得资源的利用率提升,那应该怎么做呢?我们可以尝试将M1-M2-M3放在一个方法中,然后给每一个制作流程加上一个访问锁,这样多线程访问时,既能保证制作流程顺序又能够并发执行提高效率,这就是分步制作流程,所以实验思路就变成了思路2。
思路2
首先假设A进入L1生产线,获取M1的锁执行M1,当A执行完M1获取M2的锁执行M2时,D进入L1生产线获取M1的锁执行M1;
B进入了L2生产线,获取M1的锁执行M1,当B执行完M1获取M2的锁执行M2时,E进入L2生产线获取M1的锁执行M1;
C进入L3生产线,获取M1的锁执行M1,当C执行完M1获取M2的锁执行M2时,F进入L3生产线获取M1的锁执行M1。
那么思路2是否有效?如果有效能提高多少速率?
(请查看测试表中的对比测试2,对比测试2对比是否使用分步制作流程和是否使用静态存储)
①启动12线程,使用分步制作流程,使用静态存储,有中间处理逻辑
②启动12线程,使用分步制作流程,不使用静态存储,有中间处理逻辑
③启动12线程,不使用分步制作流程,使用静态存储,有中间处理逻辑
④启动12线程,使用分步制作流程,不使用静态存储,无中间处理逻辑
⑤启动12线程,不使用分步制作流程,不使用静态存储,无中间处理逻辑
⑥启动12线程,不使用分步制作流程,不使用静态存储,有中间处理逻辑
原则上我们应该是有8组方案,但是有两组不符合专设术语中的要求,所以进行了舍弃,通过对比测试2,得出以下结果:
发现单线程执行耗时:⑤<④<①<②<③≈⑥
多线程并发执行耗时:④<①<⑤≈②<⑥≈③
说明一下,②⑥可以不使用中间处理逻辑,但是为了遵循控制变量唯一的原则加入了中间处理逻辑。
结论3:通过比较①②可以发现,单线程中使用静态存储比不使用静态存储提升约44.7%的速率,而多线程并发执行中使用静态存储相比不使用静态存储提升约6.6%。
结论4:通过比较①③可以发现,单线程中使用分步制作流程比不使用分步制作流程提升约75.1%的速率,而多线程并发执行时我们发现使用分步制作流程相比不使用分步制作流程提升约34.4%的速率。
结论5:⑤才是真正意义上的直接调用反射,因为反射并不需要中间处理逻辑,而④对⑤进行了优化,加入了分步制作流程,在多线程并发执行中使用分步制作流程使得④在⑤的基础上提升约29.3%的速率,但是在单线程中⑤比④更快速,结合结论4可以说明分步制作流程适用于有静态存储或者无静态存储多线程并发执行的情况。
结论6:对比⑤⑥可以发现中间处理逻辑对执行速率的影响,中间处理逻辑必然会使得执行速率降低,但是我们还是有必要知道它的影响程度,⑥在⑤的基础上加入了中间处理逻辑导致单线程下速率降低98.7%,多线程并发执行速率降低约26.4%,这说明中间处理逻辑非常影响速率,但是对于静态存储而言,中间处理逻辑是必不可少的,所以我们只能尝试优化而不可能禁用。
结论7:对比③⑥可以发现,当单独使用静态存储而不使用分步制作流程时,无论是单线程还是多线程并发执行的情况,都无法提升速率,结合结论1说明静态存储需要结合分步制作流程才能提升速率。
结合结论5和结论7可知在能够提升速率的情况下,分步制作流程是静态存储的必要不充分条件。再根据前几次测试的结果可以看出,12个线程抢一把锁完成A方法比12个线程抢三把锁分步完成A方法的三个部分速率更慢,也就是说分步制作流程的想法还是能够提升一定速率的,而静态存储在处理多线程时并不能够有效提升速率,因为静态存储是涉及对存储结构的遍历查找的,随着存储结构存储的数据越多,就会变得越慢,最后总会低于直接使用反射的速率,唯一能够优化的或许只能从存储结构和相关的查找算法两个角度去考虑了。我猜测分步制作流程对速率的提升程度会随着锁的数量增多或中间处理逻辑变得复杂而降低,不过这需要我进一步测试验证,接下来我们尝试对存储结构进行优化。
(请查看测试表中的对比测试3,对比测试3对比是否使用分步制作流程)
①启动12线程,使用分步制作流程,使用静态存储,有中间处理逻辑
②启动12线程,不使用分步制作流程,使用静态存储,有中间处理逻辑
在对比测试3中,我对静态存储结构进行了自定义,进而简化了中间的一些判断过程,幸运的是这确实得到了进一步优化,测试结果如下:
结论8:同在对比测试3中,单线程使用分步制作流程相比不使用提升约57.1%的速率,多线程并发执行中使用分步制作流程相比不使用提升约9.9%的速率。
结论9:与对比测试2中的①相比较,单线程中对比测试3中①的速率提升约36.2%,多线程并发执行中对比测试3中①的速率提升约27.0%。
结论10:与对比测试2中的⑤相比较,单线程中对比测试3中①的速率提升约18.3%,多线程并发执行中对比测试3中①的速率提升约31.5%。
结论9说明自定义的静态存储结构比双列表更适合我们的工厂,结论10说明我们成功通过分步制作流程和自定义静态存储结构的方式弥补了中间处理逻辑所带来的时间消耗,并且在此基础上还提升了一定速率,使得当前的模式比直接调用反射更快。字典作为静态存储结构也是一个不错的选择,那我们一起看看字典和我们自定义的结构相比哪一个更适合我们的工厂吧。
(请查看测试表中的对比测试4,对比测试4对比使用字典和自定义存储结构)
①启动3线程,使用分步制作流程,使用静态存储,有中间处理逻辑,静态存储结构为Dictionary
②启动3线程,使用分步制作流程,使用静态存储,有中间处理逻辑,静态存储结构为自定义存储结构
③启动6线程,使用分步制作流程,使用静态存储,有中间处理逻辑,静态存储结构为Dictionary
④启动6线程,使用分步制作流程,使用静态存储,有中间处理逻辑,静态存储结构为自定义存储结构
⑤启动12线程,使用分步制作流程,使用静态存储,有中间处理逻辑,静态存储结构为Dictionary
⑥启动12线程,使用分步制作流程,使用静态存储,有中间处理逻辑,静态存储结构为自定义存储结构
在对比测试4中,我们除了对比静态存储结构,也对启动的线程数进行了改变,通过对比测试4我们可以得出以下结果:
发现单线程执行耗时:⑥<⑤<④<③<②<①
多线程并发执行耗时:②<④<①<③<⑥<⑤
结论11:我们发现单线程执行耗时呈现出一个规律,线程数越多,单线程执行耗时越少。
这是因为静态存储在每次加入一个新的元素时都会耗费一定时间,所以平均耗时=(新元素存储时间+旧元素读取时间)/ 线程数,旧元素读取时间是很短的,相对新元素存储时间可以忽略不计,新元素存储时间与静态存储结构有关,可以对比①③⑤或②④⑥,所以才会呈现出结论11所述的规律。
结论12:通过对比①②或③④或⑤⑥可知,自定义存储结构相比字典更适合我们的工厂。
根据对比测试3和对比测试4我们发现自定义的静态存储结构相比双列表和字典更适合我们的工厂,再结合前几次测试说明通过结合分步制作流程、静态存储和自定义存储结构,我们当前的模式已经比直接调用反射更快了,虽然我们的优化方法对速率的提升不是极大幅度的,但是这可以看作是一个小小的进步。所以我们获得了目前最优的版本,具体代码请查看对应文件“泛型工厂应用改进尝试代码”。
实验总结
这个实验并不是去改进工厂模式,而应该是看作一种对工厂模式应用的优化,实验中我们通过分步制作流程、静态存储、自定义静态存储结构三种方式尝试对泛型工厂的应用进行优化,其中我们从单线程、多线程等方面对改进后的泛型工厂进行了测试,根据测试数据得出相关结论,实验中将优化思路和测试数据结合,一步一步地将实验的逻辑贯通起来,使得实验的操作井然有序,实验过程中对测试数据进行了筛选,并且将求取的平均值作为最终值,遵循控制变量单一的原则对比测试数据,计算各优化方式所提升的速率百分比。
实验不足
①本次实验采用的测试方式是手动测试,所以效率比较低,建议编写一套自动化测试工具。
②本次实验中对于测试数据的筛选规则比较粗略,建议借鉴一些科学的实验数据筛选规则。
③本次实验中各个测试的代码文件命名以及代码编写不规范,建议每一个测试都创建一个新的代码文件并制定代码编写规范。
实验设计UML图

实验数据
(一)对比测试
|
分类 |
线程数 |
分步制作流程 |
静态存储 |
中间处理逻辑 |
静态存储结构 | |||
|
测试 1 |
12 |
✘ |
✘ |
✘ |
无 | |||
|
单线程 |
(0.2473/11+0.2514/11+0.2251/11+0.2452/11+0.2544/11+0.1931/11+0.248/11+0.2527/11+0.2284/11+0.2871/11+0.2323/11+0.2368/11+0.2104/11+0.1991/11+0.2969/11+0.2088/11+0.2958/11+0.2857/11+0.2179/11+0.3030/11)/20 ≈ 0.02236ms | |||||||
|
多线程 并发 |
(356/11+353/11+369/11+473/11+382/11+474/11+443/11+405/11+376/11+383/11+402/11+367/11+402/11+385/11+382/11+395/11+424/11+402/11+409/11+388/11)/20 ≈ 36.2273ms | |||||||
|
测试 2 |
12 |
✘ |
✘ |
✔ |
无 | |||
|
单线程 |
(0.5067/11+0.4547/11+0.4635/11+0.4335/11+0.4951/11+0.4383/11+0.5566/11+0.5493/11+0.4451/11+0.4577/11+0.4205/11+0.4856/11+0.5487/11+0.4462/11+0.4429/11+0.4718/11+0.5608/11+0.4172/11+0.4573/11+0.546/11)/20 ≈ 0.04362ms | |||||||
|
直接new对象 |
1 |
✘ |
✘ |
✘ |
无 | |||
|
(0.0017+0.002+0.0015+0.0018+0.0026+0.0018+0.0018+0.002+0.0022+0.0018+0.0019+0.0021+0.0017+0.0017+0.0018+0.002+0.0026+0.002+0.0017+0.0021)/20 ≈ 0.0019ms | ||||||||
(二)对比测试2
|
分类 |
线程数 |
分步制作流程 |
静态存储 |
中间处理逻辑 |
静态存储结构 |
|
测试1 |
12 |
✔ |
✔ |
✔ |
List<Type> List<ConstructorInfo> |
|
单线程 |
(0.2630/11+0.2468/11+0.2430/11+0.2587/11+0.2437/11+0.2682/11+0.2648/11+0.3433/11+0.3038/11+0.3020/11+0.2680/11+0.2681/11+0.2528/11+0.2510/11+0.2504/11+0.3048/11+0.2898/11+0.2856/11+0.2640/11+0.2785/11)/20 ≈ 0.02477ms (0.2693/11+0.2842/11+0.2505/11+0.2595/11+0.2602/11+0.2629/11+0.2703/11+0.2734/11+0.2709/11+0.2729/11+0.3121/11+0.2894/11+0.2496/11+0.2532/11+0.3189/11+0.2733/11+0.2537/11+0.2820/11+0.2614/11+0.2532/11)/20 ≈ 0.02464ms | ||||
|
多线程 并发 |
(194/11+166/11+180/11+211/11+192/11+188/11+189/11+195/11+200/11+209/11+178/11+167/11+207/11+214/11+188/11+193/11+214/11+166/11+213/11+201/11)/20 ≈ 17.5682ms (197/11+215/11+208/11+186/11+204/11+209/11+212/11+188/11+206/11+181/11+188/11+194/11+186/11+189/11+212/11+194/11+192/11+188/11+191/11+167/11)/20 ≈ 17.7591ms | ||||
|
测试2 |
12 |
✔ |
✘ |
✔ |
无 |
|
单线程 |
(0.3962/11+0.4001/11+0.4079/11+0.3890/11+0.4202/11+0.3827/11+0.4235/11+0.4130/11+0.3975/11+0.3919/11+0.4119/11+0.3824/11+0.3859/11+0.3770/11+0.3767/11+0.3933/11+0.3854/11+0.3875/11+0.3833/11+0.3847/11)/20 ≈ 0.03586ms (0.4340/11+0.3873/11+0.3839/11+0.3800/11+0.4076/11+0.3882/11+0.3836/11+0.3853/11+0.4005/11+0.3831/11+0.3870/11+0.3805/11+0.3851/11+0.3887/11+0.3875/11+0.3986/11+0.4298/11+0.3871/11+0.3718/11+0.3918/11)/20 ≈ 0.03564ms | ||||
|
多线程 并发 |
(181/11+171/11+212/11+173/11+210/11+229/11+239/11+195/11+238/11+213/11+258/11+155/11+239/11+181/11+215/11+226/11+184/11+199/11+179/11+224/11)/20 ≈ 18.7318ms (179/11+195/11+216/11+237/11+200/11+222/11+199/11+209/11+241/11+240/11+236/11+173/11+243/11+198/11+210/11+218/11+153/11+212/11+184/11+200/11)/20 ≈ 18.9318ms | ||||
|
测试3 |
12 |
✘ |
✔ |
✔ |
List<Type> List<ConstructorInfo> |
|
单线程 |
(0.4438/11+0.4097/11+0.4702/11+0.5337/11+0.5064/11+0.4992/11+0.5202/11+0.4406/11+0.5306/11+0.4278/11+0.4877/11+0.4401/11+0.4657/11+0.4190/11+0.4321/11+0.4535/11+0.5456/11+0.4587/11+0.4653/11+0.5339/11)/20 ≈ 0.04310ms (0.4450/11+0.4828/11+0.4320/11+0.5183/11+0.4642/11+0.4664/11+0.5300/11+0.4376/11+0.5262/11+0.4368/11+0.4956/11+0.4988/11+0.4617/11+0.4656/11+0.4638/11+0.5258/11+0.4161/11+0.4754/11+0.5085/11+0.5026/11)/20 ≈ 0.04342ms | ||||
|
多线程 并发 |
(234/11+256/11+267/11+250/11+233/11+228/11+248/11+255/11+259/11+221/11+243/11+261/11+245/11+263/11+259/11+300/11+265/11+277/11+240/11+326/11)/20 ≈ 23.3182ms (276/11+260/11+334/11+281/11+259/11+262/11+328/11+279/11+247/11+248/11+264/11+284/11+251/11+260/11+267/11+235/11+243/11+238/11+253/11+245/11)/20 ≈ 24.1545ms | ||||
|
测试4 |
12 |
✔ |
✘ |
✘ |
无 |
|
单线程 |
(0.2384/11+0.2295/11+0.2178/11+0.2000/11+0.2438/11+0.2175/11+0.2378/11+0.2339/11+0.2340/11+0.2694/11+0.2605/11+0.2767/11+0.2464/11+0.2113/11+0.2838/11+0.2668/11+0.2412/11+0.2335/11+0.2805/11+0.3166/11)/20 ≈ 0.02245ms (0.2524/11+0.2246/11+0.2139/11+0.2437/11+0.2667/11+0.3445/11+0.2296/11+0.2475/11+0.2808/11+0.2380/11+0.2095/11+0.2219/11+0.2885/11+0.2187/11+0.2372/11+0.2984/11+0.2734/11+0.2104/11+0.3401/11+0.2183/11)/20 ≈ 0.02299ms | ||||
|
多线程 并发 |
(141/11+143/11+160/11+143/11+164/11+170/11+177/11+166/11+179/11+159/11+159/11+178/11+163/11+148/11+131/11+156/11+160/11+145/11+149/11+167/11)/20 ≈ 14.3545ms (159/11+136/11+146/11+139/11+144/11+121/11+154/11+145/11+178/11+164/11+147/11+137/11+133/11+175/11+150/11+170/11+159/11+166/11+173/11+166/11)/20 ≈ 13.9182ms | ||||
|
测试5 |
12 |
✘ |
✘ |
✘ |
无 |
|
单线程 |
(0.2051/11+0.2230/11+0.2466/11+0.2221/11+0.2142/11+0.2485/11+0.2797/11+0.2225/11+0.2487/11+0.2746/11+0.2476/11+0.2384/11+0.2052/11+0.2497/11+0.2496/11+0.2066/11+0.1999/11+0.2692/11+0.1951/11+0.2110/11)/20 ≈ 0.02116ms (0.2636/11+0.2345/11+0.2293/11+0.2750/11+0.2286/11+0.2743/11+0.2408/11+0.2233/11+0.2744/11+0.2332/11+0.2374/11+0.2537/11+0.2271/11+0.2208/11+0.2023/11+0.2841/11+0.2675/11+0.1943/11+0.2031/11+0.2189/11)/20 ≈ 0.02176ms | ||||
|
多线程 并发 |
(183/11+189/11+200/11+201/11+165/11+212/11+212/11+214/11+192/11+200/11+209/11+213/11+188/11+230/11+226/11+213/11+233/11+193/11+175/11+225/11)/20 ≈ 18.5136ms (208/11+195/11+189/11+211/11+204/11+172/11+205/11+203/11+185/11+201/11+194/11+195/11+183/11+190/11+206/11+221/11+198/11+219/11+173/11+220/11)/20 ≈ 18.0545ms | ||||
|
测试6 |
12 |
✘ |
✘ |
✔ |
无 |
|
单线程 |
(0.4733/11+0.5501/11+0.4496/11+0.4663/11+0.4831/11+0.5367/11+0.5336/11+0.4518/11+0.4509/11+0.5200/11+0.4885/11+0.4548/11+0.4147/11+0.4541/11+0.5479/11+0.4550/11+0.4590/11+0.4903/11+0.4512/11+0.4614/11)/20 ≈ 0.04360ms (0.4116/11+0.4936/11+0.4380/11+0.4129/11+0.4700/11+0.4814/11+0.4380/11+0.4606/11+0.3962/11+0.5373/11+0.4038/11+0.4558/11+0.4678/11+0.4151/11+0.4474/11+0.4822/11+0.5452/11+0.4627/11+0.4175/11+0.5360/11)/20 ≈ 0.04170ms | ||||
|
多线程 并发 |
(257/11+271/11+245/11+234/11+257/11+288/11+253/11+269/11+246/11+249/11+301/11+250/11+196/11+247/11+264/11+251/11+227/11+258/11+265/11+251/11)/20 ≈ 23.0864ms (254/11+243/11+288/11+270/11+262/11+248/11+253/11+272/11+229/11+254/11+237/11+265/11+263/11+229/11+258/11+261/11+230/11+279/11+259/11+234/11)/20 ≈ 23.1273ms | ||||
(三)简单测试
|
分类 |
线程数 |
分步制作流程 |
静态存储 |
中间处理逻辑 |
静态存储结构 |
|
测试1 |
5 |
✘ |
✘ |
✘ |
无 |
|
单线程 |
(0.007200/1+0.007300/1+0.009700/1+0.007300/1+0.006400/1+0.007100/1+0.007900/1+0.007100/1+0.008100/1+0.007600/1+0.009900/1+0.007000/1+0.007200/1+0.008900/1+0.008300/1+0.007500/1+0.008300/1+0.007700/1+0.007800/1+0.008800/1)/20 ≈ 0.007855ms (0.007200/1+0.007400/1+0.007100/1+0.006900/1+0.006400/1+0.007100/1+0.006700/1+0.008500/1+0.008200/1+0.007400/1+0.009300/1+0.007300/1+0.007400/1+0.006500/1+0.007400/1+0.007000/1+0.009000/1+0.008700/1+0.006100/1+0.006600/1)/20 ≈ 0.007410ms | ||||
|
测试2 |
5 |
✘ |
✘ |
✔ |
无 |
|
单线程 |
(0.009700/1+0.009400/1+0.009400/1+0.010100/1+0.010000/1+0.010000/1+0.013100/1+0.013700/1+0.010200/1+0.010100/1+0.010500/1+0.009900/1+0.010700/1+0.009900/1+0.009900/1+0.009600/1+0.010400/1+0.010000/1+0.015600/1+0.010600/1)/20 ≈ 0.010640ms (0.010000/1+0.009500/1+0.010800/1+0.011600/1+0.009700/1+0.010100/1+0.009700/1+0.009500/1+0.010300/1+0.013100/1+0.011300/1+0.009300/1+0.008900/1+0.008800/1+0.010900/1+0.009900/1+0.009200/1+0.009400/1+0.009300/1+0.009900/1)/20 ≈ 0.010060ms | ||||
|
测试3 |
5 |
✘ |
✔ |
✔ |
List<Type> List<ConstructorInfo> |
|
单线程 |
(0.007700/1+0.008400/1+0.008300/1+0.007400/1+0.007500/1+0.009500/1+0.008400/1+0.007400/1+0.008300/1+0.007700/1+0.007300/1+0.008000/1+0.008400/1+0.008200/1+0.008200/1+0.008300/1+0.009400/1+0.008000/1+0.008200/1+0.008200/1)/20 ≈ 0.008140ms (0.007900/1+0.007400/1+0.009600/1+0.008400/1+0.009000/1+0.007800/1+0.007100/1+0.008100/1+0.007800/1+0.009600/1+0.009000/1+0.007700/1+0.008900/1+0.008100/1+0.008300/1+0.007400/1+0.008100/1+0.008200/1+0.008400/1+0.008000/1)/20 ≈ 0.008240ms | ||||
(四)对比测试3
|
分类 |
线程数 |
分步制作流程 |
静态存储 |
中间处理逻辑 |
静态存储结构 |
|
测试1 |
12 |
✔ |
✔ |
✔ |
CustomStructs |
|
单线程 |
(0.1810/11+0.2175/11+0.2126/11+0.2066/11+0.2100/11+0.2342/11+0.1962/11+0.1825/11+0.1835/11+0.1835/11+0.1828/11+0.2141/11+0.1865/11+0.2178/11+0.1873/11+0.1740/11+0.1936/11+0.1996/11+0.2277/11+0.2215/11)/20 ≈ 0.01824ms (0.1822/11+0.2286/11+0.1970/11+0.1848/11+0.2127/11+0.2282/11+0.1951/11+0.2149/11+0.1884/11+0.1911/11+0.1861/11+0.1829/11+0.1990/11+0.2022/11+0.1862/11+0.2175/11+0.1809/11+0.1792/11+0.2209/11+0.1909/11)/20 ≈ 0.01804ms | ||||
|
多线程 并发 |
(170/11+151/11+152/11+113/11+169/11+149/11+114/11+140/11+136/11+174/11+147/11+160/11+178/11+160/11+155/11+101/11+178/11+165/11+174/11+153/11)/20 ≈ 13.8136ms (149/11+169/11+145/11+156/11+118/11+138/11+168/11+165/11+175/11+150/11+136/11+138/11+157/11+175/11+158/11+122/11+162/11+142/11+197/11+160/11)/20 ≈ 14.0000ms | ||||
|
测试2 |
12 |
✘ |
✔ |
✔ |
CustomStructs |
|
单线程 |
(0.3074/11+0.3424/11+0.3201/11+0.2986/11+0.2934/11+0.3014/11+0.3034/11+0.3018/11+0.3322/11+0.3200/11+0.2926/11+0.3323/11+0.3094/11+0.3185/11+0.3168/11+0.3261/11+0.2923/11+0.2849/11+0.3107/11+0.3043/11)/20 ≈ 0.02822ms (0.3742/11+0.3191/11+0.2873/11+0.2962/11+0.3410/11+0.2911/11+0.3319/11+0.2845/11+0.3574/11+0.3578/11+0.3241/11+0.3097/11+0.3087/11+0.3098/11+0.2826/11+0.3018/11+0.3322/11+0.3013/11+0.2931/11+0.3529/11)/20 ≈ 0.02889ms | ||||
|
多线程 并发 |
(188/11+173/11+177/11+170/11+184/11+143/11+156/11+179/11+186/11+175/11+189/11+174/11+177/11+145/11+164/11+165/11+168/11+180/11+182/11+163/11)/20 ≈ 15.6273ms (169/11+172/11+155/11+152/11+159/11+156/11+156/11+162/11+154/11+158/11+178/11+151/11+190/11+184/11+188/11+159/11+141/11+182/11+157/11+168/11)/20 ≈ 14.9591ms | ||||
(五)对比测试4
|
分类 |
线程数 |
分步制作流程 |
静态存储 |
中间处理逻辑 |
静态存储结构 |
|
测试1 |
3 |
✔ |
✔ |
✔ |
Dictionary<Type,ConstructorInfo> |
|
单线程 |
(0.1215/3+0.1344/3+0.1269/3+0.1191/3+0.1397/3+0.1207/3+0.1321/3+0.1596/3+0.1268/3+0.1240/3+0.1192/3+0.1367/3+0.1434/3+0.1453/3+0.1234/3+0.1206/3+0.1297/3+0.1248/3+0.1258/3+0.1314/3)/20 ≈ 0.04342ms (0.1167/3+0.1567/3+0.1168/3+0.1229/3+0.1276/3+0.1203/3+0.1504/3+0.1361/3+0.1232/3+0.1164/3+0.1346/3+0.1202/3+0.1226/3+0.1187/3+0.1164/3+0.1174/3+0.1163/3+0.1665/3+0.1142/3+0.1581/3)/20 ≈ 0.04287ms | ||||
|
多线程 并发 |
(11/1+12/1+11/1+13/1+11/1+12/1+13/1+12/1+12/1+12/1+12/1+12/1+12/1+13/1+14/1+12/1+12/1+11/1+13/1+12/1)/20 ≈ 12.1ms (11/1+13/1+12/1+13/1+12/1+11/1+14/1+12/1+13/1+13/1+12/1+12/1+13/1+12/1+11/1+12/1+12/1+12/1+12/1+12/1)/20 ≈ 12.2ms | ||||
|
测试2 |
3 |
✔ |
✔ |
✔ |
CustomStructs |
|
单线程 |
(0.1171/3+0.1120/3+0.1177/3+0.1102/3+0.1340/3+0.1139/3+0.1075/3+0.1267/3+0.1090/3+0.1290/3+0.1139/3+0.1130/3+0.1081/3+0.1237/3+0.1136/3+0.1240/3+0.1078/3+0.1137/3+0.1110/3+0.1166/3)/20 ≈ 0.03871ms (0.1133/3+0.1141/3+0.1197/3+0.1327/3+0.1143/3+0.1141/3+0.1109/3+0.1117/3+0.1085/3+0.1291/3+0.1203/3+0.1100/3+0.1157/3+0.1129/3+0.1165/3+0.1108/3+0.1321/3+0.1212/3+0.1133/3+0.1364/3)/20 ≈ 0.03929ms | ||||
|
多线程 并发 |
(9/1+10/1+8/1+8/1+8/1+8/1+9/1+8/1+8/1+8/1+10/1+8/1+8/1+8/1+8/1+9/1+8/1+8/1+8/1+10/1)/20 ≈ 8.45ms (9/1+8/1+8/1+8/1+8/1+9/1+9/1+8/1+9/1+8/1+9/1+8/1+8/1+8/1+8/1+8/1+8/1+8/1+10/1+8/1)/20 ≈ 8.35ms | ||||
|
测试3 |
6 |
✔ |
✔ |
✔ |
Dictionary<Type,ConstructorInfo> |
|
单线程 |
(0.1408/5+0.1424/5+0.1392/5+0.1536/5+0.1501/5+0.1513/5+0.1902/5+0.1561/5+0.1571/5+0.1512/5+0.1577/5+0.1521/5+0.1933/5+0.1562/5+0.1545/5+0.2309/5+0.1452/5+0.1729/5+0.1595/5+0.1806/5)/20 ≈ 0.03235ms (0.1523/5+0.2164/5+0.1717/5+0.1557/5+0.1463/5+0.1508/5+0.2573/5+0.1511/5+0.1788/5+0.1499/5+0.1659/5+0.1429/5+0.1838/5+0.1626/5+0.1680/5+0.1509/5+0.1619/5+0.2484/5+0.1864/5+0.1531/5)/20 ≈ 0.03454ms | ||||
|
多线程 并发 |
(86/5+67/5+65/5+69/5+69/5+65/5+65/5+73/5+80/5+71/5+76/5+64/5+78/5+79/5+65/5+66/5+71/5+66/5+54/5+64/5)/20 ≈ 13.9300ms (69/5+61/5+52/5+93/5+68/5+84/5+74/5+52/5+58/5+48/5+67/5+79/5+84/5+65/5+74/5+64/5+71/5+61/5+71/5+53/5)/20 ≈ 13.4800ms | ||||
|
测试4 |
6 |
✔ |
✔ |
✔ |
CustomStructs |
|
单线程 |
(0.1691/5+0.1360/5+0.1559/5+0.1489/5+0.1477/5+0.1709/5+0.1923/5+0.1363/5+0.1661/5+0.1486/5+0.1630/5+0.1409/5+0.1532/5+0.1470/5+0.1795/5+0.1560/5+0.1339/5+0.1381/5+0.1430/5+0.2145/5)/20 ≈ 0.03141ms (0.1588/5+0.1674/5+0.1497/5+0.1568/5+0.1681/5+0.1862/5+0.2099/5+0.1593/5+0.1454/5+0.1556/5+0.1401/5+0.1533/5+0.1433/5+0.1544/5+0.1479/5+0.1357/5+0.1419/5+0.1729/5+0.1588/5+0.1924/5)/20 ≈ 0.03198ms | ||||
|
多线程 并发 |
(50/5+40/5+33/5+45/5+44/5+46/5+42/5+52/5+46/5+59/5+50/5+52/5+54/5+46/5+64/5+43/5+67/5+39/5+46/5+32/5)/20 ≈ 9.5000ms (54/5+46/5+38/5+50/5+47/5+48/5+43/5+34/5+45/5+53/5+48/5+55/5+54/5+42/5+44/5+46/5+45/5+49/5+41/5+40/5)/20 ≈ 9.2200ms | ||||
|
测试5 |
12 |
✔ |
✔ |
✔ |
Dictionary<Type,ConstructorInfo> |
|
单线程 |
(0.2390/11+0.2359/11+0.2520/11+0.2482/11+0.2405/11+0.2176/11+0.2601/11+0.2245/11+0.2787/11+0.2277/11+0.2312/11+0.2577/11+0.3282/11+0.2152/11+0.2254/11+0.2216/11+0.2292/11+0.2399/11+0.2948/11+0.2497/11)/20 ≈ 0.02235ms (0.2633/11+0.2718/11+0.2210/11+0.2463/11+0.2688/11+0.2250/11+0.3660/11+0.2305/11+0.2052/11+0.2100/11+0.2185/11+0.2774/11+0.2096/11+0.2819/11+0.2111/11+0.2004/11+0.2871/11+0.3690/11+0.2612/11+0.2124/11)/20 ≈ 0.02289ms | ||||
|
多线程 并发 |
(199/11+219/11+183/11+211/11+203/11+199/11+197/11+219/11+207/11+203/11+215/11+188/11+215/11+227/11+223/11+227/11+221/11+192/11+194/11+197/11)/20 ≈ 18.8136ms (215/11+222/11+191/11+189/11+207/11+195/11+220/11+194/11+186/11+196/11+193/11+196/11+193/11+196/11+190/11+208/11+214/11+208/11+194/11+208/11)/20 ≈ 18.2500ms | ||||
|
测试6 |
12 |
✔ |
✔ |
✔ |
CustomStructs |
|
单线程 |
(0.2403/11+0.2195/11+0.1903/11+0.2078/11+0.1840/11+0.1850/11+0.1917/11+0.1984/11+0.1956/11+0.2035/11+0.2081/11+0.2137/11+0.2075/11+0.1892/11+0.2015/11+0.1949/11+0.2776/11+0.2533/11+0.2090/11+0.2211/11)/20 ≈ 0.01905ms (0.1960/11+0.2208/11+0.2386/11+0.2135/11+0.2000/11+0.1977/11+0.2291/11+0.2151/11+0.1998/11+0.1880/11+0.1861/11+0.2405/11+0.2101/11+0.1898/11+0.1945/11+0.2161/11+0.2081/11+0.2079/11+0.2975/11+0.2431/11)/20 ≈ 0.01951ms | ||||
|
多线程 并发 |
(178/11+152/11+159/11+126/11+176/11+126/11+150/11+159/11+160/11+149/11+157/11+152/11+164/11+151/11+171/11+163/11+156/11+178/11+131/11+144/11)/20 ≈ 14.1000ms (142/11+162/11+156/11+157/11+160/11+169/11+144/11+133/11+150/11+120/11+122/11+149/11+120/11+158/11+136/11+125/11+171/11+156/11+135/11+137/11)/20 ≈ 13.1909ms | ||||
实验代码
代码示例(C#)
using System;
using System.Threading;
using System.Reflection;
namespace Test2
{
//食物工厂,这是一个泛型工厂,可以生产所有继承了Food类的食物
class TestFactory3 : IFactory<Food>
{
//记录该工厂所生产的产品有多少种,如果涉及多个生产线,就需要采用数组
private static int typesCount = 0;
//单例模式,用于获取工厂实例
private TestFactory3() { }
private class TestFactoryHandler
{
public static readonly TestFactory3 instance = new TestFactory3();
public static readonly TestLine3 testLine = new TestLine3(typesCount);
}
public static TestFactory3 GetInstance(int p_typesCount)
{
typesCount = p_typesCount;
return TestFactoryHandler.instance;
}
//生产产品的公开方法,需要告诉生产线所要生产的产品类型
public Food Create(Type p_type)
{
return TestFactoryHandler.testLine.StartLine(p_type);
}
}
//食物生产线,这是一个食物生产线,对于生产过程相同的食物可以用同一个生产线,但是对于生产过程不同的食物应该定义不同的生产线
//假设我们所要生产的Bread和Biscuit的生产过程相同
class TestLine3 : ILine<Food>
{
//记录该生产线所生产的产品有多少种
private static int typesCount = 0;
//自定义静态存储结构CustomStructs
private static CustomStructs cis;
//访问锁
private static readonly object k_getmakeinfo = new object();
private static readonly object k_createfood = new object();
//用构造函数进行初始化
public TestLine3(int p_typesCount)
{
typesCount = p_typesCount;
cis = new CustomStructs(typesCount);
}
//启动生产过程的方法,其中包括了所有生产过程的调用以及调用条件
public Food StartLine(Type p_type)
{
lock (k_getmakeinfo)
{
if (!GetMakeInfo(p_type)) return null;
}
lock (k_createfood)
{
return MakeFood(p_type);
}
}
//具体的生产过程之一,获取生产资料和食物原料(中间处理逻辑)
private bool GetMakeInfo(Type p_type)
{
if (!cis.Contains(p_type))
{
ConstructorInfo ci = p_type.GetConstructor(new Type[0]);
if (ci == null) return false;
else
{
cis.Add(p_type, ci);
return true;
}
}
return true;
}
//具体的生产过程之一,制作食物
private Food MakeFood(Type p_type)
{
return (Food)cis[p_type].Invoke(null);
}
}
//食物之一,Bread
class TestBread : Food
{
public override void Eat()
{
Console.WriteLine("The bread tastes delicious.");
}
}
//食物之一,Biscuit
class TestBiscuit : Food
{
public override void Eat()
{
Console.WriteLine("The biscuit tastes delicious.");
}
}
//工厂接口,所有工厂都需要实现这个接口
public interface IFactory<T>
{
public T Create(Type p_type);
}
//生产线接口,所有生产线都需要实现这个接口
public interface ILine<T>
{
public T StartLine(Type p_type);
}
//食物抽象类,所有食物需要继承该类
public abstract class Food
{
public abstract void Eat();
}
//自定义静态存储结构
public class CustomStruct
{
public Type type;
public ConstructorInfo ci;
}
//自定义静态存储结构
public class CustomStructs
{
private CustomStruct[] cis;
public int capacity { get; private set; }
public int count { get; private set; }
public CustomStructs(int p_capacity)
{
capacity = p_capacity;
cis = new CustomStruct[p_capacity];
count = 0;
}
//重写索引器,可以直接通过Type下标访问
public ConstructorInfo this[Type type]
{
get
{
if (capacity == 0) return null;
foreach (CustomStruct item in cis)
{
if (item.type.Equals(type)) return item.ci;
}
return null;
}
set
{
foreach (CustomStruct item in cis)
{
if (item.type.Equals(type))
{
item.ci = value;
break;
}
}
}
}
//添加元素
public void Add(Type p_type, ConstructorInfo p_ci)
{
if (capacity != 0 && count < capacity)
{
cis[count] = new CustomStruct();
cis[count].type = p_type;
cis[count].ci = p_ci;
count++;
}
}
//检测是否包含对应Type类型元素
public bool Contains(Type p_type)
{
if (count == 0) return false;
foreach (CustomStruct item in cis)
{
if (item == null) return false;
if (item.type.Equals(p_type)) return true;
}
return false;
}
}
//测试代码
public class Test
{
//所生产的产品类型,我们这里有Bread和Biscuit
private static Type[] types = { typeof(TestBread), typeof(TestBiscuit) };
//单例模式结合工厂模式,获取工厂,并且需要告诉工厂当前需要生产多少种产品
private IFactory<Food> factory = TestFactory3.GetInstance(types.Length);
//用于记录运行至当前线程总耗时的变量
private static double sum = 0;
public void Test()
{
Thread t1 = new Thread(() =>
{
DateTime beginTime = DateTime.Now;
var food = factory.Create(types[0]);
DateTime endTime = DateTime.Now;
});
t1.Name = "t1";
Thread t2 = new Thread(() =>
{
DateTime beginTime = DateTime.Now;
var food = factory.Create(types[1]);
DateTime endTime = DateTime.Now;
sum += endTime.Subtract(beginTime).TotalMilliseconds;
Console.WriteLine("sum:" + sum);
});
t2.Name = "t2";
Thread t3 = new Thread(() =>
{
DateTime beginTime = DateTime.Now;
var food = factory.Create(types[0]);
DateTime endTime = DateTime.Now;
sum += endTime.Subtract(beginTime).TotalMilliseconds;
Console.WriteLine("sum:" + sum);
});
t3.Name = "t3";
Thread t4 = new Thread(() =>
{
DateTime beginTime = DateTime.Now;
var food = factory.Create(types[1]);
DateTime endTime = DateTime.Now;
sum += endTime.Subtract(beginTime).TotalMilliseconds;
Console.WriteLine("sum:" + sum);
});
t4.Name = "t4";
Thread t5 = new Thread(() =>
{
DateTime beginTime = DateTime.Now;
var food = factory.Create(types[0]);
DateTime endTime = DateTime.Now;
sum += endTime.Subtract(beginTime).TotalMilliseconds;
Console.WriteLine("sum:" + sum);
});
t5.Name = "t5";
Thread t6 = new Thread(() =>
{
DateTime beginTime = DateTime.Now;
var food = factory.Create(types[1]);
DateTime endTime = DateTime.Now;
sum += endTime.Subtract(beginTime).TotalMilliseconds;
Console.WriteLine("sum:" + sum);
});
t6.Name = "t6";
Thread t7 = new Thread(() =>
{
DateTime beginTime = DateTime.Now;
var food = factory.Create(types[0]);
DateTime endTime = DateTime.Now;
sum += endTime.Subtract(beginTime).TotalMilliseconds;
Console.WriteLine("sum:" + sum);
});
t7.Name = "t7";
Thread t8 = new Thread(() =>
{
DateTime beginTime = DateTime.Now;
var food = factory.Create(types[1]);
DateTime endTime = DateTime.Now;
sum += endTime.Subtract(beginTime).TotalMilliseconds;
Console.WriteLine("sum:" + sum);
});
t8.Name = "t8";
Thread t9 = new Thread(() =>
{
DateTime beginTime = DateTime.Now;
var food = factory.Create(types[0]);
DateTime endTime = DateTime.Now;
sum += endTime.Subtract(beginTime).TotalMilliseconds;
Console.WriteLine("sum:" + sum);
});
t9.Name = "t9";
Thread t10 = new Thread(() =>
{
DateTime beginTime = DateTime.Now;
var food = factory.Create(types[1]);
DateTime endTime = DateTime.Now;
sum += endTime.Subtract(beginTime).TotalMilliseconds;
Console.WriteLine("sum:" + sum);
});
t10.Name = "t10";
Thread t11 = new Thread(() =>
{
DateTime beginTime = DateTime.Now;
var food = factory.Create(types[0]);
DateTime endTime = DateTime.Now;
sum += endTime.Subtract(beginTime).TotalMilliseconds;
Console.WriteLine("sum:" + sum);
});
t11.Name = "t11";
Thread t12 = new Thread(() =>
{
DateTime beginTime = DateTime.Now;
var food = factory.Create(types[1]);
DateTime endTime = DateTime.Now;
sum += endTime.Subtract(beginTime).TotalMilliseconds;
Console.WriteLine("sum:" + sum);
});
t12.Name = "t12";
t1.Start();
// Thread.Sleep(100);
t2.Start();
// Thread.Sleep(100);
t3.Start();
// Thread.Sleep(100);
t4.Start();
// Thread.Sleep(100);
t5.Start();
// Thread.Sleep(100);
t6.Start();
// Thread.Sleep(100);
t7.Start();
// Thread.Sleep(100);
t8.Start();
// Thread.Sleep(100);
t9.Start();
// Thread.Sleep(100);
t10.Start();
// Thread.Sleep(100);
t11.Start();
// Thread.Sleep(100);
t12.Start();
}
}
}
代码解说
TestFactory3是我们的食物工厂,创建工厂时需要告诉工厂我们需要生产几种食物,我们要生产TestBread和TestBiscuit两种食物,按照实际情况Bread和Biscuit的生产过程是存在差异的,正常生产中通常也是两条生产线分别进行生产,这里为了简单我们就只建立一条生产线TestLine3,我们假设这个生产线能够生产这两种食物。我们直接调用Factory的Create的方法,并且传递一个Type类型参数,告诉工厂我们要生产什么类型的食物,而我们的工厂通过StartLine方法告诉生产线要生产什么类型的食物。我们这里对食物生产过程进行了简化,分为GetMakeInfo和MakeFood两个过程,并且我们要求二者按照顺序有序分步执行,这就是分步制作流程的体现。GetMakeInfo也就是获取泛型信息的过程,因为我们使用了静态存储,并且我们使用了自定义的静态存储结构CustomStructs,我们先检查静态存储结构中是否存在我们要生产的食物的信息,如果不存在则添加,然后进行下一步MakeFood操作,实际上这个操作我们依旧是调用反射创建对象。本次实验主要做出的改进就是将调用反射创建对象前反复获取反射信息的过程进行了静态化存储,同时针对多线程采用了分步制作流程来提升效率。结合我们的示例来说就是,我们这次生产了Bread和Biscuit两种食物并且对这两种食物的信息进行静态化存储,下一次如果需要再次生产这两种食物之一,则只需要从静态存储结构中获取即可,这就避免了重复通过反射获取反射信息的问题,其次,我们可能存在多个线程访问工厂生产食物,首先我们这里把食物的生产流程分为获取生产资料和制作两个过程,每个线程生产食物都需要经过这两个过程,多线程访问就必然存在资源访问冲突的问题,所以我们对食物生产的这两个过程分别加上了不同的访问锁。这里就有一个疑问了,我们完全可以只使用一个访问锁,让每个线程一次性完成所有生产流程把食物生产出来后再释放访问锁,为什么要给每个生产过程都加上不同的访问锁呢?如果我们这么做就会导致一个资源浪费的问题,第一个线程获取了生产资料,接下来要制作食物,那么这个时候获取生产资料这个过程就空闲了,我们完全可以让其它线程进入这个过程同样来获取生产资料,而不是所有线程都等着第一个线程完全把食物生产出来,所以我们可以让每个生产过程都可以被独立访问,而不是让所有生产过程捆绑在一起,这样就可以进一步缓解资源浪费的问题。当然本次实验中由于我们仅仅把生产过程分为了两个过程,所以没有很好体现出分步制作流程的优势,在后续实验中,我会做出进一步改进,本次实验存在诸多不足,希望有大佬能够不吝赐教和作出指正,谢谢。
如果这篇文章对你有帮助,请给作者点个赞吧!
浙公网安备 33010602011771号