“抢先式多任务”&“协同式多任务”

在“多任务”一文中,我们提到了“协同式多任务”与“抢先式多任务”的概念和二者的区别,谈到现在主流的多任务实现是“抢先式多任务”,并且留下关于未来“协同式多任务”是否会被淘汰的疑问。今天在此再举几个例子作对比,为前面提出的疑问做个推测。

第一个例子先来看看“抢先式多任务”程序的运行表现。在这个例子中,我们用C#创建一个console程序,这个程序要做的事很简单,就是在命令行窗口记录下从程序启动到做完一个正整数累加(从1累加到50亿)的时间,程序代码如下:

 1 class WasteTime
 2     {
 3         public void run()
 4         {
 5             Int64 j;
 6 
 7             for (Int64 i = 0; i < 5000000000; i++)
 8             {
 9                 j = i;
10             }
11         }
12 }
 1   class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Console.Write("Start  : ");
 6             Console.WriteLine(DateTime.Now.ToLongTimeString());
 7 
 8             /*
 9             // Method 1
10             // Simple Loop  
11             
12             Int64 j;
13 
14             for (Int64 i = 0; i < 5000000000; i++)
15             {
16                 j = i;
17             }
18             */
19             
20             
21             /*
22             // Method 2
23             // Using Object
24              
25              
26             WasteTime w1 = new WasteTime();
27             w1.run();
28 
29             WasteTime w2 = new WasteTime();
30             w2.run();
31             */
32 
33 
34             /*// method 3
35             // multithreads
36             */
37             WasteTime w1 = new WasteTime();
38             WasteTime w2 = new WasteTime();
39             
40             Thread t1 = new Thread(new ThreadStart(w1.run));
41             Thread t2 = new Thread(new ThreadStart(w2.run));
42             
43             t1.Start();
44             t2.Start();
45             
46             t1.Join();
47             t2.Join();
48             
49 
50             Console.Write("Finish : ");
51             Console.WriteLine(DateTime.Now.ToLongTimeString());
52 
53 
54             Console.ReadKey();
55         }
56     }

 

分别用三种不同的方法,得到如下的结果:

 

Method1

Method2

Method3

时间

13s

27s

16s

Cpu

使

                  注:以上数据在i5 3230m CPU电脑测试,测试过程中仅开启Visual Studio和资源监视器,图像右侧小部分曲线为开启截图软件导致

根据记录,可以看到如下现象:

1、 Method2和method3完成的工作量都是method1的两倍;

2、 method2大概用了method1两倍的时间做完累加运算;

3、 method3做完累加运算所需时间比method1多,但远比method2少;

4、 method1和method2在运算过程中都分别占用了一个cpu核心;

5、 method3在运算过程中占用了两个cpu核心;

分析以上现象,得到结论:在单线程的情况下,程序完成相同的运算所需时间基本相同,且在程序执行过程中始终占用同一个cpu内核,工作量和所需时间成线性比;在多线程的情况下,程序将任务分配在不同的cpu内核,工作量和所需时间不成线性比。

例子中,method2和method3其实可以看成是要分别完成两个任务(这里是两个相同的任务),在method2中,完成这两个任务是有先后顺序的,且在执行第二个任务的前提是第一个任务已经完成;在method3中,两个任务则是同时开始执行,且它们是独立完成的。method3的做法其实就是典型的“抢先式多任务”的实现,线程t1与t2由系统分配cpu资源和时间片,两个线程开始后,二者互相之间没有影响(仅限在这个例子中,因为线程是共享内存资源的),即不会因为其中一个线程执行的任务繁复而影响另一个线程的执行。但“抢先式多任务”的实现是需要付出更多的系统开销(overhead)的,所以,虽然两个任务分配到两个不同cpu内核,且并发执行,但它还是需要花费比method1更多的时间。这在前文也已经交代过,“抢先式多任务”的盛行,是以成熟的硬件支持为前提的。

我们针对method3,再进一步做一组测试,在这个测试中,我们分别创建1,2,3,4,5,6,7,8条线程,并记录下它们的执行时间和cpu使用情况,代码如下:

 1   class Program
 2   {
 3 
 4         static void Main(string[] args)
 5      {
 6 
 7             Console.Write("Start  : ");
 8        Console.WriteLine(DateTime.Now.ToLongTimeString());
 9 
10             // method 3
11             // multithreads
12 
13 
14             WasteTime w1 = new WasteTime();
15             WasteTime w2 = new WasteTime();
16             //WasteTime w3 = new WasteTime();
17             //WasteTime w4 = new WasteTime();
18             //WasteTime w5 = new WasteTime();
19             //WasteTime w6 = new WasteTime();
20             //WasteTime w7 = new WasteTime();
21             //WasteTime w8 = new WasteTime();
22 
23            
24             Thread t1 = new Thread(new ThreadStart(w1.run));
25             Thread t2 = new Thread(new ThreadStart(w2.run));
26             //Thread t3 = new Thread(new ThreadStart(w3.run));
27             //Thread t4 = new Thread(new ThreadStart(w4.run));
28             //Thread t5 = new Thread(new ThreadStart(w5.run));
29             //Thread t6 = new Thread(new ThreadStart(w6.run));
30             //Thread t7 = new Thread(new ThreadStart(w7.run));
31             //Thread t8 = new Thread(new ThreadStart(w8.run));
32 
33  
34             t1.Start();
35             t2.Start();
36             //t3.Start();
37             //t4.Start();
38             //t5.Start();
39             //t6.Start();
40             //t7.Start();
41             //t8.Start();
42 
43            
44 
45             t1.Join();
46             t2.Join();
47             //t3.Join();
48             //t4.Join();
49             //t5.Join();
50             //t6.Join();
51             //t7.Join();
52             //t8.Join(); 
53 
54             Console.Write("Finish : ");
55             Console.WriteLine(DateTime.Now.ToLongTimeString());
56             Console.ReadKey();
57 
58           }
59    }

 

 

