《CLR Via C# 第3版》笔记之(二十) - 计时器及伪共享

简介:

计时器在很多应用场景中广泛应用,System.Threading命名空间下,有个Timer类可以完成计时器的操作。

下面来讨论下Timer类的使用及多个CPU内核同时运行线程时如何共享高速缓存(cache)的。

主要内容:

  • 计时器的使用
  • CPU高速缓存的伪共享

 

1. 计时器的使用

1.1 计时器的创建及改变设置

计时器创建的API都类似,有以下5种:

1
2
3
4
5
public  Timer(TimerCallback callback);
public  Timer(TimerCallback callback, object  state, int  dueTime, int  period);
public  Timer(TimerCallback callback, object  state, long  dueTime, long  period);
public  Timer(TimerCallback callback, object  state, TimeSpan dueTime, TimeSpan period);
public  Timer(TimerCallback callback, object  state, uint  dueTime, uint  period);

其中各个参数的含义如下:

  1. callback:定时执行的操作
  2. state:传递给callback的参数
  3. dueTime:首次调用callback时,需要等待多少毫秒
  4. period:每隔多少毫秒执行一次callback

 

下面例子演示如何创建一个计时器,并且在运行10秒后如何改变计时器的dueTimeperiod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using  System;
using  System.Threading;
 
public  class  CLRviaCSharp_20
{
     static  void  Main( string [] args)
     {
         // 定义一个定时器,每2秒执行一次
         Timer t = new  Timer(PrintTime, "Timer Test" , 0, 2000);
 
         // 主线程等待10秒(让计时器先执行6次)
         Thread.Sleep(10000);
 
         // 改变计时器的设置,停3秒后,每2秒执行一次
         t.Change(3000, 1000);
         Console.ReadKey( true );
     }
 
     private  static  void  PrintTime( object  state)
     {
         Thread.Sleep(3000);
         Console.WriteLine( "{0}: Now is {1}" , state, DateTime.Now);
     }
}

运行上面的例子,我们发现有以下现象:

  1. 刚开始每隔2秒打印一次当前时间
  2. 10秒后,每隔1秒打印一次当前时间
  3. callback(即PrintTime函数)中虽然有Sleep(3000);但计时器的下一次callback并没有等待本次callback完成就开始运行了。

对于上述的第三点,是由于Timer在每个period到期时,如果上个callback没完成,会在线程池中再启动一个线程来完成本次的callback。

 

1.2 耗时操作(操作消耗的时间 > 计时器的时间间隔)

但是,对于某些应用场景,可能需要callback一个一个顺序执行。

如果callback的耗时较长,希望下次callback能在本次callback完成后,再过period后执行。这时,就需要我们使用TimerChange方法实现。

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
using  System;
using  System.Threading;
 
public  class  CLRviaCSharp_20
{
     static  Timer t;
 
     static  void  Main( string [] args)
     {
         // 定义一个计时器,
         // period参数设置为Timeout.Infinite的意思是计时器执行一次就结束
         t = new  Timer(PrintTime, "Timer Test" , 0, Timeout.Infinite);
         Console.ReadKey( true );
     }
 
     private  static  void  PrintTime( object  state)
     {
         Thread.Sleep(3000);
         Console.WriteLine( "{0}: Now is {1}" , state, DateTime.Now);
 
         // 因为计时器执行一次就结束,所以在callback(即PrintTime)执行完后再启动一个计时器
         // 此计时器仍然设置period参数设置为Timeout.Infinite
         // 并且设置dueTime参数为2000毫秒,从而保证下次callback在本次callback完成2秒后执行
         // 这样不管每个callback耗用多少时间,两个callback之间的间隔时间始终是2秒
         t = new  Timer(PrintTime, "Timer Test" , 2000, Timeout.Infinite);
     }
}

这样运行结果就是每隔5秒打印一次时间了。

其中2秒表示callback之间的间隔。

另外3秒表示callback的执行时间。

 

2. CPU高速缓存的伪共享

为了提高CPU反复访问内存的能力,在CPU芯片上集成了高速缓存,它的访问速度非常快。

CPU从内存读取数据时,会把读取到的数据存在高速缓存中。下次直接从高速缓存中读取,从而提升性能。

CPU每次读取数据时填充的缓存大小与本机的CPU有关,我的机器是64byte。

可用CPU-Z来查看。

image

 

在多核的情况下,如何2个内核使用的数据在一个缓存线(即上面的64byte)中,那么反而会影响性能。

因为内核A如果改变了数据后,内核B需要把内核A使用的缓存线(即上面的64byte)拷入到B的缓存线中。

反之,内核A再次操作数据时,也要把内核B使用的缓存线(即上面的64byte)拷回来。

 

