玩转C科技.NET

从学会做人开始认识这个世界!http://volnet.github.io

导航

多核心计算机运算

在上一篇《【代码保留】ThreadPoolHelper SampleCode》文章中,我们提到了ThreadPool。

而这篇文章将通过一组数据来展示多核计算、单核计算以及线程池在处理单核计算时候的一些区别。

本篇示例将继续引用上一篇示例中的例子,并作出补充,请自行查阅。(上一篇)

由于我们知道经过多年的工业发展,计算机已经逐渐从单核向多核发展,再者由于计算机(特指CPU)的发展在单核上的突破已经出现了瓶颈,而多核的发展正如火如荼地进行着,也许你手头的项目并不追求即时运算性能,也不追求高效率,甚至你的应用程序并不需要大量的运算量,但是从客户的角度分析,如果你的应用程序无法充分利用用户的CPU资源而造成大量的浪费以及没有必要的性能降低,大量的用户将逐渐迁移向多核计算领域的应用程序,因为它们确实比单核应用程序来得高效。

多核应用程序的编写与单核应用程序的编写相差甚大,过去旨在要求系统级的程序员关心的问题现在已经成为所有程序员,起码至少是大多数的程序员在未来一段时间内很快就迫不得已需要关心的问题。或许我们可以希望操作系统能够为我们完成并行运算的绝大部分工作,不过很遗憾,现在,或者至少在一小段时间内,操作系统都没有让人有理论上的满意度,它还担负不起如此重任,纷繁复杂的并行计算工作落实到了我们程序员的身上。

在上一篇中,我们实现了类似Parallel.Do的线程池方式,其实我们已经可以直接使用CTP版本的类库做这件事,而且通常将获得更优的性能,但我们这篇文章讨论的重点则是传统并行计算中的简单问题。

首先我们要求我们能够用肉眼和数据监测出我们的并行计算过程,以区别于单线程编程。下面我引入一个简单的函数WasteTime(),这是一个简单地不能再简单的函数,它要做的就是“浪费时间”(顾名思义),当然了,为了满足我们能够观测的需要,我们还需要引入时间点,由于本示例我采用了ConsoleApplication的方式进行演示,因此这里我将各个时间片输出到控制台以进行观测。下面的两个代码块将都会在本例中用到:

static void WasteTime1()
            {
            DateTime startTime = DateTime.Now;
            object o = new object();
            int i = 0;
            int j = 0;
            while (j < 1000000)
            {
            while (i < 1000000)
            {
            if (o is string)
            {
            o = new object();
            }
            else
            {
            o = new object();
            }
            i++;
            }
            j++;
            }
            DateTime endTime = DateTime.Now;
            TimeSpan timeElapsed
            = endTime - startTime;
            Console.WriteLine("startTime:{0}"
            , startTime.Millisecond);
            Console.WriteLine("endTime:{0}"
            , endTime.Millisecond);
            Console.WriteLine("timeElapsed:{0}"
            , timeElapsed.Milliseconds);
            Console.WriteLine("________________");
            }
 
static void WasteTime2()
            {
            DateTime startTime = DateTime.Now;
            for (int i = 0; i < 1000000000; i++)
            {
            string s = "b" + "b" + "b" + "b"
            + "b" + "b" + "b" + "b"
            + "b" + "b" + "b" + "b"
            + "b" + "b" + "b" + "b"
            + "b" + "b" + "b" + "b"
            + "b" + "b" + "b" + "b"
            + "b" + "b" + "b" + "b"
            + "b" + "b" + "b" + "b";
            }
            DateTime endTime = DateTime.Now;
            TimeSpan timeElapsed = endTime - startTime;
            Console.WriteLine("startTime:{0}"
            , startTime.Millisecond);
            Console.WriteLine("endTime:{0}"
            , endTime.Millisecond);
            Console.WriteLine("timeElapsed:{0}"
            , timeElapsed.Milliseconds);
            Console.WriteLine("________________");
            }

现在我们启用左边的耗时函数。

为了说明问题,现有两台计算机,其CPU分别为:

计算机A:单核迅驰;计算机B:双核酷睿;

将代码添加至程序,并对并行计算的函数添加函数调用。代码大致如下:

static void ThreadProc()
{
Console.WriteLine("Hello from the thread pool.()");
WasteTime1();
}

分别在A、B计算机上运行程序:

计算机A运行结果:
Hello from the thread pool.(object stateInfo)
            Hello from the thread pool.(object stateInfo)
            Hello from the thread pool.(object stateInfo)
            Hello from the thread pool.()
            Main thread does some work, then sleeps.
            startTime:156
            endTime:186
            timeElapsed:30
            ________________
            Hello from the thread pool.()
            startTime:186
            endTime:226
            timeElapsed:40
            ________________
            Hello from the thread pool.()
            startTime:226
            endTime:276
            timeElapsed:50
            ________________
            Hello from the thread pool.()
            startTime:276
            endTime:317
            timeElapsed:40
            ________________
            Hello from the thread pool.()
            startTime:317
            endTime:357
            timeElapsed:40
            ________________
            Hello from the thread pool.()
            startTime:357
            endTime:397
            timeElapsed:40
            ________________
            a
            b
            c