测试结果记录如下:

线程数

1

2

3

时间

13s

16s

19s

Cpu使用率情况

 

 

 

 

线程数

4

5

6

时间

22s

28s

33s

Cpu使用率情况

 

 

 

 

 

线程数

7

8

 

时间

40s

46s

 

Cpu使用率情况

 

                注:以上数据在i5 3230m cpu上测试得到,测试过程中仅开启visual studio和资源监视器,图像右侧小部分曲线为开启截图软件导致

由以上数据可以看出:

1、 当线程数不超过cpu内核数时,每增加一条线程,完成任务付出的额外时间较之线程数超过cpu内核数时增加线程数完成任务所需时间更少;

2、 当线程数超过cpu内核数时,程序执行过程中,会有若干间隔时间cpu使用率骤降,且线程数越多,间隔越频繁;

由此可以进一步得出结论:在“抢先式多任务”实现中,尽管线程数超过了cpu内核数,但各线程还是被系统分配到了不同的内核执行;随着线程数量的增加,时间片的分配也变得更频繁,系统内销也随之加大,增加一条线程额外增加的时间也变得更多。

从中我们可以看出,“抢先式多任务”的原则,是要把诸多的任务进行分割,做平行处理,因此也带来了更大的系统开销。因为无论是时间片的分配、任务的挂起和恢复都是需要系统做更多的工作,并且这些工作都是建立在一定的硬件支持上的。

以上是“抢先式多任务”例子的分析,下面举个“协同式多任务”的例子,先看如下代码:

1     interface Runnable
2   {
4 
5         void run();
6 
7     }

 

 

 1     class Cat : Runnable
 2 
 3     {
 4 
 5         public void run()
 6 
 7         {
 8 
 9             Console.WriteLine("I am a cat");
10 
11         }
12 
13     }
 1     class Cow : Runnable
 2 
 3     {
 4 
 5         public void run()
 6 
 7         {
 8 
 9             Console.WriteLine("I am a cow");
10 
11         }
12 
13     }
 1     class Dog : Runnable
 2 
 3     {
 4 
 5  
 6 
 7         public void run()
 8 
 9         {
10 
11             Console.WriteLine("I am a dog");
12 
13         }
14 
15     }
 1     class WasteTime:Runnable
 2 
 3     {
 4 
 5         public void run()
 6 
 7         {
 8 
 9             int j;
10 
11             for (int i = 0; i < 1000000000;i++ )
12 
13             {
14 
15                 j = i;
16 
17             }
18 
19             Console.WriteLine("==========This is an interval=============");
20 
21         }
22 
23     }    

 

 1     class Program
 2 
 3     {
 4 
 5         static void Main(string[] args)
 6 
 7         {
 8 
 9             List<Runnable> eventqueue = new List<Runnable>();
10 
11  
12 
13             eventqueue.Add(new Cat());
14 
15             eventqueue.Add(new Dog());
16 
17             eventqueue.Add(new Cow());
18 
19             eventqueue.Add(new WasteTime());
20 
21  
22 
23             for (int i = 0; i < 10; i++)
24 
25             {
26 
27                 foreach (Runnable r in eventqueue)
28 
29                 {
30 
31  
32 
33                     r.run();
34 
35                 }
36 
37             }
38 
39  
40 
41             Console.ReadKey();
42 
43  
44 
45         }
46 
47     }

 

 

例子中的代码要做的事,其实就是反复的每隔一段时间运行Cat,Cow,Dog三个类实例的run方法(WasteTime在这里仅仅为了取得时间间隔的效果),三个实例的run方法可以看成三个任务,因为相对于WasteTime间隔的时间,三个实例执行run方法十分迅速,可以看成是同一时间内进行,这就是一个典型的“协同式多任务”的实现——因为当执行Cat的run方法时,只要程序还没跳出该方法的函数体,cpu的使用权就一直掌握在该方法所属任务手中,这里的run方法就相当于主程序的回调函数;同样的道理适用在Cow和Dog中。任务的执行并非由系统做出统筹分配,而是“该到谁就到谁”。假如我们在其中某个run方法体内加入了复杂的代码,使得程序执行长时间不能跳出方法体,这时其他任务将被推迟执行。由于这种有秩序的执行多任务的方式,“协同式多任务”相对于“抢先式多任务”并不需要太大的系统内销,而且其在编码时能使程序员更清晰当前思路,因为这是一种程序“自上而下”的执行方式;但它却要求每个回调函数不能承载太繁重的任务。

由此可见,两种多任务的实现方式各有所长,在可预见的未来,都会在不同的应用场合中发挥作用。

posted @ 2014-02-25 12:40  nianjun  阅读(2204)  评论(0)    收藏  举报