但是,如果内核A和内核B使用的数据不在一个缓存线中,那么性能就会提高,因为少了上面的不同内核之间缓存线的拷贝操作。

下面用例子来演示两种情况下的性能。

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
using  System;
using  System.Diagnostics;
using  System.Runtime.InteropServices;
using  System.Threading.Tasks;
 
public  class  CLRviaCSharp_20
{
     // 因为我的机器的缓存线为64byte,
     // 所以两个int型字段应该在同一个缓存线中
     private  class  ShareData1
     {
         public  int  field1;
         public  int  field2;
     }
 
     // 通过设置使得两个int型字段不在一个缓存线中
     [StructLayout(LayoutKind.Explicit)]
     private  class  ShareData2
     {
         [FieldOffset(0)]
         public  int  field1;
         [FieldOffset(64)]
         public  int  field2;
     }
 
     static  void  Main( string [] args)
     {
         int  iterations = 100000000;
         ShareData1 data1 = new  ShareData1();
 
         // 情况一:2个数据在一个缓存行中
         long  start = Stopwatch.GetTimestamp();
         Task t1 = new  Task(() => {
             for  ( int  i = 0; i < iterations; i++)
                 data1.field1++;
         });
 
         Task t2 = new  Task(() => {
             for  ( int  i = 0; i < iterations; i++)
                 data1.field2++;
         });
 
         t1.Start();
         t2.Start();
 
         Task.WaitAll();
         Console.WriteLine( "Totle time is : {0:N0}" , (Stopwatch.GetTimestamp() - start));
 
         // 情况二:2个数据不在一个缓存行中
         ShareData2 data2 = new  ShareData2();
         start = Stopwatch.GetTimestamp();
         Task t3 = new  Task(() =>
         {
             for  ( int  i = 0; i < iterations; i++)
                 data2.field1++;
         });
 
         Task t4 = new  Task(() =>
         {
             for  ( int  i = 0; i < iterations; i++)
                 data2.field2++;
         });
 
         t3.Start();
         t4.Start();
 
         Task.WaitAll();
         Console.WriteLine( "Totle time is : {0:N0}" , (Stopwatch.GetTimestamp() - start));
         Console.ReadKey( true );
     }
}

在我的双核机器上,明显第二种情况性能要优于第一种情况。



本文转自wang_yb博客园博客,原文链接:http://www.cnblogs.com/wang_yb/archive/2011/11/11/2245712.html,如需转载请自行联系原作者


目录
相关文章
|
4月前
|
C# Python
C# 笔记1 - 操作目录
C# 笔记1 - 操作目录
48 0
|
3月前
|
关系型数据库 C# 数据库
技术笔记:MSCL超级工具类(C#),开发人员必备,开发利器
技术笔记:MSCL超级工具类(C#),开发人员必备,开发利器
38 3
|
3月前
|
C#
C# 版本的 计时器类 精确到微秒 秒后保留一位小数 支持年月日时分秒带单位的输出
这篇2010年的文章是从别处搬运过来的,主要包含一个C#类`TimeCount`,该类有多个方法用于处理时间相关的计算。例如,`GetMaxYearCount`计算以毫秒为单位的最大年数,`GetCurrentTimeByMiliSec`将当前时间转换为毫秒,还有`SecondsToYYMMDDhhmmss`将秒数转换为年月日时分秒的字符串。此外,类中还包括一些辅助方法,如处理小数点后保留一位数字的`RemainOneFigureAfterDot`。
|
3月前
|
Java BI C#
技术笔记:SM4加密算法实现Java和C#相互加密解密
技术笔记:SM4加密算法实现Java和C#相互加密解密
43 0
|
4月前
|
C# Python
C# 笔记3 - 重载一系列像python那样的print()方法
C# 笔记3 - 重载一系列像python那样的print()方法
41 1
|
4月前
|
存储 C# C++
C# 笔记2 - 数组、集合与与文本文件处理
C# 笔记2 - 数组、集合与与文本文件处理
70 0
|
存储 网络协议 Java
C# 快速入门笔记
C# 快速入门笔记
C# 快速入门笔记
|
SQL 开发框架 算法
【读书笔记】《Effective C#》50条建议笔记整理
对《Effective C#:改善C#代码的50个有效方法》一书整理的读书笔记。
28175 5
【读书笔记】《Effective C#》50条建议笔记整理
|
C# 开发工具
C#滑动拼图验证码实现笔记
C# 是一个现代的、通用的、面向对象的编程语言,它是由微软(Microsoft)开发的,由 Ecma 和 ISO 核准认可的。突发奇想,动手开发一个C#滑动拼图验证码,下面是我开发过程的记录。
C#滑动拼图验证码实现笔记
|
存储 算法 编译器