计算机B运行结果:
Hello from the thread pool.(object stateInfo)
            Main thread does some work, then sleeps.
            Hello from the thread pool.(object stateInfo)
            Hello from the thread pool.(object stateInfo)
            Hello from the thread pool.()
            Hello from the thread pool.()
            startTime:31
            endTime:46
            timeElapsed:15
            ____________________________
            Hello from the thread pool.()
            startTime:31
            endTime:46
            timeElapsed:15
            ____________________________
            Hello from the thread pool.()
            startTime:46
            endTime:62
            timeElapsed:15
            ____________________________
            Hello from the thread pool.()
            startTime:46
            endTime:62
            timeElapsed:15
            ____________________________
            Hello from the thread pool.()
            startTime:62
            endTime:78
            timeElapsed:15
            ____________________________
            a
            b
            c
            startTime:62
            endTime:78
            timeElapsed:15
            ____________________________

可以看出计算机A代码的运行时间为:(startTime - endTime)

(156 - 186)(186 - 226)(226 - 276)(276 - 317)(317 - 357)(357 - 397)

而计算机B代码的运行时间为:(startTime - endTime)

(31 - 46)(31 - 46)(46 - 62)(46 - 62)(62 - 78)(62 - 78)

由此可以很明显看出,双核计算机在处理多线程程序的时候,多个线程将被分配到不同的CPU内核中进行执行,而单核CPU在应对多线程程序的时候仍然是顺序执行程序的。

这看上去很像下面这张图:

threadpool1

单核的任务由主线程按顺序完成,而多核心的任务被指派给两个线程并在不同的核心上进行并行处理。

很明显多核处理器在性能上已经提供了成倍的性能,这将极大地优化我们的应用程序的执行效率,同时也将更充分地应用由硬件改进所带来的性能提升。

事实上,线程池采用默认均分的方式进行任务分配,假设现在有两个方法Method1()-Method10()和Method11()-Method20()被分别分配给两个线程A和B进行处理,而在Method1()-Method10()假设仅各有一个加法运算,而在Method11()-Method20()中有各种复杂的逻辑计算,默认情况下,线程A通常会比较早结束方法调用,但是线程B仍然在忙碌着,线程A会等待到线程B中的任务执行完毕后才可以退出。[在Parallel Framework中的相关方式则不会出现这种忙的人忙死闲的人很闲的情况,而在ThreadPool中,要实现任务本身的均分(假设任务1-14的工作量等于任务15-20的工作量),则需要程序员进行手动分配。]

既然用到了多线程技术,那么在单核计算机中它是如何工作的呢?

如果你根据上述症状判断它事实上并不进行多线程操作,则是一种错误的思考。被分配的任务分别被主线程调用,一旦满足一定的条件(如分配给任务1的时间片耗尽),将被挂起,执行下一个任务,这样以不至于线程仅仅只忙碌于一个任务而所有其他任务都在队列中排队的现象,关于调度的不同算法与操作系统知识相关。(Please Google or Baidu.)

下面一组数据展示了单核CPU+WasteTime2()的执行结果,由于WasteTime2()的耗时相对较长,因此这个数据将比较明显地表示线程切换的过程。

Main thread does some work, then sleeps.
            Hello from the thread pool.(object stateInfo)
            Hello from the thread pool.(object stateInfo)
            Hello from the thread pool.(object stateInfo)
            Hello from the thread pool.()
            Hello from the thread pool.()
            Hello from the thread pool.()
            Hello from the thread pool.()
            startTime:854
            endTime:806
            timeElapsed:951
            ________________
            Hello from the thread pool.()
            startTime:326
            endTime:132
            timeElapsed:805
            Hello from the thread pool.()
            ________________
            a
            b
            c
            startTime:930
            endTime:719
            timeElapsed:788
            ________________
            startTime:135
            endTime:723
            timeElapsed:588
            ________________
            startTime:806
            endTime:943
            timeElapsed:137
            ________________
            startTime:332
            endTime:85
            timeElapsed:752
            ________________

可以看出一个任务(ThreadProc)是被分步完成的,

总结,通过上面的描述,我们了解了一些关于线程池工作的基本工作原理,希望能够有助于理解多线程以及线程池工作的基本原理。很明显,只有编写了多线程程序,操作系统才可以将线程分配给多个核心进行处理。

多线程编程的具体细节

posted on 2007-12-23 23:32  volnet(可以叫我大V)  阅读(1069)  评论(0编辑  收藏  举报

使用Live Messenger联系我
关闭